@hviana/sema 0.4.1 → 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.
- package/dist/src/ingest-cache.js +4 -0
- package/dist/src/meter.d.ts +5 -0
- package/dist/src/meter.js +5 -0
- package/dist/src/mind/attention.js +19 -2
- package/dist/src/mind/bridge.js +265 -56
- package/dist/src/mind/junction.d.ts +7 -4
- package/dist/src/mind/junction.js +16 -5
- package/dist/src/mind/match.d.ts +15 -0
- package/dist/src/mind/match.js +92 -2
- package/dist/src/mind/mechanisms/cast.js +12 -1
- package/dist/src/mind/mechanisms/confluence.js +31 -1
- package/dist/src/mind/mechanisms/cover.d.ts +1 -1
- package/dist/src/mind/mechanisms/cover.js +29 -5
- package/dist/src/mind/mechanisms/recall.js +17 -41
- package/dist/src/mind/mind.d.ts +7 -0
- package/dist/src/mind/mind.js +25 -2
- package/dist/src/mind/pipeline-mechanism.d.ts +2 -2
- package/dist/src/mind/pipeline-mechanism.js +87 -4
- package/dist/src/mind/pipeline.js +1 -1
- package/dist/src/mind/reasoning.js +19 -11
- package/dist/src/mind/recognition.js +41 -0
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/traverse.d.ts +3 -1
- package/dist/src/mind/traverse.js +14 -13
- package/dist/src/mind/types.d.ts +10 -0
- package/package.json +1 -1
- package/src/ingest-cache.ts +4 -0
- package/src/meter.ts +5 -0
- package/src/mind/attention.ts +18 -1
- package/src/mind/bridge.ts +292 -54
- package/src/mind/junction.ts +21 -7
- package/src/mind/match.ts +92 -1
- package/src/mind/mechanisms/cast.ts +12 -0
- package/src/mind/mechanisms/confluence.ts +30 -1
- package/src/mind/mechanisms/cover.ts +36 -4
- package/src/mind/mechanisms/recall.ts +21 -44
- package/src/mind/mind.ts +39 -2
- package/src/mind/pipeline-mechanism.ts +86 -4
- package/src/mind/pipeline.ts +1 -1
- package/src/mind/reasoning.ts +15 -8
- package/src/mind/recognition.ts +40 -0
- package/src/mind/resonance.ts +0 -0
- package/src/mind/traverse.ts +17 -15
- package/src/mind/types.ts +10 -0
- package/test/49-natural-units-synonym-bridge.test.mjs +56 -15
package/dist/src/ingest-cache.js
CHANGED
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
import { deposit, dispatchIngest, ingestOne as depositOne, } from "./mind/learning.js";
|
|
35
35
|
import { bindSeat, companySignature } from "./sema.js";
|
|
36
36
|
import { BoundedMap } from "./store.js";
|
|
37
|
+
import { invalidateStructuralCaches } from "./mind/traverse.js";
|
|
38
|
+
import { invalidateJunctionCache } from "./mind/junction.js";
|
|
37
39
|
/**
|
|
38
40
|
* An ingest cache layered over a Mind.
|
|
39
41
|
*
|
|
@@ -82,6 +84,8 @@ export class CachedIngest {
|
|
|
82
84
|
}
|
|
83
85
|
// ── public API ────────────────────────────────────────────────────────
|
|
84
86
|
async ingest(input, second) {
|
|
87
|
+
invalidateStructuralCaches(this.mind);
|
|
88
|
+
invalidateJunctionCache(this.mind);
|
|
85
89
|
// One shape-reading for both ingest paths — see {@link dispatchIngest}.
|
|
86
90
|
return dispatchIngest(input, second, (i) => this.ingestOne(i), (a, b) => this.ingestPair(a, b));
|
|
87
91
|
}
|
package/dist/src/meter.d.ts
CHANGED
|
@@ -129,6 +129,11 @@ export declare class Meter {
|
|
|
129
129
|
/** Nodes popped by those ascents, against their √N·W budget — the counter
|
|
130
130
|
* that shows whether the walks are deciding early or burning the budget. */
|
|
131
131
|
junctionPops: number;
|
|
132
|
+
/** Arbitrary byte spans whose distributional company was VSA-bundled from
|
|
133
|
+
* existing episode halos. */
|
|
134
|
+
spanHalos: number;
|
|
135
|
+
/** Canonical W-windows examined while composing those span halos. */
|
|
136
|
+
spanHaloWindows: number;
|
|
132
137
|
/** `lightestDerivation` searches started. */
|
|
133
138
|
searches: number;
|
|
134
139
|
/** Chart items popped by those searches. */
|
package/dist/src/meter.js
CHANGED
|
@@ -122,6 +122,11 @@ export class Meter {
|
|
|
122
122
|
/** Nodes popped by those ascents, against their √N·W budget — the counter
|
|
123
123
|
* that shows whether the walks are deciding early or burning the budget. */
|
|
124
124
|
junctionPops = 0;
|
|
125
|
+
/** Arbitrary byte spans whose distributional company was VSA-bundled from
|
|
126
|
+
* existing episode halos. */
|
|
127
|
+
spanHalos = 0;
|
|
128
|
+
/** Canonical W-windows examined while composing those span halos. */
|
|
129
|
+
spanHaloWindows = 0;
|
|
125
130
|
/** `lightestDerivation` searches started. */
|
|
126
131
|
searches = 0;
|
|
127
132
|
/** Chart items popped by those searches. */
|
|
@@ -1509,6 +1509,16 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1509
1509
|
// the same container (or a sub-container of it) twice.
|
|
1510
1510
|
const consumed = new Set();
|
|
1511
1511
|
let probes = 0;
|
|
1512
|
+
// Once atoms themselves are hubs (N > W²), the cross-region analysis gets
|
|
1513
|
+
// one k·W walk allowance per evidence tier. Without a shared allowance,
|
|
1514
|
+
// each of k candidate pairs spends the full corpus-derived budget and a
|
|
1515
|
+
// cumulative dialogue multiplies bounded work into tens of seconds. Small
|
|
1516
|
+
// corpora retain exhaustive exact traversal: below this same scale the
|
|
1517
|
+
// budget would be smaller than the structures the tests deliberately build.
|
|
1518
|
+
const marketScale = k * ctx.space.maxGroup;
|
|
1519
|
+
const corpusScale = N > marketScale ** 3;
|
|
1520
|
+
const exactBudget = corpusScale ? { n: k * ctx.space.maxGroup } : undefined;
|
|
1521
|
+
const synonymBudget = corpusScale ? { n: k * ctx.space.maxGroup } : undefined;
|
|
1512
1522
|
for (let a = 0; a < cand.length && probes < k; a++) {
|
|
1513
1523
|
if (consumed.has(cand[a]))
|
|
1514
1524
|
continue;
|
|
@@ -1521,6 +1531,13 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1521
1531
|
continue;
|
|
1522
1532
|
if (ra.end >= rb.start)
|
|
1523
1533
|
continue; // overlap or adjacent — nothing between
|
|
1534
|
+
// In a cumulative conversation, an old↔old interaction cannot explain
|
|
1535
|
+
// the user turn currently being answered; it was already available
|
|
1536
|
+
// before that turn existed. Keep old↔current pairs (the current turn may
|
|
1537
|
+
// refer to a prior answer), but do not repeatedly spend the junction
|
|
1538
|
+
// budget recomposing two regions wholly before the current boundary.
|
|
1539
|
+
if (ctx.currentTurnStart > 0 && rb.end <= ctx.currentTurnStart)
|
|
1540
|
+
continue;
|
|
1524
1541
|
// Candidates strictly BETWEEN ra and rb (cand is sorted by start, so
|
|
1525
1542
|
// that is exactly cand[a+1 .. b-1]) that already cast their OWN vote —
|
|
1526
1543
|
// genuine, individually-corroborated evidence about what fills the gap
|
|
@@ -1587,7 +1604,7 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1587
1604
|
// ladder rung repeats a halo ANN query an earlier rung already paid for.
|
|
1588
1605
|
const sides = await loadJunctionSynonymSides(ctx, left, right);
|
|
1589
1606
|
let tier = "exact";
|
|
1590
|
-
let containers = junctionContainersFrom(ctx, left, right, cap, seedsOf(cand[a]), seedsOf(cand[b]),
|
|
1607
|
+
let containers = junctionContainersFrom(ctx, left, right, cap, seedsOf(cand[a]), seedsOf(cand[b]), exactBudget, true);
|
|
1591
1608
|
if (probe) {
|
|
1592
1609
|
probe.exact = {
|
|
1593
1610
|
attempted: true,
|
|
@@ -1598,7 +1615,7 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
|
|
|
1598
1615
|
// Tiers 2-4 — synonym containers (junctionSynonyms itself runs
|
|
1599
1616
|
// single-synonym first, falling to double-synonym only when
|
|
1600
1617
|
// single-synonym found nothing — see junction.ts).
|
|
1601
|
-
const syn = await junctionSynonyms(ctx, left, right, maxInterior, true, sides);
|
|
1618
|
+
const syn = await junctionSynonyms(ctx, left, right, maxInterior, true, sides, synonymBudget);
|
|
1602
1619
|
if (probe) {
|
|
1603
1620
|
const singleAttempted = sides.leftSiblings.length > 0 ||
|
|
1604
1621
|
sides.rightSiblings.length > 0;
|
package/dist/src/mind/bridge.js
CHANGED
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
// them reused across ≥ 2 containers (the same "≥ 2 structural parents"
|
|
27
27
|
// bar propagateSuffixes gates suffix inheritance with). An untrained
|
|
28
28
|
// word ("deadliest") has no stored windows and can never substitute.
|
|
29
|
-
// •
|
|
30
|
-
// conceptThreshold(D)
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
29
|
+
// • GRADED IDENTITY — lexical geometry is tried first at
|
|
30
|
+
// conceptThreshold(D). Differently-spelled forms fall through to VSA
|
|
31
|
+
// company: their stored W-window occurrences ascend to learned episodes,
|
|
32
|
+
// whose bundled halos must clear significanceBar(D), the same
|
|
33
|
+
// distributional-evidence bar used by analogyStrength.
|
|
34
34
|
//
|
|
35
35
|
// A candidate context is accepted when its aligned-plus-substituted spans
|
|
36
36
|
// DOMINATE the query (the same half-dominance predicate used throughout)
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
//
|
|
42
42
|
// COST: nothing on any answering path — the bridge runs only where the
|
|
43
43
|
// alternative was silence. There it pays O(|query|) content-hash probes
|
|
44
|
-
// (the propagateSuffixes trick), at most W anchor climbs and
|
|
45
|
-
// candidate reads, and one
|
|
46
|
-
//
|
|
44
|
+
// (the propagateSuffixes trick), at most W anchor climbs and
|
|
45
|
+
// 2·recallQueryK candidate reads, and one
|
|
46
|
+
// O(|query|·|candidate|)-bounded alignment each.
|
|
47
47
|
//
|
|
48
48
|
// FIXED WRONG-ANSWER GAP (found and closed 2026-07-20): a proper-noun swap
|
|
49
49
|
// could pass both derived gates above and voice a WRONG fact. Live case:
|
|
@@ -90,12 +90,14 @@
|
|
|
90
90
|
// boiling-point and lowercase-France bridge wins are unaffected; full
|
|
91
91
|
// suite green (358/358).
|
|
92
92
|
import { cosine } from "../vec.js";
|
|
93
|
-
import { conceptThreshold, dominates } from "../geometry.js";
|
|
93
|
+
import { conceptThreshold, dominates, significanceBar } from "../geometry.js";
|
|
94
94
|
import { bytesEqual, indexOf } from "../bytes.js";
|
|
95
95
|
import { foldTree, perceive, read } from "./primitives.js";
|
|
96
96
|
import { chainReach, leafIdRun } from "./canonical.js";
|
|
97
97
|
import { corpusN, edgeAncestors, hubBound, sharedReachMemo, } from "./traverse.js";
|
|
98
98
|
import { rItem, rNode } from "./trace.js";
|
|
99
|
+
import { junctionContainersFrom } from "./junction.js";
|
|
100
|
+
import { spanHalo } from "./match.js";
|
|
99
101
|
/** True when some query byte-range left UNACCOUNTED by `spans` contains a
|
|
100
102
|
* STORED window — content the store has seen that the proposed reading
|
|
101
103
|
* simply ignores. The IGNORED-KNOWN principle: a span may be dismissed
|
|
@@ -238,8 +240,33 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
238
240
|
return null;
|
|
239
241
|
const bound = hubBound(ctx);
|
|
240
242
|
const N = corpusN(ctx);
|
|
243
|
+
const marketScale = ctx.cfg.recallQueryK * W;
|
|
244
|
+
const candidateCap = N <= marketScale ** 3 ? bound : 2 * ctx.cfg.recallQueryK;
|
|
241
245
|
const bar = conceptThreshold(ctx.store.D);
|
|
246
|
+
const synonymBar = significanceBar(ctx.store.D);
|
|
242
247
|
const reachCap = chainReach(W);
|
|
248
|
+
const diagnostics = ctx.trace
|
|
249
|
+
? {
|
|
250
|
+
anchors: 0,
|
|
251
|
+
picked: 0,
|
|
252
|
+
proposed: 0,
|
|
253
|
+
structuralProposed: 0,
|
|
254
|
+
proposedGrounded: 0,
|
|
255
|
+
synonymChecks: 0,
|
|
256
|
+
bestSynonym: 0,
|
|
257
|
+
climbed: 0,
|
|
258
|
+
phraseScale: 0,
|
|
259
|
+
seeded: 0,
|
|
260
|
+
aligned: 0,
|
|
261
|
+
structurallyValid: 0,
|
|
262
|
+
coverageValid: 0,
|
|
263
|
+
identityValid: 0,
|
|
264
|
+
knownContentValid: 0,
|
|
265
|
+
bestCovered: 0,
|
|
266
|
+
bestRank: -1,
|
|
267
|
+
closest: [],
|
|
268
|
+
}
|
|
269
|
+
: null;
|
|
243
270
|
// PHRASE-SCALE CANDIDATE CAP — the same |content|·W bound the weave
|
|
244
271
|
// (pipeline-mechanism.ts), the cross-region junction ladder's
|
|
245
272
|
// `maxInterior`, and structural resonance's `maxSiblingBytes` all apply,
|
|
@@ -296,8 +323,12 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
296
323
|
continue;
|
|
297
324
|
anchors.push({ off: o, id, rarity });
|
|
298
325
|
}
|
|
299
|
-
if (
|
|
326
|
+
if (diagnostics)
|
|
327
|
+
diagnostics.anchors = anchors.length;
|
|
328
|
+
if (anchors.length === 0) {
|
|
329
|
+
ctx.trace?.step("substitutionBridge", [rItem(query, "query")], [], "no stored query window can anchor a corroborated substitution", undefined, diagnostics);
|
|
300
330
|
return null;
|
|
331
|
+
}
|
|
301
332
|
// CORROBORATION (see the module-level doc) over the precomputed window
|
|
302
333
|
// facts: the query span [qs,qe) attests when every full W-window inside
|
|
303
334
|
// it is a stored flat form and at least one is reused across ≥ 2
|
|
@@ -359,22 +390,42 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
359
390
|
const explainedSpan = (bytes, from, to) => {
|
|
360
391
|
if (to - from < W)
|
|
361
392
|
return true;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (ids === null)
|
|
365
|
-
return false;
|
|
366
|
-
const wid = ctx.store.findBranch(ids);
|
|
367
|
-
if (wid === null)
|
|
368
|
-
return false;
|
|
369
|
-
const r = edgeAncestors(ctx, wid, N, reachMemo);
|
|
370
|
-
if (r.saturated)
|
|
371
|
-
continue; // in too many places to discriminate
|
|
372
|
-
if (r.roots.length === 0)
|
|
373
|
-
return false; // reaches nothing: novel content
|
|
374
|
-
if (!dominates(r.contextsReached, N))
|
|
393
|
+
const common = (start, end) => {
|
|
394
|
+
if (end - start < W)
|
|
375
395
|
return false;
|
|
396
|
+
for (let o = start; o + W <= end; o++) {
|
|
397
|
+
const ids = leafIdRun(ctx, bytes, o, o + W);
|
|
398
|
+
if (ids === null)
|
|
399
|
+
return false;
|
|
400
|
+
const wid = ctx.store.findBranch(ids);
|
|
401
|
+
if (wid === null)
|
|
402
|
+
return false;
|
|
403
|
+
const r = edgeAncestors(ctx, wid, N, reachMemo);
|
|
404
|
+
if (r.saturated)
|
|
405
|
+
continue; // in too many places to discriminate
|
|
406
|
+
if (r.roots.length === 0)
|
|
407
|
+
return false; // reaches nothing: novel content
|
|
408
|
+
if (!dominates(r.contextsReached, N))
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
return true;
|
|
412
|
+
};
|
|
413
|
+
if (common(from, to))
|
|
414
|
+
return true;
|
|
415
|
+
// Alignment may attach the shared delimiter to either side of an inserted
|
|
416
|
+
// phrase. Up to W-1 boundary bytes are below the fold's identity scale;
|
|
417
|
+
// classify the phrase by a full-window interior core when one exists.
|
|
418
|
+
// This does not erase a short discriminative insertion: "heavy" still
|
|
419
|
+
// leaves the full `heav`/`eavy` windows for the corpus-global test.
|
|
420
|
+
for (let left = 0; left < W; left++) {
|
|
421
|
+
for (let right = 0; right < W; right++) {
|
|
422
|
+
if (left + right === 0 || left + right >= W)
|
|
423
|
+
continue;
|
|
424
|
+
if (common(from + left, to - right))
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
376
427
|
}
|
|
377
|
-
return
|
|
428
|
+
return false;
|
|
378
429
|
};
|
|
379
430
|
anchors.sort((a, b) => a.rarity - b.rarity);
|
|
380
431
|
// Up to W anchors, at least one window apart — the quantum's own count.
|
|
@@ -386,6 +437,8 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
386
437
|
continue;
|
|
387
438
|
picked.push(a);
|
|
388
439
|
}
|
|
440
|
+
if (diagnostics)
|
|
441
|
+
diagnostics.picked = picked.length;
|
|
389
442
|
// 2. Candidate trained contexts. Two proposal channels, one verifier:
|
|
390
443
|
// (a) the caller's PROPOSED hits — recall's whole-query resonance
|
|
391
444
|
// ranking, the retrieval structure built to surface near-paraphrase
|
|
@@ -395,20 +448,50 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
395
448
|
// candidate passes the same byte-exact alignment and gates below.
|
|
396
449
|
const seen = new Set();
|
|
397
450
|
const candidates = [];
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
451
|
+
// Exact co-occurrence proposes contexts the whole-form ANN can miss when a
|
|
452
|
+
// short insertion shifts every later fold boundary. The byte alignment below
|
|
453
|
+
// remains the decider. All pairs share one candidateCap·W junction
|
|
454
|
+
// allowance, ordered
|
|
455
|
+
// by their rarest side and then span: a rare content window joined to a
|
|
456
|
+
// distant frame boundary discriminates a whole question better than two
|
|
457
|
+
// neighbouring rare windows inside the same word.
|
|
458
|
+
if (query.length <= 2 * reachCap) {
|
|
459
|
+
const pairs = [];
|
|
460
|
+
for (let i = 0; i < picked.length; i++) {
|
|
461
|
+
for (let j = i + 1; j < picked.length; j++) {
|
|
462
|
+
pairs.push([picked[i], picked[j]]);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
pairs.sort((a, b) => Math.min(a[0].rarity, a[1].rarity) -
|
|
466
|
+
Math.min(b[0].rarity, b[1].rarity) ||
|
|
467
|
+
Math.abs(b[0].off - b[1].off) - Math.abs(a[0].off - a[1].off) ||
|
|
468
|
+
a[0].rarity + a[1].rarity - b[0].rarity - b[1].rarity);
|
|
469
|
+
const structuralBudget = {
|
|
470
|
+
n: chainReach(W) * W * ctx.cfg.recallQueryK,
|
|
471
|
+
};
|
|
472
|
+
for (const [left, right] of pairs.slice(0, W)) {
|
|
473
|
+
const found = junctionContainersFrom(ctx, query.subarray(left.off, left.off + W), query.subarray(right.off, right.off + W), capBytes, [left.id], [right.id], structuralBudget, true);
|
|
474
|
+
for (const hit of found) {
|
|
475
|
+
if (candidates.length >= candidateCap)
|
|
476
|
+
break;
|
|
477
|
+
if (seen.has(hit.id) || !ctx.store.hasNext(hit.id))
|
|
478
|
+
continue;
|
|
479
|
+
seen.add(hit.id);
|
|
480
|
+
candidates.push(hit.id);
|
|
481
|
+
if (diagnostics)
|
|
482
|
+
diagnostics.structuralProposed++;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// Once exact structural proposals fill the shared cap, no caller proposal
|
|
487
|
+
// can enter the verifier. Do not evaluate the lazy ANN thunk merely to
|
|
488
|
+
// discard every result at the loop's first guard.
|
|
489
|
+
const proposedIds = candidates.length < candidateCap ? await proposed() : [];
|
|
490
|
+
if (diagnostics)
|
|
491
|
+
diagnostics.proposed = proposedIds.length;
|
|
492
|
+
for (const sid of proposedIds) {
|
|
493
|
+
if (candidates.length >= candidateCap)
|
|
494
|
+
break;
|
|
412
495
|
if (seen.has(sid))
|
|
413
496
|
continue;
|
|
414
497
|
seen.add(sid);
|
|
@@ -429,15 +512,28 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
429
512
|
seen.add(use);
|
|
430
513
|
}
|
|
431
514
|
candidates.push(use);
|
|
515
|
+
if (diagnostics)
|
|
516
|
+
diagnostics.proposedGrounded++;
|
|
432
517
|
}
|
|
518
|
+
// Proposal channel — carries its caller's own bound (recall's resonance
|
|
519
|
+
// k), sharing the 2·recallQueryK candidate allowance
|
|
520
|
+
// with the structural and climb channels. A proposal may
|
|
521
|
+
// be a FLAT content twin whose continuation edge lives on the
|
|
522
|
+
// fold-shaped deposit node with the same bytes — the same twin split
|
|
523
|
+
// canonResolve bridges by re-folding (primitives.ts) — but the re-fold
|
|
524
|
+
// (a full perceive of the candidate's bytes) is paid only for proposals
|
|
525
|
+
// that could align at all: alignment can only seed at a picked anchor
|
|
526
|
+
// window occurring literally in the candidate (measured: unconditional
|
|
527
|
+
// re-folds multiplied the refusal-path latency several-fold).
|
|
528
|
+
// FIRST TOUCH of the caller's proposals — past every gate that could have
|
|
529
|
+
// refused without them (see substitutionBridge's doc).
|
|
433
530
|
// Climb channel — edge-bearing ancestors only, decided by the indexed
|
|
434
531
|
// O(1) hasNext; no byte is read here (the climb visits hundreds of
|
|
435
532
|
// roots, and reading each was measured to dominate the refusal path).
|
|
436
|
-
const proposedCount = candidates.length;
|
|
437
533
|
for (const a of picked) {
|
|
438
|
-
const reach = edgeAncestors(ctx, a.id, N);
|
|
534
|
+
const reach = edgeAncestors(ctx, a.id, N, reachMemo);
|
|
439
535
|
for (const sid of reach.roots) {
|
|
440
|
-
if (candidates.length
|
|
536
|
+
if (candidates.length >= candidateCap)
|
|
441
537
|
break;
|
|
442
538
|
if (seen.has(sid))
|
|
443
539
|
continue;
|
|
@@ -445,20 +541,42 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
445
541
|
if (!ctx.store.hasNext(sid))
|
|
446
542
|
continue;
|
|
447
543
|
candidates.push(sid);
|
|
544
|
+
if (diagnostics)
|
|
545
|
+
diagnostics.climbed++;
|
|
448
546
|
}
|
|
449
|
-
if (candidates.length
|
|
547
|
+
if (candidates.length >= candidateCap)
|
|
450
548
|
break;
|
|
451
549
|
}
|
|
452
550
|
// 3. Align each candidate; gate its mismatches; keep the best.
|
|
453
551
|
// Over-cap candidates are dropped here rather than earlier: the climb
|
|
454
552
|
// channel deliberately reads no bytes while collecting (the climb visits
|
|
455
553
|
// hundreds of roots), so this is where its proposals are first sized.
|
|
456
|
-
|
|
457
|
-
|
|
554
|
+
//
|
|
555
|
+
// Candidate bytes are read LAZILY — on first access during the seed
|
|
556
|
+
// check — not eagerly for every collected id. On a 325K-context store
|
|
557
|
+
// the climb channel alone can propose hundreds of edge-bearing ancestors
|
|
558
|
+
// (hubBound = 571), most of which will never contain a picked anchor
|
|
559
|
+
// window and would be discarded at the seed check without their bytes
|
|
560
|
+
// ever being consulted. Eager reads for 500+ candidates each traversing
|
|
561
|
+
// the DAG (profiled at 12K node records and 73KB of bytes read per
|
|
562
|
+
// refusing query) is the dominant remaining bridge cost after the ANN
|
|
563
|
+
// gate. A Map stays available for the frame-unanimity scan below, which
|
|
564
|
+
// only needs bytes of candidates that actually seeded.
|
|
565
|
+
const seededBytes = new Map();
|
|
566
|
+
/** Read a candidate's bytes once; cache for the seed check AND for the
|
|
567
|
+
* frame-unanimity scan that follows alignment. Returns null when the
|
|
568
|
+
* candidate exceeds the phrase-scale cap or has no content. */
|
|
569
|
+
const bytesOfCandidate = (sid) => {
|
|
570
|
+
const hit = seededBytes.get(sid);
|
|
571
|
+
if (hit !== undefined)
|
|
572
|
+
return hit;
|
|
458
573
|
const b = candidateBytes(sid);
|
|
459
574
|
if (b !== null)
|
|
460
|
-
|
|
461
|
-
|
|
575
|
+
seededBytes.set(sid, b);
|
|
576
|
+
return b;
|
|
577
|
+
};
|
|
578
|
+
if (diagnostics)
|
|
579
|
+
diagnostics.phraseScale = seededBytes.size;
|
|
462
580
|
// FRAME UNANIMITY: a substitution U → C inside the frame (Lf, Rf) is
|
|
463
581
|
// groundable only when the collected candidates — the store's own sample
|
|
464
582
|
// of contexts sharing the query's content — are unanimous about the
|
|
@@ -477,7 +595,7 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
477
595
|
// the substitution was accepted). "Unanimous" must mean the store's own
|
|
478
596
|
// instances agree, which requires at least one instance to consult.
|
|
479
597
|
const unanimous = (u, c, lf, rf) => {
|
|
480
|
-
for (const bytes of
|
|
598
|
+
for (const bytes of seededBytes.values()) {
|
|
481
599
|
let from = 0;
|
|
482
600
|
for (;;) {
|
|
483
601
|
const i = indexOf(bytes, lf, from);
|
|
@@ -509,9 +627,15 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
509
627
|
// check — verified live).
|
|
510
628
|
let best = null;
|
|
511
629
|
let bestAccounted = 0;
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
630
|
+
const queryHaloMemo = new Map();
|
|
631
|
+
const candidateHaloMemo = new Map();
|
|
632
|
+
for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
|
|
633
|
+
const sid = candidates[candidateIndex];
|
|
634
|
+
// Read bytes lazily — most climb-proposed candidates have no picked
|
|
635
|
+
// anchor window and will never pass the seed check below, so their
|
|
636
|
+
// bytes are never read at all.
|
|
637
|
+
const cBytes = bytesOfCandidate(sid);
|
|
638
|
+
if (cBytes === null)
|
|
515
639
|
continue;
|
|
516
640
|
// Seed at the rarest picked anchor that literally occurs in this
|
|
517
641
|
// candidate.
|
|
@@ -525,7 +649,30 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
525
649
|
}
|
|
526
650
|
if (seed === null)
|
|
527
651
|
continue;
|
|
652
|
+
if (diagnostics)
|
|
653
|
+
diagnostics.seeded++;
|
|
528
654
|
const { matched, gaps } = align(ctx, query, cBytes, seed.qo, seed.co);
|
|
655
|
+
if (diagnostics)
|
|
656
|
+
diagnostics.aligned++;
|
|
657
|
+
// Investment gate: even treating every two-sided mismatch as a valid
|
|
658
|
+
// synonym, can this alignment satisfy the bridge's final coverage rule?
|
|
659
|
+
// Distributional span composition performs bounded ancestor climbs; never
|
|
660
|
+
// pay for it on a candidate arithmetic already proves cannot win.
|
|
661
|
+
let matchStart = query.length;
|
|
662
|
+
let matchEnd = 0;
|
|
663
|
+
let potential = 0;
|
|
664
|
+
for (const [s, e] of matched) {
|
|
665
|
+
matchStart = Math.min(matchStart, s);
|
|
666
|
+
matchEnd = Math.max(matchEnd, e);
|
|
667
|
+
potential += e - s;
|
|
668
|
+
}
|
|
669
|
+
for (const g of gaps) {
|
|
670
|
+
if (g.qe > g.qs && g.ce > g.cs)
|
|
671
|
+
potential += g.qe - g.qs;
|
|
672
|
+
}
|
|
673
|
+
if (matchStart > W || query.length - matchEnd > W ||
|
|
674
|
+
!dominates(potential, query.length))
|
|
675
|
+
continue;
|
|
529
676
|
// Gate each mismatch: a corroborated, geometrically-identified
|
|
530
677
|
// substitution counts as accounted; anything else stays a gap.
|
|
531
678
|
//
|
|
@@ -565,12 +712,14 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
565
712
|
// happen to share a few letters. Uses the SAME dominates() bar
|
|
566
713
|
// (part*2 > whole) applied throughout the codebase, symmetrically:
|
|
567
714
|
// the smaller raw side must be more than half the larger. Applies
|
|
568
|
-
// to the RAW gap, before expansion — expansion
|
|
569
|
-
// sides by IDENTICAL absorbed bytes, so it cannot
|
|
570
|
-
// that was already there.
|
|
715
|
+
// to the RAW gap for GEOMETRIC identity, before expansion — expansion
|
|
716
|
+
// only ever grows both sides by IDENTICAL absorbed bytes, so it cannot
|
|
717
|
+
// fix an imbalance that was already there. Distributional synonym
|
|
718
|
+
// evidence is exempt: two phrases may occupy the same role at very
|
|
719
|
+
// different lengths.
|
|
571
720
|
let accepted = false;
|
|
572
721
|
const balanced = dominates(Math.min(uLen, cLen), Math.max(uLen, cLen));
|
|
573
|
-
const maxExtra =
|
|
722
|
+
const maxExtra = reachCap - Math.max(uLen, cLen);
|
|
574
723
|
outer: for (let extra = 0; extra <= maxExtra; extra++) {
|
|
575
724
|
for (let a = 0; a <= extra; a++) {
|
|
576
725
|
const b = extra - a;
|
|
@@ -596,7 +745,31 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
596
745
|
continue;
|
|
597
746
|
const u = query.subarray(qs2, qe2);
|
|
598
747
|
const cSpan = cBytes.subarray(cs2, ce2);
|
|
599
|
-
|
|
748
|
+
const geometric = cosine(perceive(ctx, u).v, perceive(ctx, cSpan).v);
|
|
749
|
+
const qKey = `${qs2}:${qe2}`;
|
|
750
|
+
let qHalo = queryHaloMemo.get(qKey);
|
|
751
|
+
if (qHalo === undefined) {
|
|
752
|
+
qHalo = spanHalo(ctx, query, qs2, qe2);
|
|
753
|
+
queryHaloMemo.set(qKey, qHalo);
|
|
754
|
+
}
|
|
755
|
+
const cKey = `${sid}:${cs2}:${ce2}`;
|
|
756
|
+
let cHalo = candidateHaloMemo.get(cKey);
|
|
757
|
+
if (cHalo === undefined) {
|
|
758
|
+
cHalo = spanHalo(ctx, cBytes, cs2, ce2);
|
|
759
|
+
candidateHaloMemo.set(cKey, cHalo);
|
|
760
|
+
}
|
|
761
|
+
const distributional = qHalo !== null && cHalo !== null
|
|
762
|
+
? cosine(qHalo, cHalo)
|
|
763
|
+
: 0;
|
|
764
|
+
if (diagnostics) {
|
|
765
|
+
diagnostics.synonymChecks++;
|
|
766
|
+
diagnostics.bestSynonym = Math.max(diagnostics.bestSynonym, distributional);
|
|
767
|
+
}
|
|
768
|
+
// Graded identity: byte geometry remains the cheap first tier;
|
|
769
|
+
// VSA company is the synonym tier when differently-spelled forms
|
|
770
|
+
// occupy the same learnt distributional role.
|
|
771
|
+
if ((!balanced || geometric < bar) &&
|
|
772
|
+
distributional < synonymBar) {
|
|
600
773
|
continue;
|
|
601
774
|
}
|
|
602
775
|
if (!unanimous(u, cSpan, query.subarray(qs2 - W, qs2), query.subarray(qe2, qe2 + W)))
|
|
@@ -625,6 +798,8 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
625
798
|
// recall's job, not the bridge's.
|
|
626
799
|
if (!ok)
|
|
627
800
|
continue;
|
|
801
|
+
if (diagnostics)
|
|
802
|
+
diagnostics.structurallyValid++;
|
|
628
803
|
// Coverage: matched runs plus accepted substitutions must dominate the
|
|
629
804
|
// query, every interior gap already proved ≤ W above, and the EDGES
|
|
630
805
|
// must be explained to the same one-window tolerance — the same "at
|
|
@@ -645,10 +820,34 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
645
820
|
covered += e - Math.max(s, reachEnd);
|
|
646
821
|
reachEnd = Math.max(reachEnd, e);
|
|
647
822
|
}
|
|
823
|
+
if (diagnostics) {
|
|
824
|
+
diagnostics.bestCovered = Math.max(diagnostics.bestCovered, covered);
|
|
825
|
+
const candidateGapBytes = gaps.reduce((n, g) => n + g.ce - g.cs, 0);
|
|
826
|
+
diagnostics.closest.push({
|
|
827
|
+
id: sid,
|
|
828
|
+
covered,
|
|
829
|
+
leading: spans[0][0],
|
|
830
|
+
trailing: query.length - reachEnd,
|
|
831
|
+
gaps: gaps.length,
|
|
832
|
+
substitutions: subs.length,
|
|
833
|
+
queryGapBytes: gaps.reduce((n, g) => n + g.qe - g.qs, 0),
|
|
834
|
+
candidateGapBytes,
|
|
835
|
+
gapRanges: gaps.map((g) => [g.qs, g.qe, g.cs, g.ce]),
|
|
836
|
+
candidateSurplus: cBytes.length - covered - candidateGapBytes,
|
|
837
|
+
gapsExplained: gaps.every((g) => explainedSpan(cBytes, g.cs, g.ce)),
|
|
838
|
+
});
|
|
839
|
+
diagnostics.closest.sort((a, b) => b.covered - a.covered ||
|
|
840
|
+
a.leading + a.trailing - b.leading - b.trailing ||
|
|
841
|
+
a.id - b.id);
|
|
842
|
+
if (diagnostics.closest.length > W)
|
|
843
|
+
diagnostics.closest.length = W;
|
|
844
|
+
}
|
|
648
845
|
if (spans[0][0] > W || query.length - reachEnd > W)
|
|
649
846
|
continue;
|
|
650
847
|
if (!dominates(covered, query.length))
|
|
651
848
|
continue;
|
|
849
|
+
if (diagnostics)
|
|
850
|
+
diagnostics.coverageValid++;
|
|
652
851
|
// ZERO-SUBSTITUTION ADMISSION — an IDENTITY claim, not a substitution.
|
|
653
852
|
//
|
|
654
853
|
// A candidate needing no substitution is normally refused (see the trap
|
|
@@ -715,6 +914,8 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
715
914
|
if (!gaps.every((g) => explainedSpan(cBytes, g.cs, g.ce)))
|
|
716
915
|
continue;
|
|
717
916
|
}
|
|
917
|
+
if (diagnostics)
|
|
918
|
+
diagnostics.identityValid++;
|
|
718
919
|
// KNOWN content may never be dismissed — see dismissedKnownContent
|
|
719
920
|
// (the live case: "what is the capital of france" aligning into a
|
|
720
921
|
// Matrix synopsis by writing off "ance" — a stored window of the
|
|
@@ -722,9 +923,13 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
722
923
|
// test/49's untrained "Name" remain tolerable).
|
|
723
924
|
if (dismissedKnownQ(spans))
|
|
724
925
|
continue;
|
|
926
|
+
if (diagnostics)
|
|
927
|
+
diagnostics.knownContentValid++;
|
|
725
928
|
if (covered > bestAccounted) {
|
|
726
929
|
bestAccounted = covered;
|
|
727
930
|
best = { id: sid, accounted: spans, subs };
|
|
931
|
+
if (diagnostics)
|
|
932
|
+
diagnostics.bestRank = candidateIndex;
|
|
728
933
|
}
|
|
729
934
|
}
|
|
730
935
|
if (best !== null) {
|
|
@@ -732,7 +937,11 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
732
937
|
rNode(ctx, best.id, "bridged-context"),
|
|
733
938
|
...best.subs.map((s) => rItem(query.subarray(s.qs, s.qe), "substituted")),
|
|
734
939
|
], `a trained context accounts for the query up to ${best.subs.length} ` +
|
|
735
|
-
`corroborated substitution(s) — grounding through its learnt edges
|
|
940
|
+
`corroborated substitution(s) — grounding through its learnt edges`, undefined, diagnostics);
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
ctx.trace?.step("substitutionBridge", [rItem(query, "query")], [], "candidate contexts were proposed, but none passed the bridge's " +
|
|
944
|
+
"structural identity and corroboration gates", undefined, diagnostics);
|
|
736
945
|
}
|
|
737
946
|
return best;
|
|
738
947
|
}
|
|
@@ -45,9 +45,9 @@ export declare function loadJunctionSynonymSides(ctx: MindContext, left: Uint8Ar
|
|
|
45
45
|
* Exported for callers (synonym junctions) that hold one side FIXED across
|
|
46
46
|
* several calls and so compute its seeds once instead of per call. */
|
|
47
47
|
export declare function junctionSeeds(ctx: MindContext, b: Uint8Array): number[];
|
|
48
|
-
/**
|
|
49
|
-
* parent pages, container pages), keyed by the
|
|
50
|
-
*
|
|
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
51
|
* heavily (pair sides repeat across combos, and synonym walks revisit the
|
|
52
52
|
* same neighbourhoods); the store is read-only while a response is in flight,
|
|
53
53
|
* so every one of these reads is a pure function of the id — repeats cost a
|
|
@@ -63,6 +63,7 @@ export interface WalkCache {
|
|
|
63
63
|
containers: Map<number, number[]>;
|
|
64
64
|
}
|
|
65
65
|
export declare function walkCache(ctx: MindContext): WalkCache | null;
|
|
66
|
+
export declare function invalidateJunctionCache(ctx: MindContext): void;
|
|
66
67
|
export declare function cachedRead(ctx: MindContext, cache: WalkCache | null, id: number, cap: number): Uint8Array;
|
|
67
68
|
/** Tier 1 body, parameterised on already-resolved seed lists so a caller
|
|
68
69
|
* holding one side FIXED across several calls (synonym junctions) pays for
|
|
@@ -131,4 +132,6 @@ export declare function junctionContainers(ctx: MindContext, left: Uint8Array, r
|
|
|
131
132
|
* cost is bounded at √N·W pops total regardless of how many siblings are
|
|
132
133
|
* tried. A sibling whose bytes exceed `maxInterior` is skipped (it
|
|
133
134
|
* cannot be junction-sized). */
|
|
134
|
-
export declare function junctionSynonyms(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxInterior: number, unordered?: boolean, sides?: JunctionSynonymSides
|
|
135
|
+
export declare function junctionSynonyms(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxInterior: number, unordered?: boolean, sides?: JunctionSynonymSides, sharedBudget?: {
|
|
136
|
+
n: number;
|
|
137
|
+
}): Promise<SynonymJunction[]>;
|