@hviana/sema 0.2.6 → 0.2.7

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 (131) 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 +400 -0
  64. package/dist/src/mind/attention.js +1763 -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
@@ -0,0 +1,161 @@
1
+ // ingest-cache.ts — ingest-level cache for Mind.
2
+ //
3
+ // Wraps a Mind and memoises the expensive, content-determined half of ingestion:
4
+ // turning ONE input (a context, a continuation, or a bare experience) into its
5
+ // interned graph nodes. Perception (riverFold) and interning are a PURE function
6
+ // of the input bytes — the same bytes always cut the same way and hash-cons to
7
+ // the same node ids — so the result can be cached by content and reused wherever
8
+ // those same bytes appear again, in ANY role.
9
+ //
10
+ // This is why the cache keys on the single input, not on the (context,
11
+ // continuation) pair: real training corpora repeat content at the INPUT level far
12
+ // more than at the pair level — a shared system/tool preamble in front of every
13
+ // row, a common answer ("Yes.", a refusal) under many questions, an accumulated
14
+ // dialogue prefix, an exactly-duplicated row. A pair-level key sees none of these
15
+ // (the pair as a whole is almost always unique); the input-level key catches them
16
+ // all, and a context that recurs as a continuation (or vice versa) hits too.
17
+ //
18
+ // On a cache hit only the cheap relational writes are replayed — the edge and the
19
+ // two halo pours for a pair, or the part-chain links for a single — while the
20
+ // perceive + internTree work (the dominant cost, and on a large store the dominant
21
+ // VECTOR-INDEX cost too) is skipped entirely. The interned node ids are stable
22
+ // across eviction: a re-perceive of evicted content reproduces the same ids by
23
+ // the store's hash-cons, so correctness never depends on a hit.
24
+ //
25
+ // DEPOSITION IS THE MIND'S OWN: every miss goes through the same
26
+ // {@link deposit} the direct Mind.ingest path uses — perception, interning,
27
+ // sub-span/window indexing, durable CONTAINMENT edges, whole-stream flat
28
+ // branch, changed-node tracking against the Mind's `_prevSeen` — so a store
29
+ // trained through this cache is structurally IDENTICAL to one trained by
30
+ // calling mind.ingest() directly (this class used to re-implement the deposit
31
+ // and drifted: no indexSubSpans/addContainer, wrong part-link stride). Only
32
+ // the memo layer lives here. Behaviour is identical to mind.ingest() (links
33
+ // and halos are reinforced on every call), just faster when content repeats.
34
+ import { deposit, dispatchIngest, ingestOne as depositOne, } from "./mind/learning.js";
35
+ import { bindSeat, companySignature } from "./sema.js";
36
+ import { BoundedMap } from "./store.js";
37
+ /**
38
+ * An ingest cache layered over a Mind.
39
+ *
40
+ * Usage (drop-in for mind.ingest in training loops):
41
+ * const ci = new CachedIngest(mind);
42
+ * await ci.ingest(ctx, cont); // caches both sides
43
+ * await ci.ingest("some text"); // caches the single text
44
+ * await ci.ingest([[ctx, cont]]); // array form
45
+ *
46
+ * All other Mind methods (respond, save, …) are accessed on `ci.mind`.
47
+ */
48
+ export class CachedIngest {
49
+ mind;
50
+ /** One unified, content-addressed memo of interned inputs. Each value holds
51
+ * Float32 root vector (D·4 bytes) plus a small id array, so the byte budget
52
+ * is spent on the inputs most worth remembering (LRU). */
53
+ _memo;
54
+ hits = 0;
55
+ misses = 0;
56
+ constructor(mind) {
57
+ this.mind = mind;
58
+ this._memo = this.newMemo();
59
+ }
60
+ newMemo() {
61
+ return new BoundedMap(this.mind.cfg.store.ingestCacheBytes, (c) => c.rootV.byteLength + c.partIds.length * 4 + 8 + c.keyBytes);
62
+ }
63
+ // ── cache key ───────────────────────────────────────────────────────────
64
+ // Content key for an input, or null when the input cannot be keyed reliably
65
+ // — in which case it BYPASSES the cache (recomputed every time) rather than
66
+ // risk a collision returning the wrong nodes. A string is its own key; raw
67
+ // bytes hash by FNV-1a (+ length, so a hash collision still needs an equal
68
+ // length to alias); a Grid / Grid[] has no cheap stable key, so it bypasses.
69
+ keyOf(input) {
70
+ if (typeof input === "string")
71
+ return "S:" + input;
72
+ if (input instanceof Uint8Array) {
73
+ // FNV-1a — no spread onto the call stack, safe on large binary inputs.
74
+ let h = 0x811c9dc5 >>> 0;
75
+ for (let i = 0; i < input.length; i++) {
76
+ h ^= input[i];
77
+ h = Math.imul(h, 0x01000193) >>> 0;
78
+ }
79
+ return "B:" + (h >>> 0).toString(16) + ":" + input.length;
80
+ }
81
+ return null; // Grid / Grid[] — bypass the cache
82
+ }
83
+ // ── public API ────────────────────────────────────────────────────────
84
+ async ingest(input, second) {
85
+ // One shape-reading for both ingest paths — see {@link dispatchIngest}.
86
+ return dispatchIngest(input, second, (i) => this.ingestOne(i), (a, b) => this.ingestPair(a, b));
87
+ }
88
+ // ── the one cached step: perceive + intern + index ONE input ──────────────
89
+ /** Resolve an input to its interned nodes, from the memo when its content has
90
+ * been seen before, else by the Mind's OWN untracked {@link deposit}
91
+ * (perceive + intern + sub-span/containment indexing — identical to the
92
+ * direct path; those writes are durable and idempotent, so a later memo hit
93
+ * legitimately skips them). This is the single expensive operation both
94
+ * ingest paths share; caching it here is what makes every repeated input —
95
+ * in any role — cheap. */
96
+ async resolveInput(input) {
97
+ const key = this.keyOf(input);
98
+ if (key !== null) {
99
+ const cached = this._memo.get(key);
100
+ if (cached) {
101
+ this.hits++;
102
+ return cached;
103
+ }
104
+ }
105
+ this.misses++;
106
+ // The full deposit, untracked (a continuation must not break the tracked
107
+ // deposit chain). `partIds` are the root's immediate children, already
108
+ // interned by the deposit — free to capture. Only the root vector and
109
+ // ids are retained; the tree is GC'd.
110
+ const { tree, rootId, ids } = await deposit(this.mind, input, false);
111
+ const partIds = tree.kids
112
+ ? tree.kids.map((k) => ids.get(k))
113
+ : [rootId];
114
+ const entry = {
115
+ rootV: tree.v,
116
+ rootId,
117
+ partIds,
118
+ // ~2 bytes per UTF-16 code unit — the key is retained by the map.
119
+ keyBytes: key !== null ? key.length * 2 : 0,
120
+ };
121
+ if (key !== null)
122
+ this._memo.set(key, entry);
123
+ return entry;
124
+ }
125
+ // ── pair ingest ───────────────────────────────────────────────────────
126
+ async ingestPair(ctx, cont) {
127
+ // The CONTEXT side is TRACKED (it continues the Mind's deposit chain and
128
+ // is the growing, unique-per-turn side — a cache miss anyway), via the
129
+ // same {@link deposit} the direct path uses; the continuation resolves
130
+ // through the memo.
131
+ const c = await deposit(this.mind, ctx, true);
132
+ const b = await this.resolveInput(cont);
133
+ // EDGE on the FULL context (disambiguates a shared pivot). HALO only on the
134
+ // CHANGED NODES — the coherent new subtrees this turn adds — so a recurring
135
+ // shared prefix never collects halo mass. (See Mind.ingestPair.)
136
+ await this.mind.store.link(c.rootId, b.rootId);
137
+ // Halos pour company SIGNATURES (identity), not gists (content) — see
138
+ // companySignature in sema.ts.
139
+ const contSeat = bindSeat(this.mind.space, companySignature(this.mind.space, b.rootId), 1);
140
+ for (const part of c.changed) {
141
+ const partId = c.ids.get(part);
142
+ await this.mind.store.pourHalo(partId, contSeat);
143
+ await this.mind.store.pourHalo(b.rootId, bindSeat(this.mind.space, companySignature(this.mind.space, partId), 0));
144
+ }
145
+ }
146
+ // ── one-side ingest ───────────────────────────────────────────────────
147
+ async ingestOne(input) {
148
+ // A bare experience is the direct path verbatim — tracked deposit, root
149
+ // marked a resonance target, part chain linked at the maxGroup stride.
150
+ // (The tracked side is a memo miss by design, so there is nothing for the
151
+ // cache to add here.)
152
+ return depositOne(this.mind, input);
153
+ }
154
+ // ── helpers ───────────────────────────────────────────────────────────
155
+ clear() {
156
+ this._memo = this.newMemo();
157
+ }
158
+ get size() {
159
+ return this._memo.size;
160
+ }
161
+ }
@@ -0,0 +1,6 @@
1
+ import type { MindContext } from "./types.js";
2
+ /** Re-voice an answer in the asker's own words. For each recognised form
3
+ * in the answer, find a concept-sibling in the query (by halo resonance)
4
+ * and substitute the asker's wording. The search's own cover mechanism
5
+ * splices the substitutes into the answer exactly where the forms sit. */
6
+ export declare function articulate(ctx: MindContext, answer: Uint8Array, query: Uint8Array | null): Promise<Uint8Array>;
@@ -0,0 +1,99 @@
1
+ // articulation.ts — re-voice an answer in the asker's wording (Section 5).
2
+ //
3
+ // articulate — substitute answer forms with the asker's synonyms,
4
+ // using concept (halo) resonance to match the voices.
5
+ import { spliceAll } from "./types.js";
6
+ import { recognise } from "./recognition.js";
7
+ import { contains } from "./traverse.js";
8
+ import { bestHaloMate } from "./match.js";
9
+ import { coverSequence } from "../derive/src/index.js";
10
+ import { rItem, rNode, traceDerivation } from "./trace.js";
11
+ /** Re-voice an answer in the asker's own words. For each recognised form
12
+ * in the answer, find a concept-sibling in the query (by halo resonance)
13
+ * and substitute the asker's wording. The search's own cover mechanism
14
+ * splices the substitutes into the answer exactly where the forms sit. */
15
+ export async function articulate(ctx, answer, query) {
16
+ if (!query || query.length < 2 || answer.length < 2)
17
+ return answer;
18
+ const store = ctx.store;
19
+ const t = ctx.trace?.enter("articulate", [
20
+ rItem(answer, "answer"),
21
+ rItem(query, "query"),
22
+ ]);
23
+ const keep = (note) => {
24
+ t?.done([rItem(answer, "answer")], note);
25
+ return answer;
26
+ };
27
+ const qCandidates = [];
28
+ for (const s of recognise(ctx, query).sites) {
29
+ // payload < 0 is a SINGLE-BYTE leaf (byte leaves occupy the implicit
30
+ // negative id range −256…−1 — see store.ts). A one-byte form's halo is
31
+ // promiscuous (it keeps company with everything it ever neighboured), so
32
+ // admitting it as a voice or a revoicing target lets e.g. the digit "4"
33
+ // be "re-voiced" into an unrelated digit the query happens to contain.
34
+ // Articulation therefore operates on multi-byte forms only.
35
+ if (s.payload < 0)
36
+ continue;
37
+ const h = store.halo(s.payload);
38
+ if (!h)
39
+ continue;
40
+ qCandidates.push({
41
+ start: s.start,
42
+ end: s.end,
43
+ payload: {
44
+ halo: h,
45
+ bytes: query.slice(s.start, s.end),
46
+ node: s.payload,
47
+ },
48
+ });
49
+ }
50
+ if (qCandidates.length === 0) {
51
+ return keep("no asker concept to revoice — answer unchanged");
52
+ }
53
+ if (coverSequence(query.length, qCandidates).spans.length === 0) {
54
+ return keep("no asker concept to revoice — answer unchanged");
55
+ }
56
+ const voices = qCandidates.map((s) => s.payload);
57
+ const ans = recognise(ctx, answer);
58
+ const substitutions = new Map();
59
+ for (const s of ans.sites) {
60
+ // Same single-byte-leaf exclusion as the query loop above.
61
+ if (s.payload < 0)
62
+ continue;
63
+ const h = store.halo(s.payload);
64
+ if (!h)
65
+ continue;
66
+ const found = bestHaloMate(ctx, h, voices, (v) => v.halo);
67
+ if (!found)
68
+ continue;
69
+ const voice = found.item;
70
+ if (voice.node === s.payload || contains(ctx, voice.node, s.payload)) {
71
+ continue;
72
+ }
73
+ substitutions.set(s.payload, voice.bytes);
74
+ }
75
+ if (substitutions.size === 0) {
76
+ return keep("no answer form is a synonym of an asker concept");
77
+ }
78
+ ctx.trace?.step("substitute", [...substitutions.keys()].map((n) => rNode(ctx, n, "answer-form")), [...substitutions.values()].map((b) => rItem(b, "asker-voice")), `revoice ${substitutions.size} answer form(s) in the asker's own words (synonym splice)`);
79
+ const voicedSites = ans.sites.filter((s) => substitutions.has(s.payload));
80
+ const tArtCover = ctx.trace?.enter("cover", [
81
+ ...voicedSites.map((s) => rItem(answer.subarray(s.start, s.end), "form", s.payload, [
82
+ s.start,
83
+ s.end,
84
+ ])),
85
+ ]);
86
+ const solved = ctx.search.cover(answer.length, voicedSites, new Map(), ans.leaves, ans.splits, ans.starts, substitutions, undefined, undefined, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
87
+ const segs = solved && solved.segs;
88
+ tArtCover?.done(segs === null
89
+ ? []
90
+ : segs.map((s) => rItem(s.bytes, s.rec ? "voiced" : "kept", s.node, [s.i, s.j])), segs === null
91
+ ? "the revoiced cover did not compose"
92
+ : "lightest derivation of the revoiced answer");
93
+ const result = segs && spliceAll(segs);
94
+ if (result) {
95
+ t?.done([rItem(result, "voiced", ctx.store.findLeaf(result.subarray(0, 1)) ?? undefined)], "splice the asker's wording into the answer where the same concept appears");
96
+ return result;
97
+ }
98
+ return keep("the revoiced cover did not compose — answer unchanged");
99
+ }
@@ -0,0 +1,400 @@
1
+ import type { DerivationStep } from "./graph-search.js";
2
+ import type { AncestorReach, Attention, AttentionRead, DFMode, MindContext, Region, RegionVote, SaturationInfo, SaturationStop } from "./types.js";
3
+ import { type StructuralPart } from "../geometry.js";
4
+ import { type JunctionSynonymSides } from "./junction.js";
5
+ /** How the newly-added graded junction ladder (junction.ts / attention.ts's
6
+ * {@link CrossRegionTier}) is reported on a `junctionVotes` entry. The
7
+ * instrumentation spec this implements predates that ladder and only knew
8
+ * two tiers ("exact" | "synonym"); with the richer `CrossRegionTier` now
9
+ * the real shape of a junction vote's provenance, `junctionVotes[].tier`
10
+ * reports it DIRECTLY (the tier as-is: "exact" | "single-synonym" |
11
+ * "double-synonym" | "structural-resonance") rather than collapsing every
12
+ * non-exact tier into a lossy "synonym" bucket — the whole point of
13
+ * exposing `tier` here is to let a debugger tell a halo-sibling
14
+ * substitution apart from a structural-resonance ANN guess, which the
15
+ * spec's original two-value type cannot do. */
16
+ export type ClimbConsensusJunctionTier = CrossRegionTier;
17
+ export type RegionOutcome = "voted" | "no-ann-hit" | "no-structural-reach" | "saturated-abstention" | "nonpositive-df-weight" | "contrastive-margin-rejection";
18
+ /** The best DIFFERENT-conclusion rival the contrastive-margin gate found
19
+ * while scanning an ordinary (approximate) region's ANN hits — spec §1.
20
+ * Its roots/saturation/contextsReached are already available through
21
+ * `reaches` (serialiseReaches) for `node`; not duplicated here. */
22
+ export interface ConsensusContrastiveRivalTrace {
23
+ node: number;
24
+ rank: number;
25
+ score: number;
26
+ }
27
+ export interface ConsensusRegionTrace {
28
+ index: number;
29
+ source: "perceived" | "recognised";
30
+ span: [number, number];
31
+ chunk: boolean;
32
+ known: boolean;
33
+ canonicalId?: number;
34
+ canonicalUsable: boolean;
35
+ canonicalFailed: boolean;
36
+ annQueried: boolean;
37
+ annHitsReturned: number;
38
+ annHitsExamined: number;
39
+ selected?: {
40
+ source: "canonical" | "ann";
41
+ node: number;
42
+ rank?: number;
43
+ score: number;
44
+ fallback?: "orphan" | "saturated-tie";
45
+ };
46
+ reachNode?: number;
47
+ outcome: RegionOutcome;
48
+ idf?: number;
49
+ dfWeight?: number;
50
+ contrastiveMargin?: number;
51
+ contrastiveNoiseFloor?: number;
52
+ contrastiveRival?: ConsensusContrastiveRivalTrace;
53
+ mutualWeight?: number;
54
+ voteWeightPerRoot?: number;
55
+ focusWeightPerRoot?: number;
56
+ ordinaryVoteProduced: boolean;
57
+ superseded: boolean;
58
+ }
59
+ export interface ConsensusReachTrace {
60
+ node: number;
61
+ roots: number[];
62
+ contextsReached: number;
63
+ saturated: boolean;
64
+ saturation?: SaturationStop;
65
+ }
66
+ export type AnchorRejectionReason = "below-natural-break" | "below-consensus-floor" | "leading-saturation";
67
+ export interface ConsensusAnchorTrace {
68
+ anchor: number;
69
+ rank: number;
70
+ pooledVote: number;
71
+ idfVote: number;
72
+ candidateBreadth: number;
73
+ contributingVotes: number;
74
+ contributingEvidence: number;
75
+ breadth: number;
76
+ contributingSpans: Array<[number, number]>;
77
+ clusters: number;
78
+ commit: {
79
+ status: "root" | "overlap" | "rejected";
80
+ dominant: boolean;
81
+ passesNaturalBreak?: boolean;
82
+ passesConsensusFloor?: boolean;
83
+ pastLeadingSaturation?: boolean;
84
+ rejectionReasons: AnchorRejectionReason[];
85
+ };
86
+ }
87
+ export interface JunctionVoteTrace {
88
+ container: number;
89
+ span: [number, number];
90
+ roots: number[];
91
+ sourceRegionIndices: number[];
92
+ explainedAwayRegionIndices: number[];
93
+ absorbed: number;
94
+ tier?: ClimbConsensusJunctionTier;
95
+ /** Zero-based index into `crossRegion.probes` — the probe this vote was
96
+ * produced from (spec §8). */
97
+ probe: number;
98
+ confidence: number;
99
+ /** "Evidence bytes" — the container-coverage byte count (the existing
100
+ * `bestCov` variable at the push site). */
101
+ evidenceBytes: number;
102
+ mutualWeight: number;
103
+ voteWeightPerRoot: number;
104
+ }
105
+ /** Whether one DAG/synonym tier attempt was even made for a probe, and how
106
+ * many candidate containers it returned — spec §2/§3. */
107
+ export interface CrossRegionTierAttemptTrace {
108
+ attempted: boolean;
109
+ candidatesReturned: number;
110
+ }
111
+ /** Aggregate outcome of the container-selection loop for a DAG/synonym tier
112
+ * that returned at least one container — spec §4. Only aggregate counts
113
+ * and the final outcome are recorded, never every candidate. */
114
+ export interface CrossRegionStructuralTrace {
115
+ tier: "exact" | "single-synonym" | "double-synonym";
116
+ selfEvidenceRejected: number;
117
+ contradictionRejected: number;
118
+ passedGuards: number;
119
+ selectedNode?: number;
120
+ outcome: "all-rejected" | "saturated" | "no-roots" | "nonpositive-idf" | "accepted";
121
+ }
122
+ /** One retained structural-resonance variant that actually issued its own
123
+ * ANN query — spec §5. */
124
+ export interface StructuralResonanceVariantTrace {
125
+ kind: StructuralVariant["kind"];
126
+ semanticConfidence: number;
127
+ leftSiblingId?: number;
128
+ rightSiblingId?: number;
129
+ annHitsReturned: number;
130
+ }
131
+ /** One merged structural-resonance proposal actually examined via
132
+ * edgeAncestors — spec §5. Retains node/variant/scores, but NOT
133
+ * roots/saturation/contextsReached/idf (already in `reaches`). */
134
+ export interface StructuralResonanceCandidateTrace {
135
+ node: number;
136
+ variant: StructuralVariant["kind"];
137
+ leftSiblingId?: number;
138
+ rightSiblingId?: number;
139
+ annScore: number;
140
+ semanticConfidence: number;
141
+ effectiveScore: number;
142
+ outcome: "saturated" | "no-roots" | "nonpositive-idf" | "same-as-endpoint" | "same-as-selected" | "selected" | "contrastive-rival";
143
+ }
144
+ export interface StructuralResonanceTrace {
145
+ variantBudget: number;
146
+ variants: StructuralResonanceVariantTrace[];
147
+ mergedProposals: number;
148
+ examined: StructuralResonanceCandidateTrace[];
149
+ contrastiveMargin?: number;
150
+ noiseFloor: number;
151
+ outcome: "ineligible" | "empty" | "no-valid-proposal" | "margin-rejected" | "accepted";
152
+ ineligibleReasons?: Array<"between-region" | "not-both-strong" | "not-both-known" | "gap-too-large">;
153
+ }
154
+ /** One cross-region pair the ladder actually probed — spec §2. Exactly one
155
+ * of these is pushed per pair that incremented `probes`. */
156
+ export interface CrossRegionProbeTrace {
157
+ leftRegionIndex: number;
158
+ rightRegionIndex: number;
159
+ betweenRegionIndices: number[];
160
+ exact: CrossRegionTierAttemptTrace;
161
+ singleSynonym: CrossRegionTierAttemptTrace;
162
+ doubleSynonym: CrossRegionTierAttemptTrace;
163
+ structural?: CrossRegionStructuralTrace;
164
+ resonance?: StructuralResonanceTrace;
165
+ outcome: "accepted" | "structural-rejected" | "resonance-ineligible" | "resonance-rejected";
166
+ }
167
+ export interface ClimbConsensusData {
168
+ version: 1;
169
+ cache: {
170
+ hit: boolean;
171
+ detailAvailable: boolean;
172
+ };
173
+ config: {
174
+ annK: number;
175
+ crossRegionProbeLimit: number;
176
+ mode: DFMode;
177
+ corpusN?: number;
178
+ dimension?: number;
179
+ hubBound?: number;
180
+ estimatorNoise?: number;
181
+ naturalBreak?: number;
182
+ consensusFloor?: number;
183
+ };
184
+ candidates: {
185
+ perceived: number;
186
+ recognised: number;
187
+ total: number;
188
+ };
189
+ regions?: ConsensusRegionTrace[];
190
+ reaches?: ConsensusReachTrace[];
191
+ crossRegion?: {
192
+ eligibleRegions: number;
193
+ maximalRegions: number;
194
+ probeLimit: number;
195
+ probesAttempted: number;
196
+ junctionVotes: JunctionVoteTrace[];
197
+ supersededOrdinaryVotes: number;
198
+ probes: CrossRegionProbeTrace[];
199
+ stopReason: "insufficient-regions" | "probe-limit" | "pairs-exhausted";
200
+ };
201
+ saturation?: {
202
+ regionIntervals: Array<{
203
+ start: number;
204
+ end: number;
205
+ }>;
206
+ hasLeading: boolean;
207
+ leadingEnd: number;
208
+ };
209
+ pooling?: {
210
+ inputVotes: number;
211
+ eligibleVotes: number;
212
+ saturationMaskedVotes: number;
213
+ };
214
+ anchors?: ConsensusAnchorTrace[];
215
+ result: AttentionRead;
216
+ }
217
+ /** The mutable collection buffers threaded through one traced consensus
218
+ * climb — allocated exactly once, in {@link computeAttention}, only when
219
+ * `ctx.trace` is set. Every field mirrors a `ClimbConsensusData` array/map,
220
+ * built incrementally as the pipeline runs so commit-time decisions (in
221
+ * particular) are recorded LIVE, not reconstructed afterward. */
222
+ interface TraceDraft {
223
+ perceivedCount: number;
224
+ regions: ConsensusRegionTrace[];
225
+ crossRegionJunctionVotes: JunctionVoteTrace[];
226
+ crossRegionSummary?: {
227
+ eligibleRegions: number;
228
+ maximalRegions: number;
229
+ probeLimit: number;
230
+ probesAttempted: number;
231
+ stopReason?: "insufficient-regions" | "probe-limit" | "pairs-exhausted";
232
+ };
233
+ crossRegionProbes: CrossRegionProbeTrace[];
234
+ supersededOrdinaryVotes: number;
235
+ saturation?: {
236
+ regionIntervals: Array<{
237
+ start: number;
238
+ end: number;
239
+ }>;
240
+ hasLeading: boolean;
241
+ leadingEnd: number;
242
+ };
243
+ pooling?: {
244
+ inputVotes: number;
245
+ eligibleVotes: number;
246
+ saturationMaskedVotes: number;
247
+ };
248
+ anchors: ConsensusAnchorTrace[];
249
+ }
250
+ /** The config/corpus context {@link traceAttention} needs to fill in
251
+ * `ClimbConsensusData.config` and `.result` at whichever exit fires —
252
+ * threaded down from {@link computeAttention} rather than re-derived, so
253
+ * every emission point reports the SAME numbers the real climb used. */
254
+ interface ClimbConsensusCfg {
255
+ k: number;
256
+ mode: DFMode;
257
+ perceivedCount: number;
258
+ totalRegions: number;
259
+ N?: number;
260
+ reachMemo?: ReadonlyMap<number, AncestorReach>;
261
+ naturalBreak?: number;
262
+ consensusFloor?: number;
263
+ }
264
+ /** Climb the query's perceived byte regions up the structural DAG via
265
+ * resonance, pool the evidence, and return only the ROOT points of
266
+ * attention — those that cleared commitVotes' significance floor. */
267
+ export declare function climbAttention(ctx: MindContext, query: Uint8Array, k: number, mode?: DFMode): Promise<Attention[]>;
268
+ /** Full read-out of one consensus climb: both the roots (dominant points of
269
+ * attention) and the entire ranked list. Cached via ctx.climbMemo, ALWAYS —
270
+ * see {@link recognise} for why this memo (and recognise()'s own) must
271
+ * never be skipped while tracing: computeAttention's collectRegions walks
272
+ * the query's perceived tree via the same foldTree whose subtree-resolution
273
+ * fast path makes a second call on identical bytes non-idempotent once
274
+ * ctx._resolvedSubtrees is warm (which a multi-turn conversation's shared
275
+ * prefix subtrees guarantee by the second turn). A cache hit still emits
276
+ * a trace step — abbreviated, since the full per-sub-region voting detail
277
+ * {@link traceAttention} builds isn't preserved by the cached read-out —
278
+ * so a traced response is never silently blacked out for a repeated
279
+ * query. */
280
+ export declare function climbAttentionAll(ctx: MindContext, query: Uint8Array, k: number, mode?: DFMode): Promise<AttentionRead>;
281
+ export declare function computeAttention(ctx: MindContext, query: Uint8Array, k: number, mode: DFMode): Promise<AttentionRead>;
282
+ export declare function collectRegions(ctx: MindContext, query: Uint8Array): Region[];
283
+ export declare function voteRegions(ctx: MindContext, query: Uint8Array, regions: readonly Region[], k: number, mode: DFMode, N: number, reachMemo?: Map<number, AncestorReach>, td?: TraceDraft): Promise<{
284
+ votes: RegionVote[];
285
+ saturated: boolean[];
286
+ voters: Array<{
287
+ id: number;
288
+ score: number;
289
+ w: number;
290
+ } | null>;
291
+ }>;
292
+ /** The consensus vote as EVIDENCE POOLING, not shortest path: each surviving
293
+ * region is an axiom; it contributes to every root it climbed to (or, for a
294
+ * terminal answer node, to the contexts that lead to it) by a `combine:
295
+ * "sum"` rule, so independent regions corroborating the same anchor ADD
296
+ * rather than compete to be the cheapest route (see {@link Rule.combine} in
297
+ * derive/src/deduction.ts). Run through the very engine {@link
298
+ * GraphSearch} covers with — `lightestDerivation` — so a pooled-evidence
299
+ * decision is, like a followed edge or a spliced connector, one weighted
300
+ * rule of the SAME deduction system, not a separate hand-rolled tally that
301
+ * merely logs alongside it. `votesIdf`/`support` are the same two
302
+ * read-outs {@link commitVotes} always gated on; only how they accumulate
303
+ * changed. */
304
+ export declare function poolVotes(ctx: MindContext, regionVotes: readonly RegionVote[], sat: SaturationInfo, N: number, td?: TraceDraft): {
305
+ votes: Map<number, number>;
306
+ votesIdf: Map<number, number>;
307
+ support: Map<number, {
308
+ start: number;
309
+ end: number;
310
+ w: number;
311
+ }>;
312
+ /** Per-anchor SCALE-INVARIANT support: Σ RegionVote.absorbed over the
313
+ * distinct contributing regions — see Attention.breadth. */
314
+ regionSupport: Map<number, number>;
315
+ /** Per-anchor contributing region spans — see Attention.clusters. */
316
+ regionSpans: Map<number, Array<[number, number]>>;
317
+ steps: DerivationStep[];
318
+ };
319
+ export declare function commitVotes(ctx: MindContext, pooled: {
320
+ votes: Map<number, number>;
321
+ votesIdf: Map<number, number>;
322
+ support: Map<number, {
323
+ start: number;
324
+ end: number;
325
+ w: number;
326
+ }>;
327
+ regionSupport: Map<number, number>;
328
+ regionSpans: Map<number, Array<[number, number]>>;
329
+ steps: DerivationStep[];
330
+ }, sat: SaturationInfo, regions: readonly Region[], regionVoter: ReadonlyArray<{
331
+ id: number;
332
+ score: number;
333
+ w: number;
334
+ } | null>, N: number, td?: TraceDraft, cfg?: ClimbConsensusCfg): AttentionRead;
335
+ export declare function detectSaturated(ctx: MindContext, regions: ReadonlyArray<{
336
+ start: number;
337
+ end: number;
338
+ chunk?: boolean;
339
+ }>, saturated: ReadonlyArray<boolean>): SaturationInfo;
340
+ export declare function canonicalChunkId(ctx: MindContext, regionBytes: Uint8Array, N: number, reachMemo?: Map<number, AncestorReach>): number | null;
341
+ export declare function naturalBreak(votes: number[]): number;
342
+ export type CrossRegionTier = "exact" | "single-synonym" | "double-synonym" | "structural-resonance";
343
+ export interface StructuralVariant {
344
+ left: StructuralPart;
345
+ right: StructuralPart;
346
+ kind: "exact-exact" | "left-synonym" | "right-synonym" | "double-synonym";
347
+ semanticConfidence: number;
348
+ leftSiblingId?: number;
349
+ rightSiblingId?: number;
350
+ }
351
+ export interface StructuralResonanceProposal {
352
+ id: number;
353
+ annScore: number;
354
+ semanticConfidence: number;
355
+ effectiveScore: number;
356
+ variant: StructuralVariant["kind"];
357
+ leftSiblingId?: number;
358
+ rightSiblingId?: number;
359
+ }
360
+ /** Build, bound and order every mandatory structural variant (§7-8): the
361
+ * exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
362
+ * synonym variants (single- and double-synonym combined, one shared
363
+ * budget) are appended, ordered by confidence, then kind, then sibling id. */
364
+ export declare function buildStructuralVariants(ctx: MindContext, ra: Region, rb: Region, sides: JunctionSynonymSides): {
365
+ variants: StructuralVariant[];
366
+ exactLeft: StructuralPart;
367
+ exactRight: StructuralPart;
368
+ };
369
+ /** The final approximate tier: compose every retained structural variant,
370
+ * ANN-query each, merge proposals by candidate id, and validate the winner
371
+ * through the SAME structural gates every other tier answers to (saturation,
372
+ * roots, IDF, contrastive margin). Returns null when nothing survives. */
373
+ export declare function structuralResonance(ctx: MindContext, query: Uint8Array, ra: Region, rb: Region, sides: JunctionSynonymSides, k: number, N: number, reachMemo: Map<number, AncestorReach>,
374
+ /** Each side's OWN individual climb roots (from voteRegions), when it cast
375
+ * one — the self-evidence backstop structural-resonance needs and the
376
+ * exact tier gets for free from literal byte containment (§11's whole
377
+ * premise: recover a JOINT context neither side votes for alone). A
378
+ * candidate whose reach is exactly one side's own conclusion is not new
379
+ * evidence of a joint whole; it is that side's resonance rediscovering
380
+ * itself through a synthetic gist still dominated by its own direction. */
381
+ ownRootsA: readonly number[] | undefined, ownRootsB: readonly number[] | undefined, trace?: StructuralResonanceTrace): Promise<{
382
+ proposal: StructuralResonanceProposal;
383
+ reach: AncestorReach;
384
+ idf: number;
385
+ } | null>;
386
+ /** Emit the "climbConsensus" step — the human-readable note this always
387
+ * produced, now paired (when `ctx.trace` and `cfg` are both present) with
388
+ * the structured {@link ClimbConsensusData} payload on the SAME step's
389
+ * `data` field. Every exit of {@link computeAttention} funnels through
390
+ * here, so instrumentation and the existing rationale text can never drift
391
+ * apart — see the instrumentation spec's §9 "every exit path". */
392
+ export declare function traceAttention(ctx: MindContext, regions: ReadonlyArray<{
393
+ start: number;
394
+ end: number;
395
+ }>, regionVoter: ReadonlyArray<{
396
+ id: number;
397
+ score: number;
398
+ w: number;
399
+ } | null>, roots: ReadonlyArray<Attention>, steps?: ReadonlyArray<DerivationStep>, td?: TraceDraft, cfg?: ClimbConsensusCfg, ranked?: ReadonlyArray<Attention>): void;
400
+ export {};