@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.
Files changed (45) hide show
  1. package/dist/src/ingest-cache.js +4 -0
  2. package/dist/src/meter.d.ts +5 -0
  3. package/dist/src/meter.js +5 -0
  4. package/dist/src/mind/attention.js +19 -2
  5. package/dist/src/mind/bridge.js +265 -56
  6. package/dist/src/mind/junction.d.ts +7 -4
  7. package/dist/src/mind/junction.js +16 -5
  8. package/dist/src/mind/match.d.ts +15 -0
  9. package/dist/src/mind/match.js +92 -2
  10. package/dist/src/mind/mechanisms/cast.js +12 -1
  11. package/dist/src/mind/mechanisms/confluence.js +31 -1
  12. package/dist/src/mind/mechanisms/cover.d.ts +1 -1
  13. package/dist/src/mind/mechanisms/cover.js +29 -5
  14. package/dist/src/mind/mechanisms/recall.js +17 -41
  15. package/dist/src/mind/mind.d.ts +7 -0
  16. package/dist/src/mind/mind.js +25 -2
  17. package/dist/src/mind/pipeline-mechanism.d.ts +2 -2
  18. package/dist/src/mind/pipeline-mechanism.js +87 -4
  19. package/dist/src/mind/pipeline.js +1 -1
  20. package/dist/src/mind/reasoning.js +19 -11
  21. package/dist/src/mind/recognition.js +41 -0
  22. package/dist/src/mind/resonance.js +0 -0
  23. package/dist/src/mind/traverse.d.ts +3 -1
  24. package/dist/src/mind/traverse.js +14 -13
  25. package/dist/src/mind/types.d.ts +10 -0
  26. package/package.json +1 -1
  27. package/src/ingest-cache.ts +4 -0
  28. package/src/meter.ts +5 -0
  29. package/src/mind/attention.ts +18 -1
  30. package/src/mind/bridge.ts +292 -54
  31. package/src/mind/junction.ts +21 -7
  32. package/src/mind/match.ts +92 -1
  33. package/src/mind/mechanisms/cast.ts +12 -0
  34. package/src/mind/mechanisms/confluence.ts +30 -1
  35. package/src/mind/mechanisms/cover.ts +36 -4
  36. package/src/mind/mechanisms/recall.ts +21 -44
  37. package/src/mind/mind.ts +39 -2
  38. package/src/mind/pipeline-mechanism.ts +86 -4
  39. package/src/mind/pipeline.ts +1 -1
  40. package/src/mind/reasoning.ts +15 -8
  41. package/src/mind/recognition.ts +40 -0
  42. package/src/mind/resonance.ts +0 -0
  43. package/src/mind/traverse.ts +17 -15
  44. package/src/mind/types.ts +10 -0
  45. package/test/49-natural-units-synonym-bridge.test.mjs +56 -15
@@ -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
  }
@@ -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]), undefined, true);
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;
@@ -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
- // • 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.
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 hubBound
45
- // candidate reads, and one O(|query|·|candidate|)-bounded alignment each —
46
- // all capped by existing derived bounds (W, chainReach, hubBound).
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 (anchors.length === 0)
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
- for (let o = from; o + W <= to; o++) {
363
- const ids = leafIdRun(ctx, bytes, o, o + W);
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 true;
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
- // Proposal channel carries its caller's own bound (recall's resonance
399
- // k), so it neither consumes the climb's hub budget (on a small corpus
400
- // √N is a handful and the proposals would crowd the climb out entirely)
401
- // nor lets per-candidate byte work grow past that bound. A proposal may
402
- // be a FLAT content twin whose continuation edge lives on the
403
- // fold-shaped deposit node with the same bytes the same twin split
404
- // canonResolve bridges by re-folding (primitives.ts) but the re-fold
405
- // (a full perceive of the candidate's bytes) is paid only for proposals
406
- // that could align at all: alignment can only seed at a picked anchor
407
- // window occurring literally in the candidate (measured: unconditional
408
- // re-folds multiplied the refusal-path latency several-fold).
409
- // FIRST TOUCH of the caller's proposals — past every gate that could have
410
- // refused without them (see substitutionBridge's doc).
411
- for (const sid of await proposed()) {
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 - proposedCount >= bound)
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 - proposedCount >= bound)
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
- const allBytes = new Map();
457
- for (const sid of candidates) {
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
- allBytes.set(sid, b);
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 allBytes.values()) {
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
- for (const sid of candidates) {
513
- const cBytes = allBytes.get(sid);
514
- if (cBytes === undefined)
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 only ever grows both
569
- // sides by IDENTICAL absorbed bytes, so it cannot fix an imbalance
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 = balanced ? reachCap - Math.max(uLen, cLen) : -1;
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
- if (cosine(perceive(ctx, u).v, perceive(ctx, cSpan).v) < bar) {
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
- /** Per-response cache of the identity walks' pure reads (capped bytes,
49
- * parent pages, container pages), keyed by the response lifecycle object
50
- * (ctx.climbMemo). One response issues many walks whose ancestries overlap
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): Promise<SynonymJunction[]>;
135
+ export declare function junctionSynonyms(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxInterior: number, unordered?: boolean, sides?: JunctionSynonymSides, sharedBudget?: {
136
+ n: number;
137
+ }): Promise<SynonymJunction[]>;