@hviana/sema 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +136 -0
  42. package/dist/src/derive/src/deduction.js +159 -0
  43. package/dist/src/derive/src/index.d.ts +8 -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 +175 -0
  56. package/dist/src/geometry.js +823 -0
  57. package/dist/src/index.d.ts +17 -0
  58. package/dist/src/index.js +19 -0
  59. package/dist/src/ingest-cache.d.ts +41 -0
  60. package/dist/src/ingest-cache.js +165 -0
  61. package/dist/src/meter.d.ts +176 -0
  62. package/dist/src/meter.js +274 -0
  63. package/dist/src/mind/articulation.d.ts +6 -0
  64. package/dist/src/mind/articulation.js +99 -0
  65. package/dist/src/mind/attention.d.ts +414 -0
  66. package/dist/src/mind/attention.js +2082 -0
  67. package/dist/src/mind/bridge.d.ts +39 -0
  68. package/dist/src/mind/bridge.js +947 -0
  69. package/dist/src/mind/canonical.d.ts +34 -0
  70. package/dist/src/mind/canonical.js +93 -0
  71. package/dist/src/mind/graph-search.d.ts +294 -0
  72. package/dist/src/mind/graph-search.js +996 -0
  73. package/dist/src/mind/index.d.ts +9 -0
  74. package/dist/src/mind/index.js +5 -0
  75. package/dist/src/mind/junction.d.ts +137 -0
  76. package/dist/src/mind/junction.js +342 -0
  77. package/dist/src/mind/learning.d.ts +75 -0
  78. package/dist/src/mind/learning.js +270 -0
  79. package/dist/src/mind/match.d.ts +181 -0
  80. package/dist/src/mind/match.js +655 -0
  81. package/dist/src/mind/mechanisms/alu.d.ts +4 -0
  82. package/dist/src/mind/mechanisms/alu.js +36 -0
  83. package/dist/src/mind/mechanisms/cast.d.ts +89 -0
  84. package/dist/src/mind/mechanisms/cast.js +784 -0
  85. package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
  86. package/dist/src/mind/mechanisms/confluence.js +255 -0
  87. package/dist/src/mind/mechanisms/cover.d.ts +6 -0
  88. package/dist/src/mind/mechanisms/cover.js +227 -0
  89. package/dist/src/mind/mechanisms/extraction.d.ts +33 -0
  90. package/dist/src/mind/mechanisms/extraction.js +300 -0
  91. package/dist/src/mind/mechanisms/recall.d.ts +16 -0
  92. package/dist/src/mind/mechanisms/recall.js +364 -0
  93. package/dist/src/mind/mind.d.ts +337 -0
  94. package/dist/src/mind/mind.js +617 -0
  95. package/dist/src/mind/pipeline-mechanism.d.ts +172 -0
  96. package/dist/src/mind/pipeline-mechanism.js +465 -0
  97. package/dist/src/mind/pipeline.d.ts +49 -0
  98. package/dist/src/mind/pipeline.js +275 -0
  99. package/dist/src/mind/primitives.d.ts +66 -0
  100. package/dist/src/mind/primitives.js +306 -0
  101. package/dist/src/mind/rationale.d.ts +139 -0
  102. package/dist/src/mind/rationale.js +163 -0
  103. package/dist/src/mind/reasoning.d.ts +40 -0
  104. package/dist/src/mind/reasoning.js +280 -0
  105. package/dist/src/mind/recognition.d.ts +20 -0
  106. package/dist/src/mind/recognition.js +504 -0
  107. package/dist/src/mind/resonance.d.ts +23 -0
  108. package/dist/src/mind/resonance.js +0 -0
  109. package/dist/src/mind/trace.d.ts +15 -0
  110. package/dist/src/mind/trace.js +73 -0
  111. package/dist/src/mind/traverse.d.ts +126 -0
  112. package/dist/src/mind/traverse.js +650 -0
  113. package/dist/src/mind/types.d.ts +333 -0
  114. package/dist/src/mind/types.js +130 -0
  115. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  116. package/dist/src/rabitq-ivf/src/database.js +201 -0
  117. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  118. package/dist/src/rabitq-ivf/src/index.js +4 -0
  119. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  120. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  121. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  122. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  123. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  124. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  125. package/dist/src/sema.d.ts +31 -0
  126. package/dist/src/sema.js +63 -0
  127. package/dist/src/store-sqlite.d.ts +184 -0
  128. package/dist/src/store-sqlite.js +942 -0
  129. package/dist/src/store.d.ts +678 -0
  130. package/dist/src/store.js +1703 -0
  131. package/dist/src/vec.d.ts +31 -0
  132. package/dist/src/vec.js +109 -0
  133. package/package.json +1 -1
@@ -0,0 +1,9 @@
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";
8
+ export type { DepositReport } from "./learning.js";
9
+ export type { DecideGroundingData, NarrowDecisionData, Provenance, } from "./pipeline.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,137 @@
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
+ /** Session cache of the identity walks' pure reads (capped bytes,
49
+ * parent pages, container pages), keyed by the write-invalidated structural
50
+ * lifecycle object. 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 invalidateJunctionCache(ctx: MindContext): void;
67
+ export declare function cachedRead(ctx: MindContext, cache: WalkCache | null, id: number, cap: number): Uint8Array;
68
+ /** Tier 1 body, parameterised on already-resolved seed lists so a caller
69
+ * holding one side FIXED across several calls (synonym junctions) pays for
70
+ * that side's seeds once, not once per call. The byte-containment check
71
+ * below ensures only genuine containers are returned regardless of seeds.
72
+ *
73
+ * BOUNDED at corpus scale by three disciplines (profiled on a 17.7M-node
74
+ * store, where the unbounded form spent >90% of a query's CPU here):
75
+ * • PHRASE-SCALE READS — a junction container is by contract a whole the
76
+ * pair nearly exhausts (glue from a period to a phrase), so every visit
77
+ * reads at most `maxContainer + 1` bytes (`bytesPrefix` stops early).
78
+ * A node whose bytes exceed the cap cannot be a junction container, and
79
+ * its ancestors are strictly larger — the branch is PRUNED, never
80
+ * reconstructing a corpus-sized deposit (an oasst2 conversation) just
81
+ * to reject it.
82
+ * • EXPANSION BUDGET — at most √N·W nodes are popped in total: a √N-wide
83
+ * frontier (the one fan-out convention) through the ~W structural
84
+ * levels that separate phrase-scale content from its containers
85
+ * (perception trees are W-ary, so a junction container lies within a
86
+ * few levels of its parts). A side too common to decide within the
87
+ * budget abstains here and falls through to the resonance tier (the
88
+ * climb's own saturation semantics).
89
+ *
90
+ * REFUTED TIGHTENING (measured, 325K contexts): giving this walk
91
+ * edgeAncestors' cumulative LATERAL-CONE limit — abstain once the
92
+ * accumulated lateral entries pass √N — would cut most of the pops (47
93
+ * of 56 walks exceed √N lateral, and 47 exhaust the budget), but TWO OF
94
+ * THE FOUR walks that actually found a container had lateral spread of
95
+ * 1425 and 1426, far past √N. The cone limit is sound for
96
+ * edgeAncestors' question and wrong for this one: a junction container
97
+ * is legitimately reached across many containing structures. Half the
98
+ * successful junctions would be lost.
99
+ * • per-node hub guards — parent fan-outs beyond √N are hubs (not
100
+ * expanded); each node contributes at most one √N page of containers;
101
+ * √N collected candidates decide. */
102
+ export declare function junctionContainersFrom(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxContainer: number, leftSeeds: number[], rightSeeds: number[],
103
+ /** Shared expansion budget — a TIER's √N pops, not each walk's, when one
104
+ * tier issues several walks (synonym junctions try up to 2·haloQueryK
105
+ * siblings; without a shared budget each sibling would spend its own √N). */
106
+ budget?: {
107
+ n: number;
108
+ },
109
+ /** ORDER-FREE containment: also accept containers holding right-then-left.
110
+ * A junction is evidence that the two forms were LEARNT TOGETHER; which
111
+ * one the query happened to mention first is a fact about the query, not
112
+ * about the learnt whole. The walk is identical (the seed ascent does not
113
+ * depend on order) — only the byte-containment test gains a second probe,
114
+ * so order-freedom costs two indexOf calls per visited node, never a
115
+ * second walk. */
116
+ unordered?: boolean): Junction[];
117
+ /** Tier 1 entry point: every learnt whole that literally contains
118
+ * left-then-right, found by ascending the structural DAG (parents +
119
+ * containment links) from the two sides' content-addressed identities.
120
+ * Both sides' seeds resolved fresh, one call. */
121
+ export declare function junctionContainers(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxContainer: number, unordered?: boolean): Junction[];
122
+ /** Tier 2.5: synonym junctions — the container ascent (tier 1) applied to
123
+ * halo siblings of left and right. When a distributional synonym of one
124
+ * form participates in a learnt whole with the other form, the container
125
+ * between the synonym and the other side is valid evidence for the
126
+ * original pair. The container evidence is exact (content-addressed DAG
127
+ * ascent, with window-id-enhanced seeds so non-W-aligned siblings still
128
+ * ascend); the relaxation is only in which form occupies one side — a
129
+ * distributional sibling rather than the exact form.
130
+ *
131
+ * ONE expansion budget is shared by every sibling walk in this call, so
132
+ * cost is bounded at √N·W pops total regardless of how many siblings are
133
+ * tried. A sibling whose bytes exceed `maxInterior` is skipped (it
134
+ * cannot be junction-sized). */
135
+ export declare function junctionSynonyms(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxInterior: number, unordered?: boolean, sides?: JunctionSynonymSides, sharedBudget?: {
136
+ n: number;
137
+ }): Promise<SynonymJunction[]>;
@@ -0,0 +1,342 @@
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
+ const WALK_CACHE_MAX = 100_000;
57
+ export function walkCache(ctx) {
58
+ if (ctx.climbMemo === null)
59
+ return null;
60
+ let c = walkCaches.get(ctx._structMemoKey);
61
+ if (c === undefined) {
62
+ walkCaches.set(ctx._structMemoKey, c = { reads: new Map(), parents: new Map(), containers: new Map() });
63
+ }
64
+ else if (c.reads.size + c.parents.size + c.containers.size >= WALK_CACHE_MAX) {
65
+ c.reads.clear();
66
+ c.parents.clear();
67
+ c.containers.clear();
68
+ }
69
+ return c;
70
+ }
71
+ export function invalidateJunctionCache(ctx) {
72
+ walkCaches.delete(ctx._structMemoKey);
73
+ }
74
+ export function cachedRead(ctx, cache, id, cap) {
75
+ if (cache === null)
76
+ return read(ctx, id, cap + 1);
77
+ const hit = cache.reads.get(id);
78
+ // A cached COMPLETE read serves any cap; a cached truncated read serves
79
+ // any cap it already covers (the caller only checks `length > cap`).
80
+ if (hit !== undefined && (hit.complete || hit.b.length > cap))
81
+ return hit.b;
82
+ const b = read(ctx, id, cap + 1);
83
+ cache.reads.set(id, { b, complete: b.length <= cap });
84
+ return b;
85
+ }
86
+ function cachedParents(ctx, cache, id, limit) {
87
+ if (cache === null)
88
+ return ctx.store.parentsFirst(id, limit);
89
+ let v = cache.parents.get(id);
90
+ if (v === undefined) {
91
+ v = ctx.store.parentsFirst(id, limit);
92
+ cache.parents.set(id, v);
93
+ }
94
+ return v;
95
+ }
96
+ function cachedContainers(ctx, cache, id, limit) {
97
+ if (cache === null)
98
+ return ctx.store.containersSlice(id, 0, limit);
99
+ let v = cache.containers.get(id);
100
+ if (v === undefined) {
101
+ v = ctx.store.containersSlice(id, 0, limit);
102
+ cache.containers.set(id, v);
103
+ }
104
+ return v;
105
+ }
106
+ /** Tier 1 body, parameterised on already-resolved seed lists so a caller
107
+ * holding one side FIXED across several calls (synonym junctions) pays for
108
+ * that side's seeds once, not once per call. The byte-containment check
109
+ * below ensures only genuine containers are returned regardless of seeds.
110
+ *
111
+ * BOUNDED at corpus scale by three disciplines (profiled on a 17.7M-node
112
+ * store, where the unbounded form spent >90% of a query's CPU here):
113
+ * • PHRASE-SCALE READS — a junction container is by contract a whole the
114
+ * pair nearly exhausts (glue from a period to a phrase), so every visit
115
+ * reads at most `maxContainer + 1` bytes (`bytesPrefix` stops early).
116
+ * A node whose bytes exceed the cap cannot be a junction container, and
117
+ * its ancestors are strictly larger — the branch is PRUNED, never
118
+ * reconstructing a corpus-sized deposit (an oasst2 conversation) just
119
+ * to reject it.
120
+ * • EXPANSION BUDGET — at most √N·W nodes are popped in total: a √N-wide
121
+ * frontier (the one fan-out convention) through the ~W structural
122
+ * levels that separate phrase-scale content from its containers
123
+ * (perception trees are W-ary, so a junction container lies within a
124
+ * few levels of its parts). A side too common to decide within the
125
+ * budget abstains here and falls through to the resonance tier (the
126
+ * climb's own saturation semantics).
127
+ *
128
+ * REFUTED TIGHTENING (measured, 325K contexts): giving this walk
129
+ * edgeAncestors' cumulative LATERAL-CONE limit — abstain once the
130
+ * accumulated lateral entries pass √N — would cut most of the pops (47
131
+ * of 56 walks exceed √N lateral, and 47 exhaust the budget), but TWO OF
132
+ * THE FOUR walks that actually found a container had lateral spread of
133
+ * 1425 and 1426, far past √N. The cone limit is sound for
134
+ * edgeAncestors' question and wrong for this one: a junction container
135
+ * is legitimately reached across many containing structures. Half the
136
+ * successful junctions would be lost.
137
+ * • per-node hub guards — parent fan-outs beyond √N are hubs (not
138
+ * expanded); each node contributes at most one √N page of containers;
139
+ * √N collected candidates decide. */
140
+ export function junctionContainersFrom(ctx, left, right, maxContainer, leftSeeds, rightSeeds,
141
+ /** Shared expansion budget — a TIER's √N pops, not each walk's, when one
142
+ * tier issues several walks (synonym junctions try up to 2·haloQueryK
143
+ * siblings; without a shared budget each sibling would spend its own √N). */
144
+ budget,
145
+ /** ORDER-FREE containment: also accept containers holding right-then-left.
146
+ * A junction is evidence that the two forms were LEARNT TOGETHER; which
147
+ * one the query happened to mention first is a fact about the query, not
148
+ * about the learnt whole. The walk is identical (the seed ascent does not
149
+ * depend on order) — only the byte-containment test gains a second probe,
150
+ * so order-freedom costs two indexOf calls per visited node, never a
151
+ * second walk. */
152
+ unordered = false) {
153
+ const bound = hubBound(ctx);
154
+ const joinedLength = left.length + right.length;
155
+ const seeds = [...new Set([...leftSeeds, ...rightSeeds])];
156
+ if (seeds.length === 0)
157
+ return [];
158
+ const b = budget ?? { n: bound * ctx.space.maxGroup };
159
+ if (ctx.meter)
160
+ ctx.meter.junctionWalks++;
161
+ // DEPTH CAP: perception trees are W-ary and a junction container is
162
+ // phrase-scale, so it sits within ~log_W(maxContainer) structural levels
163
+ // of its parts — at most W levels for any practical W (plus the
164
+ // containment hop the seeds already are). Ancestry beyond that depth is
165
+ // strictly larger than any admissible container; walking it can only burn
166
+ // budget, never find a junction.
167
+ const maxDepth = ctx.space.maxGroup;
168
+ const out = [];
169
+ const cache = walkCache(ctx);
170
+ const seen = new Set(seeds);
171
+ const stack = seeds.map((id) => ({
172
+ id,
173
+ d: 0,
174
+ }));
175
+ while (stack.length > 0 && out.length < bound && b.n-- > 0) {
176
+ const { id: x, d } = stack.pop();
177
+ if (ctx.meter)
178
+ ctx.meter.junctionPops++;
179
+ const f = cachedRead(ctx, cache, x, maxContainer);
180
+ if (f.length > maxContainer)
181
+ continue; // beyond phrase scale: prune branch
182
+ if (unordered) {
183
+ // Order-free containment does NOT require disjoint occurrences: two
184
+ // grid-aligned fragments of the same whole legitimately OVERLAP inside
185
+ // it ("red " at 0 and " cir" at 3 in `red circle`), and both being
186
+ // literal substrings is the evidence. The interior is the gap between
187
+ // them when they are disjoint, empty otherwise. Only the containment
188
+ // test differs from the ordered form — and because occurrences may
189
+ // overlap or abut, `f.length > joinedLength` is too strict (grid
190
+ // fragments of one whole sum past it; "red " + "circle" exactly equals
191
+ // `red circle`). The container must be a STRICT super-form of each
192
+ // side, so that holding both is more than restating either.
193
+ const li = indexOf(f, left, 0);
194
+ const ri = li >= 0 ? indexOf(f, right, 0) : -1;
195
+ if (li >= 0 && ri >= 0 && f.length > Math.max(left.length, right.length)) {
196
+ const lo = Math.min(li + left.length, ri + right.length);
197
+ const hi = Math.max(li, ri);
198
+ out.push({
199
+ id: x,
200
+ interior: lo < hi ? f.subarray(lo, hi) : f.subarray(0, 0),
201
+ });
202
+ }
203
+ }
204
+ else if (f.length > joinedLength) {
205
+ const li = indexOf(f, left, 0);
206
+ if (li >= 0) {
207
+ const ri = indexOf(f, right, li + left.length);
208
+ if (ri >= 0) {
209
+ out.push({ id: x, interior: f.subarray(li + left.length, ri) });
210
+ }
211
+ }
212
+ }
213
+ if (d >= maxDepth)
214
+ continue; // deeper ancestry is beyond phrase scale
215
+ const parents = cachedParents(ctx, cache, x, bound + 1);
216
+ if (parents.length <= bound) { // beyond √N parents: a hub, not expanded
217
+ for (const p of parents) {
218
+ if (!seen.has(p)) {
219
+ seen.add(p);
220
+ stack.push({ id: p, d: d + 1 });
221
+ }
222
+ }
223
+ }
224
+ // Containment fan-out under the SAME hub reading as parents: a node
225
+ // whose containers fill a whole √N page is COMMON content — its
226
+ // containment ancestry reaches a non-discriminative slice of the corpus
227
+ // (the climb's saturation semantics), and walking it would spend the
228
+ // entire budget discovering nothing a junction could use. Such a node
229
+ // is not expanded through containment; a pair whose sides are common
230
+ // abstains here in a handful of pops and falls through to the resonance
231
+ // tier. Below the page bound the read IS the full container list, so
232
+ // the walk stays exact exactly where identity evidence discriminates.
233
+ const containers = cachedContainers(ctx, cache, x, bound);
234
+ if (containers.length < bound) {
235
+ for (const c of containers) {
236
+ if (!seen.has(c)) {
237
+ seen.add(c);
238
+ stack.push({ id: c, d: d + 1 });
239
+ }
240
+ }
241
+ }
242
+ }
243
+ return out;
244
+ }
245
+ /** Tier 1 entry point: every learnt whole that literally contains
246
+ * left-then-right, found by ascending the structural DAG (parents +
247
+ * containment links) from the two sides' content-addressed identities.
248
+ * Both sides' seeds resolved fresh, one call. */
249
+ export function junctionContainers(ctx, left, right, maxContainer, unordered = false) {
250
+ return junctionContainersFrom(ctx, left, right, maxContainer, junctionSeeds(ctx, left), junctionSeeds(ctx, right), undefined, unordered);
251
+ }
252
+ /** Tier 2.5: synonym junctions — the container ascent (tier 1) applied to
253
+ * halo siblings of left and right. When a distributional synonym of one
254
+ * form participates in a learnt whole with the other form, the container
255
+ * between the synonym and the other side is valid evidence for the
256
+ * original pair. The container evidence is exact (content-addressed DAG
257
+ * ascent, with window-id-enhanced seeds so non-W-aligned siblings still
258
+ * ascend); the relaxation is only in which form occupies one side — a
259
+ * distributional sibling rather than the exact form.
260
+ *
261
+ * ONE expansion budget is shared by every sibling walk in this call, so
262
+ * cost is bounded at √N·W pops total regardless of how many siblings are
263
+ * tried. A sibling whose bytes exceed `maxInterior` is skipped (it
264
+ * cannot be junction-sized). */
265
+ export async function junctionSynonyms(ctx, left, right, maxInterior, unordered = false, sides, sharedBudget) {
266
+ const s = sides ?? await loadJunctionSynonymSides(ctx, left, right);
267
+ if (s.leftId === null && s.rightId === null)
268
+ return [];
269
+ // ── Tier 2.5a: single-synonym — one side replaced by a halo sibling ──────
270
+ // ONE shared expansion budget across BOTH directions of this tier.
271
+ const singleBudget = sharedBudget ??
272
+ { n: hubBound(ctx) * ctx.space.maxGroup };
273
+ const singleOut = new Map();
274
+ const keepBest = (map, j, tier, confidence) => {
275
+ const prev = map.get(j.id);
276
+ if (prev === undefined || confidence > prev.confidence) {
277
+ map.set(j.id, { ...j, tier, confidence });
278
+ }
279
+ };
280
+ // Left-side synonyms: containers of sibling+right. `right`'s seeds are
281
+ // FIXED across every sibling this loop tries.
282
+ if (s.leftId !== null) {
283
+ const rightSeeds = junctionSeeds(ctx, right);
284
+ for (const sib of s.leftSiblings) {
285
+ const sibBytes = read(ctx, sib.id, maxInterior + 1);
286
+ if (sibBytes.length === 0 || sibBytes.length > maxInterior)
287
+ continue;
288
+ const containers = junctionContainersFrom(ctx, sibBytes, right, sibBytes.length + right.length + maxInterior, junctionSeeds(ctx, sibBytes), rightSeeds, singleBudget, unordered);
289
+ for (const c of containers) {
290
+ keepBest(singleOut, c, "single-synonym", sib.score);
291
+ }
292
+ }
293
+ }
294
+ // Right-side synonyms: containers of left+sibling. `left`'s seeds are
295
+ // likewise fixed across this loop.
296
+ if (s.rightId !== null) {
297
+ const leftSeeds = junctionSeeds(ctx, left);
298
+ for (const sib of s.rightSiblings) {
299
+ const sibBytes = read(ctx, sib.id, maxInterior + 1);
300
+ if (sibBytes.length === 0 || sibBytes.length > maxInterior)
301
+ continue;
302
+ const containers = junctionContainersFrom(ctx, left, sibBytes, left.length + sibBytes.length + maxInterior, leftSeeds, junctionSeeds(ctx, sibBytes), singleBudget, unordered);
303
+ for (const c of containers) {
304
+ keepBest(singleOut, c, "single-synonym", sib.score);
305
+ }
306
+ }
307
+ }
308
+ if (singleOut.size > 0)
309
+ return [...singleOut.values()];
310
+ // ── Tier 2.5b: double-synonym — BOTH sides replaced, tried only when
311
+ // single-synonym found NOTHING. Every (leftSibling, rightSibling) pair,
312
+ // sorted deterministically, bounded to haloQueryK pairs total, ONE fresh
313
+ // shared budget for the whole tier. ─────────────────────────────────────
314
+ if (s.leftSiblings.length === 0 || s.rightSiblings.length === 0)
315
+ return [];
316
+ const pairs = [];
317
+ for (const l of s.leftSiblings) {
318
+ for (const r of s.rightSiblings) {
319
+ pairs.push({ l, r, confidence: Math.min(l.score, r.score) });
320
+ }
321
+ }
322
+ pairs.sort((a, b) => b.confidence - a.confidence ||
323
+ a.l.id - b.l.id ||
324
+ a.r.id - b.r.id);
325
+ const doubleOut = new Map();
326
+ const budget = sharedBudget ??
327
+ { n: hubBound(ctx) * ctx.space.maxGroup };
328
+ const tries = Math.min(pairs.length, ctx.cfg.haloQueryK);
329
+ for (let i = 0; i < tries; i++) {
330
+ const { l, r, confidence } = pairs[i];
331
+ const lBytes = read(ctx, l.id, maxInterior + 1);
332
+ const rBytes = read(ctx, r.id, maxInterior + 1);
333
+ if (lBytes.length === 0 || lBytes.length > maxInterior ||
334
+ rBytes.length === 0 || rBytes.length > maxInterior)
335
+ continue;
336
+ const containers = junctionContainersFrom(ctx, lBytes, rBytes, lBytes.length + rBytes.length + maxInterior, junctionSeeds(ctx, lBytes), junctionSeeds(ctx, rBytes), budget, unordered);
337
+ for (const c of containers) {
338
+ keepBest(doubleOut, c, "double-synonym", confidence);
339
+ }
340
+ }
341
+ return [...doubleOut.values()];
342
+ }
@@ -0,0 +1,75 @@
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
+ /** What one ingested item deposited — reported through {@link ingest}'s
32
+ * optional `onDeposit` callback. Pure provenance read-out: node ids are
33
+ * content-addressed, so two byte-identical items report the SAME ids (that
34
+ * is content addressing working, not an error), and the callback observes
35
+ * the deposit without influencing it. */
36
+ export interface DepositReport {
37
+ /** Zero-based position of the item in the ingested input (0 for a scalar
38
+ * or single pair). */
39
+ index: number;
40
+ /** Whether the item was a bare experience or a (context, continuation)
41
+ * pair. */
42
+ kind: "one" | "pair";
43
+ /** Root node id of the item's (context) bytes. */
44
+ contextId: number;
45
+ /** Root node id of the continuation bytes — pairs only. */
46
+ continuationId?: number;
47
+ }
48
+ /** Ingest a pair (context, continuation) — learn an edge and pour halos.
49
+ * Returns the deposited root ids (context, continuation) — a pure
50
+ * read-out; callers that ignore it behave exactly as before. */
51
+ export declare function ingestPair(ctx: MindContext, ctxInput: Input, cont: Input): Promise<{
52
+ ctxId: number;
53
+ contId: number;
54
+ }>;
55
+ /** Dispatch the public ingest input shapes onto one-input / pair handlers —
56
+ * THE one reading of ingest's polymorphic surface (scalar, (context,
57
+ * continuation) pair, or a list mixing bare inputs and pairs). Both ingest
58
+ * paths — the direct one below and {@link CachedIngest} — route through
59
+ * this, so the shape-detection can never drift between them again (the
60
+ * ingest cache once re-implemented it and drifted). */
61
+ export declare function dispatchIngest(input: Input | (Input | [Input, Input])[], second: Input | undefined, onOne: (input: Input) => Promise<Sema & {
62
+ id: number;
63
+ }>, onPair: (ctxInput: Input, cont: Input) => Promise<void>): Promise<(Sema & {
64
+ id: number;
65
+ }) | undefined>;
66
+ /** Ingest an input or array of inputs/pairs. The public ingest entry point.
67
+ *
68
+ * `onDeposit`, when given, is invoked once per ingested item with the
69
+ * deposited root node ids ({@link DepositReport}) — item-level provenance
70
+ * for tooling that needs to know which stored node an ingested item became.
71
+ * Purely observational: the callback runs after the item's deposit
72
+ * completed and nothing reads its result. */
73
+ export declare function ingest(ctx: MindContext, input: Input | (Input | [Input, Input])[], second?: Input, onDeposit?: (report: DepositReport) => void): Promise<(Sema & {
74
+ id: number;
75
+ }) | undefined>;