@hviana/sema 0.2.9 → 0.4.0
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/AGENTS.md +71 -5
- package/dist/src/derive/src/deduction.d.ts +12 -1
- package/dist/src/derive/src/deduction.js +5 -1
- package/dist/src/derive/src/index.d.ts +1 -0
- package/dist/src/geometry.d.ts +3 -30
- package/dist/src/geometry.js +330 -82
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/meter.d.ts +171 -0
- package/dist/src/meter.js +269 -0
- package/dist/src/mind/attention.d.ts +5 -4
- package/dist/src/mind/attention.js +254 -23
- package/dist/src/mind/bridge.d.ts +10 -1
- package/dist/src/mind/bridge.js +179 -10
- package/dist/src/mind/canonical.d.ts +6 -1
- package/dist/src/mind/canonical.js +6 -1
- package/dist/src/mind/graph-search.d.ts +9 -0
- package/dist/src/mind/graph-search.js +59 -19
- package/dist/src/mind/index.d.ts +2 -0
- package/dist/src/mind/junction.d.ts +10 -0
- package/dist/src/mind/junction.js +14 -0
- package/dist/src/mind/learning.d.ts +32 -4
- package/dist/src/mind/learning.js +26 -4
- package/dist/src/mind/match.d.ts +40 -0
- package/dist/src/mind/match.js +125 -1
- package/dist/src/mind/mechanisms/cast.js +63 -6
- package/dist/src/mind/mechanisms/extraction.d.ts +0 -34
- package/dist/src/mind/mechanisms/extraction.js +1 -88
- package/dist/src/mind/mechanisms/recall.d.ts +3 -0
- package/dist/src/mind/mechanisms/recall.js +77 -14
- package/dist/src/mind/mind.d.ts +59 -5
- package/dist/src/mind/mind.js +115 -93
- package/dist/src/mind/pipeline-mechanism.d.ts +33 -3
- package/dist/src/mind/pipeline-mechanism.js +179 -10
- package/dist/src/mind/pipeline.d.ts +29 -0
- package/dist/src/mind/pipeline.js +79 -21
- package/dist/src/mind/primitives.d.ts +11 -15
- package/dist/src/mind/primitives.js +47 -28
- package/dist/src/mind/reasoning.d.ts +7 -1
- package/dist/src/mind/reasoning.js +40 -8
- package/dist/src/mind/recognition.js +93 -20
- package/dist/src/mind/traverse.d.ts +11 -0
- package/dist/src/mind/traverse.js +88 -7
- package/dist/src/mind/types.d.ts +39 -5
- package/dist/src/store.d.ts +15 -0
- package/dist/src/store.js +91 -6
- package/package.json +1 -1
- package/src/derive/src/deduction.ts +15 -0
- package/src/derive/src/index.ts +1 -0
- package/src/geometry.ts +350 -122
- package/src/index.ts +1 -0
- package/src/meter.ts +333 -0
- package/src/mind/attention.ts +276 -31
- package/src/mind/bridge.ts +187 -10
- package/src/mind/canonical.ts +6 -1
- package/src/mind/graph-search.ts +60 -21
- package/src/mind/index.ts +6 -0
- package/src/mind/junction.ts +12 -0
- package/src/mind/learning.ts +46 -5
- package/src/mind/match.ts +146 -1
- package/src/mind/mechanisms/cast.ts +62 -6
- package/src/mind/mechanisms/extraction.ts +2 -103
- package/src/mind/mechanisms/recall.ts +84 -17
- package/src/mind/mind.ts +144 -99
- package/src/mind/pipeline-mechanism.ts +203 -13
- package/src/mind/pipeline.ts +144 -36
- package/src/mind/primitives.ts +49 -33
- package/src/mind/reasoning.ts +39 -7
- package/src/mind/recognition.ts +89 -19
- package/src/mind/traverse.ts +89 -8
- package/src/mind/types.ts +39 -5
- package/src/store.ts +75 -6
- package/test/14-scaling.test.mjs +17 -7
- package/test/31-audit.test.mjs +4 -1
- package/test/33-multi-candidate.test.mjs +13 -1
- package/test/36-already-answered-fusion.test.mjs +10 -3
- package/test/46-recognise-multibyte-edge.test.mjs +3 -3
- package/test/53-cross-region-probe-instrumentation.test.mjs +36 -6
- package/test/54-evidence-k-instrumentation.test.mjs +175 -0
- package/test/55-cost-meter.test.mjs +284 -0
- package/test/56-bridge-identity-admission.test.mjs +209 -0
- package/test/57-fusion-order.test.mjs +104 -0
- package/test/58-subquantum-sites.test.mjs +112 -0
- package/test/59-fold-invariance.test.mjs +226 -0
package/src/mind/match.ts
CHANGED
|
@@ -31,7 +31,7 @@ import { conceptThreshold, identityBar, significanceBar } from "../geometry.js";
|
|
|
31
31
|
import { indexOf } from "../bytes.js";
|
|
32
32
|
import type { MindContext } from "./types.js";
|
|
33
33
|
import { leafIdRun } from "./canonical.js";
|
|
34
|
-
import { gistOf, read, resolve } from "./primitives.js";
|
|
34
|
+
import { foldTree, gistOf, perceive, read, resolve } from "./primitives.js";
|
|
35
35
|
import {
|
|
36
36
|
argmaxCosine,
|
|
37
37
|
chooseAmong,
|
|
@@ -119,6 +119,25 @@ export function alignRuns(
|
|
|
119
119
|
query: Uint8Array,
|
|
120
120
|
ct: Uint8Array,
|
|
121
121
|
): Array<{ qs: number; qe: number; cs: number }> {
|
|
122
|
+
if (ctx.meter) {
|
|
123
|
+
ctx.meter.alignments++;
|
|
124
|
+
// The alignment family's honest unit: the seed index is O(|query|) but
|
|
125
|
+
// the run extension is O(|query|·|ct|) in the worst case, and a weave
|
|
126
|
+
// that starts scanning conversation-length contexts shows up HERE long
|
|
127
|
+
// before it shows up in a call count.
|
|
128
|
+
ctx.meter.alignCells += query.length * ct.length;
|
|
129
|
+
}
|
|
130
|
+
// MEASURED AND REFUTED — seeding at the write side's unit floor W−1 instead
|
|
131
|
+
// of W. `canonicalWindows` interns both lengths precisely so a form
|
|
132
|
+
// straddling a group boundary is reachable from either cut, and the runs
|
|
133
|
+
// found here are exactly such remnants: test/29 C1's query shares only `ce `
|
|
134
|
+
// with `Ice is cold` — three bytes, never seeded at W, so that structure
|
|
135
|
+
// enters the weave carrying nothing but the scaffolding run every exemplar
|
|
136
|
+
// shares. But this is a byte MATCHER between two streams, not an index, and
|
|
137
|
+
// at W−1 the corpus is dense with spurious 3-byte agreements: the extra runs
|
|
138
|
+
// reshuffle which point claims which span, and test/29 A2 loses its analog.
|
|
139
|
+
// C1 does not pass either way. The store's unit floor does not transfer to
|
|
140
|
+
// the aligner's seed length.
|
|
122
141
|
const quantum = Math.min(ctx.space.maxGroup, ct.length);
|
|
123
142
|
if (quantum < 1 || query.length < quantum) return [];
|
|
124
143
|
const gram = (b: Uint8Array, at: number): string => {
|
|
@@ -167,6 +186,13 @@ export function alignRuns(
|
|
|
167
186
|
* kind, so the substitution/redirection schemas work unchanged on conceptual
|
|
168
187
|
* alignment. */
|
|
169
188
|
export interface GradedRun {
|
|
189
|
+
/** True for a run the CLIMB proposed rather than the byte matcher found — see
|
|
190
|
+
* {@link computeWeave}'s phase 2. Provenance only: a proposed run reaches
|
|
191
|
+
* CAST already gated (dominant literal agreement, not frame, and over bytes
|
|
192
|
+
* no literal run claimed), so every consumer reads it exactly like any other
|
|
193
|
+
* run. Filtering seat-shaped reads by this flag was tried and is NOT
|
|
194
|
+
* needed — the gates, not the flag, are what make it safe. */
|
|
195
|
+
proposed?: boolean;
|
|
170
196
|
qs: number;
|
|
171
197
|
qe: number;
|
|
172
198
|
cs: number;
|
|
@@ -542,3 +568,122 @@ export async function project(
|
|
|
542
568
|
if (fc) return fc;
|
|
543
569
|
return reverseContext(ctx, id, guide);
|
|
544
570
|
}
|
|
571
|
+
|
|
572
|
+
// ── The span-shape family ───────────────────────────────────────────────────
|
|
573
|
+
//
|
|
574
|
+
// "Is this answer drawn from this context?" has TWO formally distinct
|
|
575
|
+
// readings, and the pair plus the anchor classifier built on them are SHARED
|
|
576
|
+
// machinery — extraction proposes span-shaped exemplars with them, the
|
|
577
|
+
// shared `Precomputed.spanShapedOf` container computes them, and fusion
|
|
578
|
+
// (reasoning.ts) gates on the strict one. They lived inside
|
|
579
|
+
// mechanisms/extraction.ts, so `pipeline-mechanism.ts` and `reasoning.ts`
|
|
580
|
+
// both had to import back OUT of a specific mechanism — an inversion the
|
|
581
|
+
// mechanism market forbids (AGENTS §2.6: the shared contract may not depend
|
|
582
|
+
// on any one mechanism; §2.5: a shared matcher belongs to this family, never
|
|
583
|
+
// to a mechanism's private helpers). Deleting extraction must not break the
|
|
584
|
+
// shared container, so they live here.
|
|
585
|
+
//
|
|
586
|
+
// • isSpanShaped — the OPEN reading (sparse in-order embedding).
|
|
587
|
+
// • containsSpan — the STRICT reading (contiguous run or resolved node).
|
|
588
|
+
// • skillExemplar — classify one anchor into (context, answer) using them.
|
|
589
|
+
//
|
|
590
|
+
// The two readings are NOT interchangeable; AGENTS §2.5 pins the distinction
|
|
591
|
+
// and each function's own doc states what breaks if it is substituted.
|
|
592
|
+
|
|
593
|
+
/** Check whether an anchor is a span-shaped skill exemplar: it represents a
|
|
594
|
+
* fact whose context and answer together form a span-in-context pattern.
|
|
595
|
+
* If the anchor has a nextOf continuation, that is the answer and the anchor
|
|
596
|
+
* itself is the context. Otherwise the anchor's prevOf parents provide
|
|
597
|
+
* candidate contexts, and the longest one whose span is span-shaped wins. */
|
|
598
|
+
export async function skillExemplar(
|
|
599
|
+
ctx: MindContext,
|
|
600
|
+
anchor: number,
|
|
601
|
+
guide?: Vec | null,
|
|
602
|
+
): Promise<{ contextBytes: Uint8Array; answerBytes: Uint8Array } | null> {
|
|
603
|
+
if (ctx.store.hasNext(anchor)) {
|
|
604
|
+
const contextBytes = read(ctx, anchor);
|
|
605
|
+
const answerBytes = await follow(ctx, anchor, guide);
|
|
606
|
+
if (
|
|
607
|
+
answerBytes !== null && isSpanShaped(ctx, contextBytes, answerBytes)
|
|
608
|
+
) {
|
|
609
|
+
return { contextBytes, answerBytes };
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
const answerBytes = read(ctx, anchor);
|
|
614
|
+
// Candidate contexts, capped at the hub bound (a common answer's reverse
|
|
615
|
+
// fan-in is corpus-sized).
|
|
616
|
+
const capped = ctx.store.prevFirst(anchor, hubBound(ctx));
|
|
617
|
+
const spanShaped: Array<{ id: number; bytes: Uint8Array }> = [];
|
|
618
|
+
for (const p of capped) {
|
|
619
|
+
const ctxB = read(ctx, p);
|
|
620
|
+
if (ctxB.length > 0 && isSpanShaped(ctx, ctxB, answerBytes)) {
|
|
621
|
+
spanShaped.push({ id: p, bytes: ctxB });
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (spanShaped.length === 0) return null;
|
|
625
|
+
// Among span-shaped contexts, the longest wins (the smallest spanning frame
|
|
626
|
+
// heuristic's dual: more frame to locate in the query); the query gist,
|
|
627
|
+
// when given, breaks LENGTH TIES via chooseAmong — the same reverse-regime
|
|
628
|
+
// disambiguator every context pick uses, whose gist cache spares the
|
|
629
|
+
// re-fold this block once paid per tied candidate. Same strict first-seen
|
|
630
|
+
// tie-break as the hand loop it replaces.
|
|
631
|
+
const maxLen = Math.max(...spanShaped.map((s) => s.bytes.length));
|
|
632
|
+
const longest = spanShaped.filter((s) => s.bytes.length === maxLen);
|
|
633
|
+
let contextBytes = longest[0].bytes;
|
|
634
|
+
if (guide && longest.length > 1) {
|
|
635
|
+
const pick = chooseAmong(ctx, longest.map((s) => s.id), guide).id;
|
|
636
|
+
contextBytes = longest.find((s) => s.id === pick)!.bytes;
|
|
637
|
+
}
|
|
638
|
+
return { contextBytes, answerBytes };
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** Whether the answer is a SPARSE subsequence of the context (bytes in
|
|
642
|
+
* order, arbitrary gaps) — the OPEN span-shape reading (see the section
|
|
643
|
+
* note above). This is what lets extraction validate a MULTI-PIECE
|
|
644
|
+
* exemplar whose answer is stitched from several context runs — but it is
|
|
645
|
+
* deliberately permissive, so it must never be used as evidence that one
|
|
646
|
+
* span was "drawn from" another (see {@link containsSpan} for that).
|
|
647
|
+
*
|
|
648
|
+
* There is deliberately NO containsSpan pre-check here: strict containment
|
|
649
|
+
* IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
|
|
650
|
+
* whose content-addressed identity means its bytes occur contiguously — is
|
|
651
|
+
* an in-order embedding with zero gaps), so the scan below decides alone,
|
|
652
|
+
* with the same truth value. The old pre-check re-perceived the context
|
|
653
|
+
* (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
|
|
654
|
+
* pure cost, no discrimination. */
|
|
655
|
+
export function isSpanShaped(
|
|
656
|
+
_ctx: MindContext,
|
|
657
|
+
context: Uint8Array,
|
|
658
|
+
answer: Uint8Array,
|
|
659
|
+
): boolean {
|
|
660
|
+
let ai = 0;
|
|
661
|
+
for (let ci = 0; ci < context.length && ai < answer.length; ci++) {
|
|
662
|
+
if (context[ci] === answer[ai]) ai++;
|
|
663
|
+
}
|
|
664
|
+
return ai === answer.length;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/** STRICT containment: the answer's resolved node appears in the context's
|
|
668
|
+
* folded tree, or the answer occurs as one CONTIGUOUS byte run of the
|
|
669
|
+
* context. This is real evidence the answer was drawn from the context.
|
|
670
|
+
* Fusion gates on this — the sparse-subsequence reading of
|
|
671
|
+
* {@link isSpanShaped} is trivially satisfied by short answers over long
|
|
672
|
+
* queries ("cold" is a gap-tolerant subsequence of most sentences holding
|
|
673
|
+
* c…o…l…d in order), and gating fusion on it silently starved multi-topic
|
|
674
|
+
* queries of their further points of attention. */
|
|
675
|
+
export function containsSpan(
|
|
676
|
+
ctx: MindContext,
|
|
677
|
+
context: Uint8Array,
|
|
678
|
+
answer: Uint8Array,
|
|
679
|
+
): boolean {
|
|
680
|
+
const ansId = resolve(ctx, answer);
|
|
681
|
+
if (ansId !== null) {
|
|
682
|
+
let found = false;
|
|
683
|
+
foldTree(ctx, perceive(ctx, context), 0, (_n, _s, _e, node) => {
|
|
684
|
+
if (node === ansId) found = true;
|
|
685
|
+
});
|
|
686
|
+
if (found) return true;
|
|
687
|
+
}
|
|
688
|
+
return indexOf(context, answer, 0) >= 0;
|
|
689
|
+
}
|
|
@@ -351,14 +351,27 @@ export async function counterfactualTransfer(
|
|
|
351
351
|
? s.ctx.subarray(0, r.cs + (r.qe - r.qs))
|
|
352
352
|
: query.subarray(r.qs, r.qe);
|
|
353
353
|
};
|
|
354
|
+
// The subject is the closest structure whose FILLER RUN precedes the seat.
|
|
355
|
+
// The gate is on `runs[0]` — the run `fillerOf` actually reads — not on the
|
|
356
|
+
// point's LAST run: requiring the subject's whole alignment to end before
|
|
357
|
+
// the seat disqualifies any structure the query mentions on BOTH sides of
|
|
358
|
+
// it, which is the shape CAST exists for. Measured on
|
|
359
|
+
// `steel is frigid so steel is ???` against `steel is hard so steel is
|
|
360
|
+
// strong` (runs "steel is " at 0-9 and "d so steel is " at 14-28) and
|
|
361
|
+
// `water is frigid so water is freezing` (seat "frigi" at 9-14): the
|
|
362
|
+
// subject's filler run sits squarely before the seat, but its second run —
|
|
363
|
+
// the recurrence AFTER it, the very thing that makes the sentence an
|
|
364
|
+
// analogy — pushed lastRun past the seat and no substitution fired at all.
|
|
365
|
+
// The ordering key follows the gate to the same run, so "closest preceding"
|
|
366
|
+
// still means closest by the evidence actually used.
|
|
354
367
|
const beforeOf = (p: Point, r: GradedRun): Point | undefined =>
|
|
355
368
|
argmaxBy(
|
|
356
369
|
points.filter((s) =>
|
|
357
|
-
s !== p &&
|
|
370
|
+
s !== p && s.runs[0].qe <= r.qs &&
|
|
358
371
|
s.runs[0].cs < quantum &&
|
|
359
372
|
usable(s.runs[0].qs, s.runs[0].qe)
|
|
360
373
|
),
|
|
361
|
-
(s) =>
|
|
374
|
+
(s) => s.runs[0].qs,
|
|
362
375
|
-Infinity,
|
|
363
376
|
true,
|
|
364
377
|
)?.item;
|
|
@@ -371,6 +384,18 @@ export async function counterfactualTransfer(
|
|
|
371
384
|
const before = beforeOf(p, r);
|
|
372
385
|
if (before === undefined) return null;
|
|
373
386
|
if (r.cs > fillerOf(before).length + quantum) return null;
|
|
387
|
+
// SUBSTITUTION MUST ACTUALLY DISPLACE. The schema's premise is that the
|
|
388
|
+
// displaced structure's seat is held by something ELSE, which the
|
|
389
|
+
// subject then replaces. When the subject's filler already occurs in
|
|
390
|
+
// that structure, there is nothing to displace — the "transfer" restates
|
|
391
|
+
// the structure with its own occupant put back, and the answer is a
|
|
392
|
+
// tautology. Measured on `Michelangelo is to sculpture as who is to
|
|
393
|
+
// literature?`: the weave aligned the concept `Michelangelo` and the
|
|
394
|
+
// exemplar `The David was sculpted by Michelangelo.`, and substitution
|
|
395
|
+
// produced `Michelangelo sculpted by Michelangelo.` — then outbid every
|
|
396
|
+
// honest candidate with it (test/29 A2). Byte containment, the same
|
|
397
|
+
// primitive the self-evidence and contradiction guards use.
|
|
398
|
+
if (indexOf(p.ctx, fillerOf(before), 0) >= 0) return null;
|
|
374
399
|
return { p, before, depth: p.ctx.length - r.cs };
|
|
375
400
|
})
|
|
376
401
|
.filter((c): c is NonNullable<typeof c> => c !== null);
|
|
@@ -479,13 +504,27 @@ export async function counterfactualTransfer(
|
|
|
479
504
|
* schema accounts them. */
|
|
480
505
|
src: Point;
|
|
481
506
|
}
|
|
507
|
+
// QUERY-SCALE — "this learnt context is the same size as the question", the
|
|
508
|
+
// bound comparison holds its dominant and its analogs to. A byte-exact
|
|
509
|
+
// `<= query.length` made that judgement turn on a difference the
|
|
510
|
+
// architecture cannot perceive: on `The Weeping Woman was painted by Pablo
|
|
511
|
+
// Picasso.` (47 bytes) the weave's dominant was `The Night Watch was painted
|
|
512
|
+
// by Rembrandt van Rijn.` (50) — three bytes over, so comparison refused
|
|
513
|
+
// outright, while the interchangeable `The Mona Lisa was painted by Leonardo
|
|
514
|
+
// da Vinci.` (47) would have passed. WHICH exemplar becomes dominant is
|
|
515
|
+
// settled by run-claiming order among equals, so a 3-byte difference was
|
|
516
|
+
// deciding whether the schema fires at all (test/33 1b). W is the smallest
|
|
517
|
+
// distinction perception can make — the same quantum countClusters separates
|
|
518
|
+
// neighbourhoods by — so a context within one quantum of the query's length
|
|
519
|
+
// carries no independently perceivable unit beyond it and is the same scale.
|
|
520
|
+
const queryScale = (n: number): boolean => n - query.length < quantum;
|
|
482
521
|
const analogs: AnalogCandidate[] = [];
|
|
483
522
|
for (const p of points) {
|
|
484
523
|
if (p === dominant) continue;
|
|
485
524
|
// Push the point's own anchor only when its context fits within
|
|
486
525
|
// the query (the seat sentence must not dominate the comparison).
|
|
487
526
|
if (
|
|
488
|
-
p.ctx.length
|
|
527
|
+
queryScale(p.ctx.length) &&
|
|
489
528
|
indexOf(dominant.ctx, p.ctx, 0) < 0 &&
|
|
490
529
|
indexOf(p.ctx, dominant.ctx, 0) < 0 &&
|
|
491
530
|
indexOf(query, p.ctx, 0) < 0
|
|
@@ -504,7 +543,7 @@ export async function counterfactualTransfer(
|
|
|
504
543
|
for (const nid of ctx.store.nextFirst(p.anchor, hubBound(ctx))) {
|
|
505
544
|
const nctx = read(ctx, nid);
|
|
506
545
|
if (
|
|
507
|
-
nctx.length
|
|
546
|
+
!queryScale(nctx.length) ||
|
|
508
547
|
indexOf(dominant.ctx, nctx, 0) >= 0 ||
|
|
509
548
|
indexOf(nctx, dominant.ctx, 0) >= 0 ||
|
|
510
549
|
indexOf(query, nctx, 0) >= 0
|
|
@@ -512,6 +551,23 @@ export async function counterfactualTransfer(
|
|
|
512
551
|
analogs.push({ anchor: nid, point: null, src: p });
|
|
513
552
|
}
|
|
514
553
|
}
|
|
554
|
+
// MEASURED AND REFUTED — proposing analogs from the dominant's halo when the
|
|
555
|
+
// query-local generator finds none. Both loops above are query-local (an
|
|
556
|
+
// aligned point, or one forward hop off one), while the gate that judges
|
|
557
|
+
// candidates — analogyStrength's halo tier — is cross-domain by construction
|
|
558
|
+
// and is licence enough on its own (`bestHalo` exempts it from the naming and
|
|
559
|
+
// trusted-root bars). So the gate reads as strictly more capable than the
|
|
560
|
+
// generator, and closing that asymmetry looks like the fix for test/29 A2
|
|
561
|
+
// (`Michelangelo is to sculpture as who is to literature?`, whose only stored
|
|
562
|
+
// content is `Michelangelo`: the weave aligns the concept and its own
|
|
563
|
+
// exemplar, each contains the other, so every candidate is excluded and
|
|
564
|
+
// comparison checks zero).
|
|
565
|
+
//
|
|
566
|
+
// It proposes nothing. Measured on A2's own 13-pair corpus: `Michelangelo`
|
|
567
|
+
// HAS a halo, and `haloSiblings` returns not one sibling above
|
|
568
|
+
// significanceBar — the distributional company that would make Shakespeare a
|
|
569
|
+
// cross-domain analog was never trained. The asymmetry is real but it is not
|
|
570
|
+
// what stops A2; the corpus is.
|
|
515
571
|
let bestAnalog: AnalogCandidate | null = null;
|
|
516
572
|
let bestSim = 0;
|
|
517
573
|
let bestHalo = false;
|
|
@@ -756,7 +812,7 @@ export async function counterfactualTransfer(
|
|
|
756
812
|
bestAnalog !== null &&
|
|
757
813
|
(bestHalo || analogNamed || rootTrusted) &&
|
|
758
814
|
!cmpDismisses &&
|
|
759
|
-
dominant.ctx.length
|
|
815
|
+
queryScale(dominant.ctx.length) &&
|
|
760
816
|
roots.length <= 1 &&
|
|
761
817
|
!dominates(cmpMaxGap, query.length) &&
|
|
762
818
|
cmpMaxGap < dominant.ctx.length
|
|
@@ -802,7 +858,7 @@ export async function counterfactualTransfer(
|
|
|
802
858
|
);
|
|
803
859
|
} else if (
|
|
804
860
|
bestAnalog !== null &&
|
|
805
|
-
dominant.ctx.length
|
|
861
|
+
queryScale(dominant.ctx.length) &&
|
|
806
862
|
roots.length <= 1
|
|
807
863
|
) {
|
|
808
864
|
ctx.trace?.step(
|
|
@@ -7,12 +7,9 @@
|
|
|
7
7
|
// (or one of its pieces is), and the context is the smallest spanning frame
|
|
8
8
|
// that contains it.
|
|
9
9
|
|
|
10
|
-
import type { Vec } from "../../vec.js";
|
|
11
10
|
import type { MindContext } from "../types.js";
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import { follow, locate } from "../match.js";
|
|
15
|
-
import { chooseAmong, hubBound } from "../traverse.js";
|
|
11
|
+
import { read } from "../primitives.js";
|
|
12
|
+
import { follow, isSpanShaped, locate, skillExemplar } from "../match.js";
|
|
16
13
|
import { concatBytes, indexOf } from "../../bytes.js";
|
|
17
14
|
import { decodeText, unexplainedLabel } from "../rationale.js";
|
|
18
15
|
import type {
|
|
@@ -358,104 +355,6 @@ export function answerRunsInContext(
|
|
|
358
355
|
return merged.length > 0 ? merged : null;
|
|
359
356
|
}
|
|
360
357
|
|
|
361
|
-
/** Check whether an anchor is a span-shaped skill exemplar: it represents a
|
|
362
|
-
* fact whose context and answer together form a span-in-context pattern.
|
|
363
|
-
* If the anchor has a nextOf continuation, that is the answer and the anchor
|
|
364
|
-
* itself is the context. Otherwise the anchor's prevOf parents provide
|
|
365
|
-
* candidate contexts, and the longest one whose span is span-shaped wins. */
|
|
366
|
-
export async function skillExemplar(
|
|
367
|
-
ctx: MindContext,
|
|
368
|
-
anchor: number,
|
|
369
|
-
guide?: Vec | null,
|
|
370
|
-
): Promise<{ contextBytes: Uint8Array; answerBytes: Uint8Array } | null> {
|
|
371
|
-
if (ctx.store.hasNext(anchor)) {
|
|
372
|
-
const contextBytes = read(ctx, anchor);
|
|
373
|
-
const answerBytes = await follow(ctx, anchor, guide);
|
|
374
|
-
if (
|
|
375
|
-
answerBytes !== null && isSpanShaped(ctx, contextBytes, answerBytes)
|
|
376
|
-
) {
|
|
377
|
-
return { contextBytes, answerBytes };
|
|
378
|
-
}
|
|
379
|
-
return null;
|
|
380
|
-
}
|
|
381
|
-
const answerBytes = read(ctx, anchor);
|
|
382
|
-
// Candidate contexts, capped at the hub bound (a common answer's reverse
|
|
383
|
-
// fan-in is corpus-sized).
|
|
384
|
-
const capped = ctx.store.prevFirst(anchor, hubBound(ctx));
|
|
385
|
-
const spanShaped: Array<{ id: number; bytes: Uint8Array }> = [];
|
|
386
|
-
for (const p of capped) {
|
|
387
|
-
const ctxB = read(ctx, p);
|
|
388
|
-
if (ctxB.length > 0 && isSpanShaped(ctx, ctxB, answerBytes)) {
|
|
389
|
-
spanShaped.push({ id: p, bytes: ctxB });
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
if (spanShaped.length === 0) return null;
|
|
393
|
-
// Among span-shaped contexts, the longest wins (the smallest spanning frame
|
|
394
|
-
// heuristic's dual: more frame to locate in the query); the query gist,
|
|
395
|
-
// when given, breaks LENGTH TIES via chooseAmong — the same reverse-regime
|
|
396
|
-
// disambiguator every context pick uses, whose gist cache spares the
|
|
397
|
-
// re-fold this block once paid per tied candidate. Same strict first-seen
|
|
398
|
-
// tie-break as the hand loop it replaces.
|
|
399
|
-
const maxLen = Math.max(...spanShaped.map((s) => s.bytes.length));
|
|
400
|
-
const longest = spanShaped.filter((s) => s.bytes.length === maxLen);
|
|
401
|
-
let contextBytes = longest[0].bytes;
|
|
402
|
-
if (guide && longest.length > 1) {
|
|
403
|
-
const pick = chooseAmong(ctx, longest.map((s) => s.id), guide).id;
|
|
404
|
-
contextBytes = longest.find((s) => s.id === pick)!.bytes;
|
|
405
|
-
}
|
|
406
|
-
return { contextBytes, answerBytes };
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/** Whether the answer is a SPARSE subsequence of the context (bytes in
|
|
410
|
-
* order, arbitrary gaps) — the OPEN span-shape reading (see the section
|
|
411
|
-
* note above). This is what lets extraction validate a MULTI-PIECE
|
|
412
|
-
* exemplar whose answer is stitched from several context runs — but it is
|
|
413
|
-
* deliberately permissive, so it must never be used as evidence that one
|
|
414
|
-
* span was "drawn from" another (see {@link containsSpan} for that).
|
|
415
|
-
*
|
|
416
|
-
* There is deliberately NO containsSpan pre-check here: strict containment
|
|
417
|
-
* IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
|
|
418
|
-
* whose content-addressed identity means its bytes occur contiguously — is
|
|
419
|
-
* an in-order embedding with zero gaps), so the scan below decides alone,
|
|
420
|
-
* with the same truth value. The old pre-check re-perceived the context
|
|
421
|
-
* (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
|
|
422
|
-
* pure cost, no discrimination. */
|
|
423
|
-
export function isSpanShaped(
|
|
424
|
-
_ctx: MindContext,
|
|
425
|
-
context: Uint8Array,
|
|
426
|
-
answer: Uint8Array,
|
|
427
|
-
): boolean {
|
|
428
|
-
let ai = 0;
|
|
429
|
-
for (let ci = 0; ci < context.length && ai < answer.length; ci++) {
|
|
430
|
-
if (context[ci] === answer[ai]) ai++;
|
|
431
|
-
}
|
|
432
|
-
return ai === answer.length;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/** STRICT containment: the answer's resolved node appears in the context's
|
|
436
|
-
* folded tree, or the answer occurs as one CONTIGUOUS byte run of the
|
|
437
|
-
* context. This is real evidence the answer was drawn from the context.
|
|
438
|
-
* Fusion gates on this — the sparse-subsequence reading of
|
|
439
|
-
* {@link isSpanShaped} is trivially satisfied by short answers over long
|
|
440
|
-
* queries ("cold" is a gap-tolerant subsequence of most sentences holding
|
|
441
|
-
* c…o…l…d in order), and gating fusion on it silently starved multi-topic
|
|
442
|
-
* queries of their further points of attention. */
|
|
443
|
-
export function containsSpan(
|
|
444
|
-
ctx: MindContext,
|
|
445
|
-
context: Uint8Array,
|
|
446
|
-
answer: Uint8Array,
|
|
447
|
-
): boolean {
|
|
448
|
-
const ansId = resolve(ctx, answer);
|
|
449
|
-
if (ansId !== null) {
|
|
450
|
-
let found = false;
|
|
451
|
-
foldTree(ctx, perceive(ctx, context), 0, (_n, _s, _e, node) => {
|
|
452
|
-
if (node === ansId) found = true;
|
|
453
|
-
});
|
|
454
|
-
if (found) return true;
|
|
455
|
-
}
|
|
456
|
-
return indexOf(context, answer, 0) >= 0;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
358
|
// ── Pipeline mechanism ──────────────────────────────────────────────────────
|
|
460
359
|
|
|
461
360
|
export const extractionMechanism: PipelineMechanism = {
|
|
@@ -29,6 +29,9 @@ export interface RecallResult {
|
|
|
29
29
|
accounted: Array<[number, number]>;
|
|
30
30
|
moves: number;
|
|
31
31
|
unexplained: string;
|
|
32
|
+
/** See {@link import("../pipeline-mechanism.js").MechanismResult.complete}
|
|
33
|
+
* — set by the IDENTITY-bridge tier alone. */
|
|
34
|
+
complete?: boolean;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
/** Recall the answer by resonating the whole query against the content index. */
|
|
@@ -48,6 +51,7 @@ export async function recallByResonance(
|
|
|
48
51
|
accounted: Array<[number, number]>,
|
|
49
52
|
moves: number,
|
|
50
53
|
echoed = false,
|
|
54
|
+
complete = false,
|
|
51
55
|
): RecallResult | null => {
|
|
52
56
|
t?.done(
|
|
53
57
|
bytes === null
|
|
@@ -61,6 +65,7 @@ export async function recallByResonance(
|
|
|
61
65
|
accounted,
|
|
62
66
|
moves,
|
|
63
67
|
unexplained: unexplainedLabel(query, accounted),
|
|
68
|
+
...(complete ? { complete } : {}),
|
|
64
69
|
};
|
|
65
70
|
};
|
|
66
71
|
const k = pre.k;
|
|
@@ -329,14 +334,35 @@ export async function recallByResonance(
|
|
|
329
334
|
// natural, tuning-free ceiling (probe every cluster) for a call that
|
|
330
335
|
// is ALREADY refusal-path-only and must not miss a candidate hiding
|
|
331
336
|
// behind an unlucky structural distance.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
//
|
|
338
|
+
// MEASURED COST, AND WHY IT STAYS (17.9M vectors / 325K contexts):
|
|
339
|
+
// this one call is ~570 ms and ~45% of all inference time on a refusing
|
|
340
|
+
// query. The cost is entirely `exhaustive` (nprobe = every cluster),
|
|
341
|
+
// NOT the widened k — timed on that store: k=571 exhaustive 632 ms,
|
|
342
|
+
// k=24 exhaustive 536 ms, k=571 NON-exhaustive 12 ms. So narrowing k
|
|
343
|
+
// buys nothing and the 50x is the whole-index scan itself.
|
|
344
|
+
// It is load-bearing: over an 18-query battery the bridge produced a
|
|
345
|
+
// winner 4 times, and ALL FOUR winners came from this proposal channel
|
|
346
|
+
// — the anchor-climb channel won nothing on its own. Reordering the
|
|
347
|
+
// channels (climb first, resonate only on failure) would therefore pay
|
|
348
|
+
// the climb, fail, and pay this anyway. Do not weaken it without
|
|
349
|
+
// re-running that measurement.
|
|
350
|
+
// Handed to the bridge as a THUNK: this exhaustive whole-index probe is
|
|
351
|
+
// the most expensive single act on the refusal path, and the bridge's
|
|
352
|
+
// own cheap gates (query length, the O(|query|) stored-window anchor
|
|
353
|
+
// scan) can refuse without any proposal at all. See substitutionBridge.
|
|
354
|
+
const wideIds = async (): Promise<ReadonlyArray<number>> => {
|
|
355
|
+
const wide = k >= hubBound(ctx)
|
|
356
|
+
? whole
|
|
357
|
+
: ctx.meter
|
|
358
|
+
? await ctx.meter.time(
|
|
359
|
+
"recall.exhaustiveResonate",
|
|
360
|
+
() => ctx.store.resonate(queryGist, hubBound(ctx), true),
|
|
361
|
+
)
|
|
362
|
+
: await ctx.store.resonate(queryGist, hubBound(ctx), true);
|
|
363
|
+
return wide.map((h) => h.id);
|
|
364
|
+
};
|
|
365
|
+
const bridged = await substitutionBridge(ctx, query, wideIds);
|
|
340
366
|
if (bridged !== null) {
|
|
341
367
|
const g = await project(ctx, bridged.id, queryGist);
|
|
342
368
|
// A projection contained in a substituted candidate-side span is the
|
|
@@ -357,16 +383,56 @@ export async function recallByResonance(
|
|
|
357
383
|
) {
|
|
358
384
|
return ground(
|
|
359
385
|
g,
|
|
360
|
-
|
|
361
|
-
`up to
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
|
|
386
|
+
bridged.subs.length === 0
|
|
387
|
+
? `identity bridge — a trained context IS this query, up to ` +
|
|
388
|
+
`scaffolding the corpus itself treats as filler`
|
|
389
|
+
: `substitution bridge — a trained context accounts for the ` +
|
|
390
|
+
`query up to ${bridged.subs.length} corroborated ` +
|
|
391
|
+
`substitution(s)`,
|
|
392
|
+
// WHAT THIS GROUNDING EXPLAINS — the spans its alignment covers,
|
|
393
|
+
// matched AND substituted, for BOTH tiers.
|
|
394
|
+
//
|
|
395
|
+
// A corroborated substitution is not a gap in the explanation; it is
|
|
396
|
+
// an explanation the mechanism PAID for, one CONCEPT each in `moves`
|
|
397
|
+
// just below. Leaving its span unaccounted charges the same act
|
|
398
|
+
// twice — once as a move, once as PASS-per-unexplained-byte — and the
|
|
399
|
+
// second charge is far the larger, so a bridge that matched 28 of 29
|
|
400
|
+
// bytes declared the whole query unexplained and lost to any
|
|
401
|
+
// mechanism with a smaller honest claim. Measured on test/49's
|
|
402
|
+
// paraphrase: it FOUND the trained fact through two corroborated case
|
|
403
|
+
// substitutions and was outbid 29011 to 1012 by a CAST comparison
|
|
404
|
+
// that voiced the wrong country.
|
|
405
|
+
//
|
|
406
|
+
// This tier DID once report `[]` for the substituted case, against
|
|
407
|
+
// the observation that "pricing the aligned spans outweighed
|
|
408
|
+
// extraction's correct answer in the grounding decider". That is no
|
|
409
|
+
// longer so and the suite is the witness: with the fold's regions
|
|
410
|
+
// content-defined and the junction tiers no longer consuming each
|
|
411
|
+
// other's candidates, full accounting here passes every test that
|
|
412
|
+
// refutation was recorded for. Reporting only the LITERALLY matched
|
|
413
|
+
// spans (accounted minus the substituted ones) was also implemented
|
|
414
|
+
// and is a strictly worse reading of the same ladder — it still
|
|
415
|
+
// double-charges, just less.
|
|
416
|
+
//
|
|
417
|
+
// An IDENTITY bridge (zero substitutions) substituted nothing, so
|
|
418
|
+
// there is nothing to be humble about: every accounted byte is a
|
|
419
|
+
// LITERAL match against a trained form, and the query is that form
|
|
420
|
+
// up to scaffolding. Reporting `[]` for it was actively wrong in
|
|
421
|
+
// two ways — it priced a full explanation at PASS-per-byte so junk
|
|
422
|
+
// outweighed it, and, because the honest-remainder test in think()
|
|
423
|
+
// reads the same spans, it left the whole query "unaccounted" and
|
|
424
|
+
// forced the multi-topic fusion gate open. Observed live: the
|
|
425
|
+
// correct "What is the process of photosynthesis?" grounding was
|
|
426
|
+
// fused away into an unrelated "Hello! How can I assist you
|
|
427
|
+
// today?" point of attention.
|
|
428
|
+
[...bridged.accounted],
|
|
369
429
|
CONCEPT * bridged.subs.length + STEP,
|
|
430
|
+
false,
|
|
431
|
+
// COMPLETE only for the identity tier: the query IS this trained
|
|
432
|
+
// context, so `g` is that context's own continuation — the whole
|
|
433
|
+
// read-out. A SUBSTITUTED bridge makes no such claim (it stood a
|
|
434
|
+
// different word in the query's place), so it stays extendable.
|
|
435
|
+
bridged.subs.length === 0,
|
|
370
436
|
);
|
|
371
437
|
}
|
|
372
438
|
}
|
|
@@ -434,6 +500,7 @@ export const recallMechanism: PipelineMechanism = {
|
|
|
434
500
|
moves: r.moves,
|
|
435
501
|
unexplained: r.unexplained,
|
|
436
502
|
provenance: r.echoed ? "recall-echo" : "recall",
|
|
503
|
+
...(r.complete ? { complete: true } : {}),
|
|
437
504
|
}];
|
|
438
505
|
},
|
|
439
506
|
};
|