@hviana/sema 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/geometry.d.ts +26 -0
- package/dist/src/geometry.js +32 -3
- package/dist/src/mind/attention.d.ts +246 -7
- package/dist/src/mind/attention.js +771 -173
- package/dist/src/mind/bridge.d.ts +30 -0
- package/dist/src/mind/bridge.js +569 -0
- package/dist/src/mind/index.d.ts +2 -0
- package/dist/src/mind/junction.d.ts +30 -1
- package/dist/src/mind/junction.js +73 -18
- package/dist/src/mind/match.d.ts +15 -2
- package/dist/src/mind/match.js +3 -8
- package/dist/src/mind/mechanisms/cast.d.ts +54 -0
- package/dist/src/mind/mechanisms/cast.js +268 -37
- package/dist/src/mind/mechanisms/cover.js +16 -31
- package/dist/src/mind/mechanisms/recall.js +66 -0
- package/dist/src/mind/mind.d.ts +2 -0
- package/dist/src/mind/rationale.d.ts +7 -2
- package/dist/src/mind/rationale.js +6 -5
- package/dist/src/mind/reasoning.d.ts +11 -0
- package/dist/src/mind/reasoning.js +58 -2
- package/dist/src/mind/recognition.js +127 -7
- package/dist/src/mind/traverse.js +90 -25
- package/dist/src/mind/types.d.ts +57 -2
- package/dist/src/mind/types.js +38 -7
- package/dist/src/store.d.ts +12 -3
- package/dist/src/store.js +9 -3
- package/package.json +1 -1
- package/src/geometry.ts +52 -3
- package/src/mind/attention.ts +1199 -128
- package/src/mind/bridge.ts +596 -0
- package/src/mind/index.ts +16 -0
- package/src/mind/junction.ts +125 -16
- package/src/mind/match.ts +19 -5
- package/src/mind/mechanisms/cast.ts +290 -38
- package/src/mind/mechanisms/cover.ts +23 -36
- package/src/mind/mechanisms/recall.ts +79 -0
- package/src/mind/mind.ts +15 -0
- package/src/mind/rationale.ts +12 -4
- package/src/mind/reasoning.ts +71 -2
- package/src/mind/recognition.ts +132 -7
- package/src/mind/traverse.ts +91 -24
- package/src/mind/types.ts +95 -6
- package/src/store.ts +19 -5
- package/test/36-already-answered-fusion.test.mjs +128 -0
- package/test/37-cluster-dispersion-fusion.test.mjs +190 -0
- package/test/38-reason-restate-guard.test.mjs +94 -0
- package/test/39-cast-restate-guard.test.mjs +102 -0
- package/test/40-choosenext-scale-guard.test.mjs +75 -0
- package/test/41-seatofnode-direction.test.mjs +85 -0
- package/test/42-recognise-trace-idempotence.test.mjs +106 -0
- package/test/43-cast-analog-seat.test.mjs +244 -0
- package/test/44-recognise-edge-whitespace.test.mjs +63 -0
- package/test/45-liftanswer-restated-trim.test.mjs +60 -0
- package/test/46-recognise-multibyte-edge.test.mjs +85 -0
- package/test/47-cast-comparison-coverage.test.mjs +134 -0
- package/test/48-recognise-turn-connective.test.mjs +125 -0
- package/test/49-natural-units-synonym-bridge.test.mjs +175 -0
- package/test/50-cast-analog-consensus-floor.test.mjs +179 -0
- package/test/51-structural-resonance-ladder.test.mjs +552 -0
- package/test/52-climb-consensus-instrumentation.test.mjs +324 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MindContext } from "./types.js";
|
|
2
|
+
/** One accepted substitution: query span [qs,qe) stands in for the
|
|
3
|
+
* candidate context's span — recorded for the rationale trace. */
|
|
4
|
+
interface Substitution {
|
|
5
|
+
qs: number;
|
|
6
|
+
qe: number;
|
|
7
|
+
cs: number;
|
|
8
|
+
ce: number;
|
|
9
|
+
}
|
|
10
|
+
/** A bridged grounding proposal: the trained context to ground, the query
|
|
11
|
+
* spans its alignment accounts for, and the substitutions that closed it. */
|
|
12
|
+
export interface BridgeHit {
|
|
13
|
+
id: number;
|
|
14
|
+
accounted: Array<[number, number]>;
|
|
15
|
+
subs: Substitution[];
|
|
16
|
+
}
|
|
17
|
+
/** True when some query byte-range left UNACCOUNTED by `spans` contains a
|
|
18
|
+
* STORED window — content the store has seen that the proposed reading
|
|
19
|
+
* simply ignores. The IGNORED-KNOWN principle: a span may be dismissed
|
|
20
|
+
* only when the store itself has never seen it; known content the
|
|
21
|
+
* alignment failed to account for is grounds for refusal, while genuinely
|
|
22
|
+
* novel spans (an untrained word, stray punctuation) remain tolerable.
|
|
23
|
+
* Shared by the substitution bridge's own acceptance and CAST's
|
|
24
|
+
* frame-tier comparison gate (cast.ts). Pure attestation — no
|
|
25
|
+
* similarity, no constants. */
|
|
26
|
+
export declare function dismissedKnownContent(ctx: MindContext, query: Uint8Array, spans: ReadonlyArray<readonly [number, number]>): boolean;
|
|
27
|
+
/** Recall's corroborated-substitution bridge — see the module comment.
|
|
28
|
+
* Returns the best bridged grounding proposal, or null. */
|
|
29
|
+
export declare function substitutionBridge(ctx: MindContext, query: Uint8Array, proposed?: ReadonlyArray<number>): Promise<BridgeHit | null>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
// bridge.ts — corroborated-substitution grounding (recall's last tier before
|
|
2
|
+
// refusal).
|
|
3
|
+
//
|
|
4
|
+
// THE GAP (test/49): a query phrased through a near-synonym of a trained
|
|
5
|
+
// word ("Name the biggest planet." against a corpus that only ever says
|
|
6
|
+
// "largest planet") reaches nothing — recognition finds no form, whole-query
|
|
7
|
+
// resonance stays below the reach bar — even though the fact is trained and
|
|
8
|
+
// the synonym pairing is corroborated across the corpus. Words are never
|
|
9
|
+
// independently addressable nodes (deposit interns whole streams plus W-1/W
|
|
10
|
+
// leaf windows; a word mid-sentence falls between those scales), so no halo
|
|
11
|
+
// ever links "biggest" to "largest" — and the write side cannot cheaply
|
|
12
|
+
// mint such nodes without polluting the shared indexes (measured: an
|
|
13
|
+
// earlier write-side attempt regressed 24 unrelated tests).
|
|
14
|
+
//
|
|
15
|
+
// THE MECHANISM — read-only, refusal-path-only. When recall is about to
|
|
16
|
+
// refuse, the query's own content-addressed windows (the W-byte leaf-id
|
|
17
|
+
// flat branches indexSubSpans already interns at every byte offset) are
|
|
18
|
+
// probed against the store; the rarest ones anchor a climb (edgeAncestors —
|
|
19
|
+
// the same climb consensus voting uses) to the trained contexts that
|
|
20
|
+
// contain them. Each candidate context is ALIGNED to the query byte-for-
|
|
21
|
+
// byte around the anchor, leaving mismatched spans; a mismatch grounds as a
|
|
22
|
+
// SUBSTITUTION only under two derived gates:
|
|
23
|
+
//
|
|
24
|
+
// • CORROBORATION — the query-side span is itself corpus-attested: every
|
|
25
|
+
// W-window inside it resolves as a stored flat form, at least one of
|
|
26
|
+
// them reused across ≥ 2 containers (the same "≥ 2 structural parents"
|
|
27
|
+
// bar propagateSuffixes gates suffix inheritance with). An untrained
|
|
28
|
+
// word ("deadliest") has no stored windows and can never substitute.
|
|
29
|
+
// • GEOMETRIC IDENTITY — the two spans' own perceived gists must clear
|
|
30
|
+
// conceptThreshold(D), the same "same concept" bar haloSiblings and
|
|
31
|
+
// articulation already gate on. This is what separates a synonym pair
|
|
32
|
+
// the fold geometry genuinely identifies ("biggest"~"largest", sharing
|
|
33
|
+
// most of their bytes and their role) from an arbitrary co-frame word.
|
|
34
|
+
//
|
|
35
|
+
// A candidate context is accepted when its aligned-plus-substituted spans
|
|
36
|
+
// DOMINATE the query (the same half-dominance predicate used throughout)
|
|
37
|
+
// and every unexplained gap stays within one perception window W (the same
|
|
38
|
+
// single-window tolerance identityBar prices). The accepted context is
|
|
39
|
+
// then grounded exactly like any recall hit — project() through its learnt
|
|
40
|
+
// edges — so the answer is a trained continuation, never synthesized bytes.
|
|
41
|
+
//
|
|
42
|
+
// COST: nothing on any answering path — the bridge runs only where the
|
|
43
|
+
// alternative was silence. There it pays O(|query|) content-hash probes
|
|
44
|
+
// (the propagateSuffixes trick), at most W anchor climbs and hubBound
|
|
45
|
+
// candidate reads, and one O(|query|·|candidate|)-bounded alignment each —
|
|
46
|
+
// all capped by existing derived bounds (W, chainReach, hubBound).
|
|
47
|
+
//
|
|
48
|
+
// FIXED WRONG-ANSWER GAP (found and closed 2026-07-20): a proper-noun swap
|
|
49
|
+
// could pass both derived gates above and voice a WRONG fact. Live case:
|
|
50
|
+
// "The capital of France is" (a prefix-completion probe) bridged through a
|
|
51
|
+
// substitution reading "of Fra[nce]" as "of Spain si[nce]" into "Madrid has
|
|
52
|
+
// been the capital of Spain since 1561...", because the TRUE France fact
|
|
53
|
+
// ("The capital of France is Paris.") is a terminal statement with no
|
|
54
|
+
// outgoing edge and is therefore never admitted as a bridge candidate (§
|
|
55
|
+
// candidate admission above) — so no competing evidence for "France" was
|
|
56
|
+
// ever collected, and the Spain candidate's own text satisfied frame
|
|
57
|
+
// unanimity vacuously (nothing to disagree with).
|
|
58
|
+
// REFUTED FIX 1 — require unanimous()'s frame-consensus scan to find a
|
|
59
|
+
// genuine corroborating occurrence (not vacuous-true on zero evidence):
|
|
60
|
+
// breaks test/49 — "biggest"~"largest" is corroborated ONLY by the very
|
|
61
|
+
// candidate proposing the substitution in that miniature corpus (no
|
|
62
|
+
// OTHER trained pair pairs either word with "planet"); requiring
|
|
63
|
+
// external evidence makes that legitimate case fail too.
|
|
64
|
+
// REFUTED FIX 2 — exclude the candidate's own bytes from being its own
|
|
65
|
+
// corroborating witness (same idea, scoped to self-reference): same
|
|
66
|
+
// failure, same reason — self-witness is ALL the evidence test/49 has.
|
|
67
|
+
// REFUTED FIX 3 — require the CANDIDATE-side substituted span to also
|
|
68
|
+
// clear the ≥2-container reuse bar (attestedSpan, symmetric with the
|
|
69
|
+
// query-side attestedQ): does not discriminate — "Spain" is reused
|
|
70
|
+
// across at least as many trained contexts as "France" is, so it
|
|
71
|
+
// passes trivially.
|
|
72
|
+
// THE ACTUAL FIX — RAW BALANCE (see the substitution loop below): the raw
|
|
73
|
+
// mismatch (BEFORE expansion absorbs any matched flanking bytes) must
|
|
74
|
+
// be roughly length-balanced on both sides — dominates(min(uLen,cLen),
|
|
75
|
+
// max(uLen,cLen)), the SAME "part*2 > whole" bar used throughout the
|
|
76
|
+
// codebase, no new constant. Measured on both cases: the legitimate
|
|
77
|
+
// "biggest"~"largest" substitution's raw diff is "big"/"lar" (3/3
|
|
78
|
+
// bytes, perfectly balanced — expansion then absorbs the shared "gest"
|
|
79
|
+
// suffix, identical on both sides, to reach an attestable span). The
|
|
80
|
+
// wrong "France"~"Spain" substitution's raw diff was "Fra"/"Spai" (3/8
|
|
81
|
+
// bytes) — the align sweep's greedy search had found a coincidental
|
|
82
|
+
// "nce " match years later inside "since", so 3 bytes of query content
|
|
83
|
+
// were standing in for 8 bytes of candidate content. That asymmetry is
|
|
84
|
+
// exactly what a real lexical/morphological synonym never has and an
|
|
85
|
+
// arbitrary sentence divergence always does; expansion (which only
|
|
86
|
+
// grows both sides by IDENTICAL absorbed bytes) can never repair a raw
|
|
87
|
+
// imbalance, so gating on the RAW gap is the correct point of attack.
|
|
88
|
+
// Verified: real-store repro now falls through to an honest echo of
|
|
89
|
+
// the true trained fact instead of the wrong Spain continuation; the
|
|
90
|
+
// boiling-point and lowercase-France bridge wins are unaffected; full
|
|
91
|
+
// suite green (358/358).
|
|
92
|
+
import { cosine } from "../vec.js";
|
|
93
|
+
import { conceptThreshold, dominates } from "../geometry.js";
|
|
94
|
+
import { bytesEqual, indexOf } from "../bytes.js";
|
|
95
|
+
import { foldTree, perceive } from "./primitives.js";
|
|
96
|
+
import { chainReach, leafIdRun } from "./canonical.js";
|
|
97
|
+
import { corpusN, edgeAncestors, hubBound } from "./traverse.js";
|
|
98
|
+
import { rItem, rNode } from "./trace.js";
|
|
99
|
+
/** True when some query byte-range left UNACCOUNTED by `spans` contains a
|
|
100
|
+
* STORED window — content the store has seen that the proposed reading
|
|
101
|
+
* simply ignores. The IGNORED-KNOWN principle: a span may be dismissed
|
|
102
|
+
* only when the store itself has never seen it; known content the
|
|
103
|
+
* alignment failed to account for is grounds for refusal, while genuinely
|
|
104
|
+
* novel spans (an untrained word, stray punctuation) remain tolerable.
|
|
105
|
+
* Shared by the substitution bridge's own acceptance and CAST's
|
|
106
|
+
* frame-tier comparison gate (cast.ts). Pure attestation — no
|
|
107
|
+
* similarity, no constants. */
|
|
108
|
+
export function dismissedKnownContent(ctx, query, spans) {
|
|
109
|
+
const W = ctx.space.maxGroup;
|
|
110
|
+
const sorted = [...spans].sort((a, b) => a[0] - b[0]);
|
|
111
|
+
let cursor = 0;
|
|
112
|
+
for (const [s, e] of [...sorted, [query.length, query.length]]) {
|
|
113
|
+
for (let o = cursor; o + W <= s; o++) {
|
|
114
|
+
const ids = leafIdRun(ctx, query, o, o + W);
|
|
115
|
+
if (ids !== null && ctx.store.findBranch(ids) !== null)
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
cursor = Math.max(cursor, e);
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
/** Extend a seed match (query offset qo ↔ candidate offset co) to its
|
|
123
|
+
* maximal common run, then walk outward in both directions collecting
|
|
124
|
+
* further common runs of at least W bytes across bounded mismatch gaps
|
|
125
|
+
* (each side ≤ chainReach). Returns the matched query spans and the
|
|
126
|
+
* mismatch pairs between consecutive runs. */
|
|
127
|
+
function align(ctx, q, c, qo, co) {
|
|
128
|
+
const W = ctx.space.maxGroup;
|
|
129
|
+
const reachCap = chainReach(W);
|
|
130
|
+
// Maximal run around the seed.
|
|
131
|
+
let qs = qo, ss = co;
|
|
132
|
+
while (qs > 0 && ss > 0 && q[qs - 1] === c[ss - 1]) {
|
|
133
|
+
qs--;
|
|
134
|
+
ss--;
|
|
135
|
+
}
|
|
136
|
+
let qe = qo, se = co;
|
|
137
|
+
while (qe < q.length && se < c.length && q[qe] === c[se]) {
|
|
138
|
+
qe++;
|
|
139
|
+
se++;
|
|
140
|
+
}
|
|
141
|
+
const matched = [[qs, qe]];
|
|
142
|
+
const gaps = [];
|
|
143
|
+
// The next common run of ≥ W bytes past (qi, si), with each side's gap
|
|
144
|
+
// bounded by chainReach; smallest total gap wins (nearest continuation).
|
|
145
|
+
const runLenAt = (qi, si) => {
|
|
146
|
+
let n = 0;
|
|
147
|
+
while (qi + n < q.length && si + n < c.length && q[qi + n] === c[si + n]) {
|
|
148
|
+
n++;
|
|
149
|
+
}
|
|
150
|
+
return n;
|
|
151
|
+
};
|
|
152
|
+
// RIGHT sweep.
|
|
153
|
+
let qi = qe, si = se;
|
|
154
|
+
for (;;) {
|
|
155
|
+
let found = false;
|
|
156
|
+
for (let total = 1; total <= 2 * reachCap && !found; total++) {
|
|
157
|
+
for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
|
|
158
|
+
const gs = total - gq;
|
|
159
|
+
if (gs > reachCap)
|
|
160
|
+
continue;
|
|
161
|
+
if (qi + gq >= q.length || si + gs >= c.length)
|
|
162
|
+
continue;
|
|
163
|
+
const n = runLenAt(qi + gq, si + gs);
|
|
164
|
+
if (n >= W || qi + gq + n === q.length) {
|
|
165
|
+
if (n === 0)
|
|
166
|
+
continue;
|
|
167
|
+
if (gq > 0 || gs > 0) {
|
|
168
|
+
gaps.push({ qs: qi, qe: qi + gq, cs: si, ce: si + gs });
|
|
169
|
+
}
|
|
170
|
+
matched.push([qi + gq, qi + gq + n]);
|
|
171
|
+
qi = qi + gq + n;
|
|
172
|
+
si = si + gs + n;
|
|
173
|
+
found = true;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!found)
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
// LEFT sweep (mirror).
|
|
182
|
+
qi = qs;
|
|
183
|
+
si = ss;
|
|
184
|
+
for (;;) {
|
|
185
|
+
let found = false;
|
|
186
|
+
for (let total = 1; total <= 2 * reachCap && !found; total++) {
|
|
187
|
+
for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
|
|
188
|
+
const gs = total - gq;
|
|
189
|
+
if (gs > reachCap)
|
|
190
|
+
continue;
|
|
191
|
+
if (qi - gq <= 0 || si - gs <= 0)
|
|
192
|
+
continue;
|
|
193
|
+
// Run ENDING at (qi - gq, si - gs).
|
|
194
|
+
let n = 0;
|
|
195
|
+
while (n < qi - gq && n < si - gs &&
|
|
196
|
+
q[qi - gq - 1 - n] === c[si - gs - 1 - n]) {
|
|
197
|
+
n++;
|
|
198
|
+
}
|
|
199
|
+
if (n >= W || n === qi - gq) {
|
|
200
|
+
if (n === 0)
|
|
201
|
+
continue;
|
|
202
|
+
if (gq > 0 || gs > 0) {
|
|
203
|
+
gaps.push({ qs: qi - gq, qe: qi, cs: si - gs, ce: si });
|
|
204
|
+
}
|
|
205
|
+
matched.push([qi - gq - n, qi - gq]);
|
|
206
|
+
qi = qi - gq - n;
|
|
207
|
+
si = si - gs - n;
|
|
208
|
+
found = true;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!found)
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
return { matched, gaps };
|
|
217
|
+
}
|
|
218
|
+
/** Recall's corroborated-substitution bridge — see the module comment.
|
|
219
|
+
* Returns the best bridged grounding proposal, or null. */
|
|
220
|
+
export async function substitutionBridge(ctx, query, proposed = []) {
|
|
221
|
+
const W = ctx.space.maxGroup;
|
|
222
|
+
if (query.length < 2 * W)
|
|
223
|
+
return null;
|
|
224
|
+
const bound = hubBound(ctx);
|
|
225
|
+
const N = corpusN(ctx);
|
|
226
|
+
const bar = conceptThreshold(ctx.store.D);
|
|
227
|
+
const reachCap = chainReach(W);
|
|
228
|
+
// 1. The query's stored windows, rarest first (fewest containers — the
|
|
229
|
+
// most discriminative anchors; hub-clamped like every fan-out read).
|
|
230
|
+
// The scan doubles as the ONE store probe of every query window: the
|
|
231
|
+
// per-offset stored/reused facts it establishes serve every later
|
|
232
|
+
// attestation and ignored-known check as plain array reads (the same
|
|
233
|
+
// probes repeated per candidate dominated the refusal-path cost).
|
|
234
|
+
const nWin = Math.max(0, query.length - W + 1);
|
|
235
|
+
const winStored = new Uint8Array(nWin);
|
|
236
|
+
const winReused = new Uint8Array(nWin);
|
|
237
|
+
const anchors = [];
|
|
238
|
+
for (let o = 0; o + W <= query.length; o++) {
|
|
239
|
+
const ids = leafIdRun(ctx, query, o, o + W);
|
|
240
|
+
if (ids === null)
|
|
241
|
+
continue;
|
|
242
|
+
const id = ctx.store.findBranch(ids);
|
|
243
|
+
if (id === null)
|
|
244
|
+
continue;
|
|
245
|
+
winStored[o] = 1;
|
|
246
|
+
const rarity = ctx.store.containersSlice(id, 0, bound + 1).length;
|
|
247
|
+
if (rarity >= 2)
|
|
248
|
+
winReused[o] = 1;
|
|
249
|
+
if (rarity === 0)
|
|
250
|
+
continue;
|
|
251
|
+
anchors.push({ off: o, id, rarity });
|
|
252
|
+
}
|
|
253
|
+
if (anchors.length === 0)
|
|
254
|
+
return null;
|
|
255
|
+
// CORROBORATION (see the module-level doc) over the precomputed window
|
|
256
|
+
// facts: the query span [qs,qe) attests when every full W-window inside
|
|
257
|
+
// it is a stored flat form and at least one is reused across ≥ 2
|
|
258
|
+
// containers. Spans shorter than W carry no window of their own and can
|
|
259
|
+
// never substitute.
|
|
260
|
+
const attestedQ = (qs, qe) => {
|
|
261
|
+
if (qe - qs < W)
|
|
262
|
+
return false;
|
|
263
|
+
let reused = false;
|
|
264
|
+
for (let o = qs; o + W <= qe; o++) {
|
|
265
|
+
if (!winStored[o])
|
|
266
|
+
return false;
|
|
267
|
+
if (winReused[o])
|
|
268
|
+
reused = true;
|
|
269
|
+
}
|
|
270
|
+
return reused;
|
|
271
|
+
};
|
|
272
|
+
// dismissedKnownContent (see above) over the same precomputed facts.
|
|
273
|
+
const dismissedKnownQ = (spans) => {
|
|
274
|
+
const sorted = [...spans].sort((a, b) => a[0] - b[0]);
|
|
275
|
+
let cursor = 0;
|
|
276
|
+
for (const [s, e] of [...sorted, [query.length, query.length]]) {
|
|
277
|
+
for (let o = cursor; o + W <= s; o++)
|
|
278
|
+
if (winStored[o])
|
|
279
|
+
return true;
|
|
280
|
+
cursor = Math.max(cursor, e);
|
|
281
|
+
}
|
|
282
|
+
return false;
|
|
283
|
+
};
|
|
284
|
+
anchors.sort((a, b) => a.rarity - b.rarity);
|
|
285
|
+
// Up to W anchors, at least one window apart — the quantum's own count.
|
|
286
|
+
const picked = [];
|
|
287
|
+
for (const a of anchors) {
|
|
288
|
+
if (picked.length >= W)
|
|
289
|
+
break;
|
|
290
|
+
if (picked.some((p) => Math.abs(p.off - a.off) < W))
|
|
291
|
+
continue;
|
|
292
|
+
picked.push(a);
|
|
293
|
+
}
|
|
294
|
+
// 2. Candidate trained contexts. Two proposal channels, one verifier:
|
|
295
|
+
// (a) the caller's PROPOSED hits — recall's whole-query resonance
|
|
296
|
+
// ranking, the retrieval structure built to surface near-paraphrase
|
|
297
|
+
// forms the window climb cannot single out at corpus scale; (b) each
|
|
298
|
+
// picked anchor climbed to its edge-bearing ancestors (the same climb
|
|
299
|
+
// consensus voting uses). Both are only ever PROPOSALS — every
|
|
300
|
+
// candidate passes the same byte-exact alignment and gates below.
|
|
301
|
+
const seen = new Set();
|
|
302
|
+
const candidates = [];
|
|
303
|
+
// Proposal channel — carries its caller's own bound (recall's resonance
|
|
304
|
+
// k), so it neither consumes the climb's hub budget (on a small corpus
|
|
305
|
+
// √N is a handful and the proposals would crowd the climb out entirely)
|
|
306
|
+
// nor lets per-candidate byte work grow past that bound. A proposal may
|
|
307
|
+
// be a FLAT content twin whose continuation edge lives on the
|
|
308
|
+
// fold-shaped deposit node with the same bytes — the same twin split
|
|
309
|
+
// canonResolve bridges by re-folding (primitives.ts) — but the re-fold
|
|
310
|
+
// (a full perceive of the candidate's bytes) is paid only for proposals
|
|
311
|
+
// that could align at all: alignment can only seed at a picked anchor
|
|
312
|
+
// window occurring literally in the candidate (measured: unconditional
|
|
313
|
+
// re-folds multiplied the refusal-path latency several-fold).
|
|
314
|
+
for (const sid of proposed) {
|
|
315
|
+
if (seen.has(sid))
|
|
316
|
+
continue;
|
|
317
|
+
seen.add(sid);
|
|
318
|
+
const tb = ctx.store.bytes(sid);
|
|
319
|
+
if (tb.length === 0)
|
|
320
|
+
continue;
|
|
321
|
+
if (!picked.some((a) => indexOf(tb, query.subarray(a.off, a.off + W), 0) >= 0))
|
|
322
|
+
continue;
|
|
323
|
+
let use = sid;
|
|
324
|
+
if (!ctx.store.hasNext(use)) {
|
|
325
|
+
const folded = foldTree(ctx, perceive(ctx, tb), 0).node;
|
|
326
|
+
if (folded === null || folded === sid || !ctx.store.hasNext(folded)) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
use = folded;
|
|
330
|
+
if (seen.has(use))
|
|
331
|
+
continue;
|
|
332
|
+
seen.add(use);
|
|
333
|
+
}
|
|
334
|
+
candidates.push(use);
|
|
335
|
+
}
|
|
336
|
+
// Climb channel — edge-bearing ancestors only, decided by the indexed
|
|
337
|
+
// O(1) hasNext; no byte is read here (the climb visits hundreds of
|
|
338
|
+
// roots, and reading each was measured to dominate the refusal path).
|
|
339
|
+
const proposedCount = candidates.length;
|
|
340
|
+
for (const a of picked) {
|
|
341
|
+
const reach = edgeAncestors(ctx, a.id, N);
|
|
342
|
+
for (const sid of reach.roots) {
|
|
343
|
+
if (candidates.length - proposedCount >= bound)
|
|
344
|
+
break;
|
|
345
|
+
if (seen.has(sid))
|
|
346
|
+
continue;
|
|
347
|
+
seen.add(sid);
|
|
348
|
+
if (!ctx.store.hasNext(sid))
|
|
349
|
+
continue;
|
|
350
|
+
candidates.push(sid);
|
|
351
|
+
}
|
|
352
|
+
if (candidates.length - proposedCount >= bound)
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
// 3. Align each candidate; gate its mismatches; keep the best.
|
|
356
|
+
const allBytes = new Map();
|
|
357
|
+
for (const sid of candidates)
|
|
358
|
+
allBytes.set(sid, ctx.store.bytes(sid));
|
|
359
|
+
// FRAME UNANIMITY: a substitution U → C inside the frame (Lf, Rf) is
|
|
360
|
+
// groundable only when the collected candidates — the store's own sample
|
|
361
|
+
// of contexts sharing the query's content — are unanimous about the
|
|
362
|
+
// filler: every occurrence of Lf…Rf across them holds either U (the
|
|
363
|
+
// query's own word, corroboration) or C. A THIRD distinct filler means
|
|
364
|
+
// the frame is a VALUE SLOT ("was born in _" held Germany, Poland,
|
|
365
|
+
// England, Serbia — observed live), and picking one value would assert
|
|
366
|
+
// knowledge the store does not have. Consensus of the store's own
|
|
367
|
+
// instances, no similarity judgement, no tuned constant.
|
|
368
|
+
// Requires a genuine CORROBORATING sighting, not merely the absence of a
|
|
369
|
+
// conflicting one: scanning only the handful of resonance/climb-proposed
|
|
370
|
+
// candidates means the frame can easily occur NOWHERE else among them
|
|
371
|
+
// (observed live: "of Fra[nce]" -> "of Spain si[nce]" passed vacuously —
|
|
372
|
+
// the frame "tal …nce " never recurred among the collected candidates at
|
|
373
|
+
// all, so there was no consensus, only an absence of disagreement, yet
|
|
374
|
+
// the substitution was accepted). "Unanimous" must mean the store's own
|
|
375
|
+
// instances agree, which requires at least one instance to consult.
|
|
376
|
+
const unanimous = (u, c, lf, rf) => {
|
|
377
|
+
for (const bytes of allBytes.values()) {
|
|
378
|
+
let from = 0;
|
|
379
|
+
for (;;) {
|
|
380
|
+
const i = indexOf(bytes, lf, from);
|
|
381
|
+
if (i < 0)
|
|
382
|
+
break;
|
|
383
|
+
from = i + 1;
|
|
384
|
+
const start = i + lf.length;
|
|
385
|
+
const j = indexOf(bytes.subarray(start, start + reachCap + rf.length), rf, 0);
|
|
386
|
+
if (j < 0)
|
|
387
|
+
continue;
|
|
388
|
+
const filler = bytes.subarray(start, start + j);
|
|
389
|
+
if (filler.length === 0)
|
|
390
|
+
continue;
|
|
391
|
+
if (!bytesEqual(filler, u) && !bytesEqual(filler, c))
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return true;
|
|
396
|
+
};
|
|
397
|
+
// (A candidate need NOT contain the query's rarest window literally: the
|
|
398
|
+
// rarest window may sit INSIDE the very word being substituted (observed
|
|
399
|
+
// live: "chemical symbol for water" whose rarest window "l sy" spans
|
|
400
|
+
// "symbol" — the trained formula-question can never contain it). A
|
|
401
|
+
// candidate that instead dodges the query's known content by writing it
|
|
402
|
+
// off as gaps is refused by dismissedKnownContent below, which subsumes
|
|
403
|
+
// the old rarest-window containment gate: rare windows inside an accepted
|
|
404
|
+
// substitution are accounted for; rare windows outside the accepted spans
|
|
405
|
+
// force refusal (the Matrix-synopsis junk stays dead by exactly that
|
|
406
|
+
// check — verified live).
|
|
407
|
+
let best = null;
|
|
408
|
+
let bestAccounted = 0;
|
|
409
|
+
for (const sid of candidates) {
|
|
410
|
+
const cBytes = allBytes.get(sid);
|
|
411
|
+
if (cBytes.length === 0)
|
|
412
|
+
continue;
|
|
413
|
+
// Seed at the rarest picked anchor that literally occurs in this
|
|
414
|
+
// candidate.
|
|
415
|
+
let seed = null;
|
|
416
|
+
for (const a of picked) {
|
|
417
|
+
const co = indexOf(cBytes, query.subarray(a.off, a.off + W), 0);
|
|
418
|
+
if (co >= 0) {
|
|
419
|
+
seed = { qo: a.off, co };
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (seed === null)
|
|
424
|
+
continue;
|
|
425
|
+
const { matched, gaps } = align(ctx, query, cBytes, seed.qo, seed.co);
|
|
426
|
+
// Gate each mismatch: a corroborated, geometrically-identified
|
|
427
|
+
// substitution counts as accounted; anything else stays a gap.
|
|
428
|
+
//
|
|
429
|
+
// A raw mismatch is the MINIMAL byte diff ("big" ↔ "lar" inside
|
|
430
|
+
// biggest/largest), usually below the scale at which either side is a
|
|
431
|
+
// corpus unit. The true unit is found by EXPANSION: absorb flanking
|
|
432
|
+
// bytes from the adjacent matched runs (equal on both sides by
|
|
433
|
+
// construction, so both spans grow identically) until the query side
|
|
434
|
+
// attests and the pair clears the concept bar — smallest expansion
|
|
435
|
+
// first, capped at chainReach like the mismatch itself. Absorbed
|
|
436
|
+
// bytes were already matched, so coverage is unchanged.
|
|
437
|
+
const subs = [];
|
|
438
|
+
let ok = true;
|
|
439
|
+
for (const g of gaps) {
|
|
440
|
+
const uLen = g.qe - g.qs, cLen = g.ce - g.cs;
|
|
441
|
+
if (uLen === 0 || cLen === 0 || uLen > reachCap || cLen > reachCap) {
|
|
442
|
+
// Pure insertion/deletion or over-long mismatch. Query-side: one
|
|
443
|
+
// perception window (the identityBar tolerance). Candidate-side:
|
|
444
|
+
// one chain reach (W², the two-level composite bound) — a genuine
|
|
445
|
+
// paraphrase inserts inflection-scale material ("does water boil"
|
|
446
|
+
// ↔ "should water be boiled"), while a divergent candidate jumps
|
|
447
|
+
// hundreds of bytes between the query's frames.
|
|
448
|
+
if (uLen > W || cLen > reachCap)
|
|
449
|
+
ok = false;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
// RAW BALANCE gate (closes the OPEN GAP above): the two sides of a
|
|
453
|
+
// genuine lexical substitution swap comparable amounts of content —
|
|
454
|
+
// "big"/"lar" (3/3 bytes, before expansion absorbs the shared "gest"
|
|
455
|
+
// suffix to reach an attestable "biggest"/"largest"). A candidate
|
|
456
|
+
// whose two sentences simply diverge into unrelated continuations
|
|
457
|
+
// produces a LOPSIDED raw mismatch instead — the live wrong answer's
|
|
458
|
+
// raw gap was "Fra"/"Spai" widened to (3,8) by the align sweep
|
|
459
|
+
// finding a coincidental "nce " match years later in "since" — 3
|
|
460
|
+
// bytes of query content standing in for 8 bytes of candidate
|
|
461
|
+
// content is not a word swap, it is two different sentences that
|
|
462
|
+
// happen to share a few letters. Uses the SAME dominates() bar
|
|
463
|
+
// (part*2 > whole) applied throughout the codebase, symmetrically:
|
|
464
|
+
// the smaller raw side must be more than half the larger. Applies
|
|
465
|
+
// to the RAW gap, before expansion — expansion only ever grows both
|
|
466
|
+
// sides by IDENTICAL absorbed bytes, so it cannot fix an imbalance
|
|
467
|
+
// that was already there.
|
|
468
|
+
let accepted = false;
|
|
469
|
+
const balanced = dominates(Math.min(uLen, cLen), Math.max(uLen, cLen));
|
|
470
|
+
const maxExtra = balanced ? reachCap - Math.max(uLen, cLen) : -1;
|
|
471
|
+
outer: for (let extra = 0; extra <= maxExtra; extra++) {
|
|
472
|
+
for (let a = 0; a <= extra; a++) {
|
|
473
|
+
const b = extra - a;
|
|
474
|
+
const qs2 = g.qs - a, qe2 = g.qe + b;
|
|
475
|
+
const cs2 = g.cs - a, ce2 = g.ce + b;
|
|
476
|
+
if (qs2 < 0 || qe2 > query.length)
|
|
477
|
+
continue;
|
|
478
|
+
if (cs2 < 0 || ce2 > cBytes.length)
|
|
479
|
+
continue;
|
|
480
|
+
// INTERIOR gate: a substitution must sit INSIDE matched
|
|
481
|
+
// structure — at least one full window of matched bytes must
|
|
482
|
+
// remain adjacent on BOTH sides after absorption. Every junk
|
|
483
|
+
// substitution observed live sat at the query's edge, with only
|
|
484
|
+
// terminal punctuation beyond it ("…born in [England].",
|
|
485
|
+
// "…capital of [Zamunda]?"): an edge mismatch is the query
|
|
486
|
+
// trailing off into different content, not a word standing in a
|
|
487
|
+
// shared frame.
|
|
488
|
+
const leftOk = matched.some(([s, e]) => e >= qs2 && qs2 - s >= W);
|
|
489
|
+
const rightOk = matched.some(([s, e]) => s <= qe2 && e - qe2 >= W);
|
|
490
|
+
if (!leftOk || !rightOk)
|
|
491
|
+
continue;
|
|
492
|
+
if (!attestedQ(qs2, qe2))
|
|
493
|
+
continue;
|
|
494
|
+
const u = query.subarray(qs2, qe2);
|
|
495
|
+
const cSpan = cBytes.subarray(cs2, ce2);
|
|
496
|
+
if (cosine(perceive(ctx, u).v, perceive(ctx, cSpan).v) < bar) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (!unanimous(u, cSpan, query.subarray(qs2 - W, qs2), query.subarray(qe2, qe2 + W)))
|
|
500
|
+
continue;
|
|
501
|
+
subs.push({ qs: qs2, qe: qe2, cs: cs2, ce: ce2 });
|
|
502
|
+
accepted = true;
|
|
503
|
+
break outer;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (!accepted && (uLen > W || cLen > reachCap))
|
|
507
|
+
ok = false;
|
|
508
|
+
}
|
|
509
|
+
// A candidate with ZERO gaps needs no substitution and might seem like
|
|
510
|
+
// the strongest possible bridge, but accepting it here is a trap: this
|
|
511
|
+
// mechanism runs only where recall's own resonance/echo tiers already
|
|
512
|
+
// declined to ground a same-shape, zero-substitution match — usually
|
|
513
|
+
// because the query is a strict byte-PREFIX of several candidates
|
|
514
|
+
// (many trained "The capital of X is Y." facts share the query "The
|
|
515
|
+
// capital of France is" as a substring once the true France fact is
|
|
516
|
+
// filtered out for lacking a continuation edge) and nothing here
|
|
517
|
+
// corroborates picking one candidate's completion over another's
|
|
518
|
+
// (observed live: prefix-completion bridged to an unrelated "London"
|
|
519
|
+
// trivia distractor over the true France fact, which precedes it in
|
|
520
|
+
// resonance rank but has no outgoing edge to bridge through). This
|
|
521
|
+
// mechanism exists to explain SUBSTITUTIONS; a query needing none is
|
|
522
|
+
// recall's job, not the bridge's.
|
|
523
|
+
if (!ok || subs.length === 0)
|
|
524
|
+
continue;
|
|
525
|
+
// Coverage: matched runs plus accepted substitutions must dominate the
|
|
526
|
+
// query, every interior gap already proved ≤ W above, and the EDGES
|
|
527
|
+
// must be explained to the same one-window tolerance — the same "at
|
|
528
|
+
// most one river window of foreign content" identityBar prices. The
|
|
529
|
+
// live junk this closes: alignments that matched a query's scaffolding
|
|
530
|
+
// and one substitution but left the query's whole trailing content
|
|
531
|
+
// ("…planet orbiting our sun.", 24 bytes) unexplained, yet still
|
|
532
|
+
// half-dominated the byte count.
|
|
533
|
+
const spans = [
|
|
534
|
+
...matched,
|
|
535
|
+
...subs.map((s) => [s.qs, s.qe]),
|
|
536
|
+
].sort((x, y) => x[0] - y[0]);
|
|
537
|
+
let covered = 0;
|
|
538
|
+
let reachEnd = 0;
|
|
539
|
+
for (const [s, e] of spans) {
|
|
540
|
+
if (e <= reachEnd)
|
|
541
|
+
continue;
|
|
542
|
+
covered += e - Math.max(s, reachEnd);
|
|
543
|
+
reachEnd = Math.max(reachEnd, e);
|
|
544
|
+
}
|
|
545
|
+
if (spans[0][0] > W || query.length - reachEnd > W)
|
|
546
|
+
continue;
|
|
547
|
+
if (!dominates(covered, query.length))
|
|
548
|
+
continue;
|
|
549
|
+
// KNOWN content may never be dismissed — see dismissedKnownContent
|
|
550
|
+
// (the live case: "what is the capital of france" aligning into a
|
|
551
|
+
// Matrix synopsis by writing off "ance" — a stored window of the
|
|
552
|
+
// trained "France" — as a gap, while genuinely novel spans like
|
|
553
|
+
// test/49's untrained "Name" remain tolerable).
|
|
554
|
+
if (dismissedKnownQ(spans))
|
|
555
|
+
continue;
|
|
556
|
+
if (covered > bestAccounted) {
|
|
557
|
+
bestAccounted = covered;
|
|
558
|
+
best = { id: sid, accounted: spans, subs };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (best !== null) {
|
|
562
|
+
ctx.trace?.step("substitutionBridge", [rItem(query, "query")], [
|
|
563
|
+
rNode(ctx, best.id, "bridged-context"),
|
|
564
|
+
...best.subs.map((s) => rItem(query.subarray(s.qs, s.qe), "substituted")),
|
|
565
|
+
], `a trained context accounts for the query up to ${best.subs.length} ` +
|
|
566
|
+
`corroborated substitution(s) — grounding through its learnt edges`);
|
|
567
|
+
}
|
|
568
|
+
return best;
|
|
569
|
+
}
|
package/dist/src/mind/index.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ export type { Input, Response } from "./mind.js";
|
|
|
3
3
|
export type { ComputedSpan, ExtensionHost } from "./mind.js";
|
|
4
4
|
export type { MechanismResult, PipelineMechanism, Precomputed, } from "./pipeline-mechanism.js";
|
|
5
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";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Hit } from "../store.js";
|
|
1
2
|
import type { MindContext } from "./types.js";
|
|
2
3
|
export interface Junction {
|
|
3
4
|
/** The node whose learnt bytes evidence this junction (a container form,
|
|
@@ -6,6 +7,34 @@ export interface Junction {
|
|
|
6
7
|
/** The bytes that belong between left and right. */
|
|
7
8
|
interior: Uint8Array;
|
|
8
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>;
|
|
9
38
|
/** Seed node ids to ascend from for one side of a junction: the side's own
|
|
10
39
|
* node when it is a stored form, plus — when the node has no structural
|
|
11
40
|
* parents — its canonical window ids. A non-W-aligned node may have no
|
|
@@ -92,4 +121,4 @@ export declare function junctionContainers(ctx: MindContext, left: Uint8Array, r
|
|
|
92
121
|
* cost is bounded at √N·W pops total regardless of how many siblings are
|
|
93
122
|
* tried. A sibling whose bytes exceed `maxInterior` is skipped (it
|
|
94
123
|
* cannot be junction-sized). */
|
|
95
|
-
export declare function junctionSynonyms(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxInterior: number, unordered?: boolean): Promise<
|
|
124
|
+
export declare function junctionSynonyms(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxInterior: number, unordered?: boolean, sides?: JunctionSynonymSides): Promise<SynonymJunction[]>;
|