@hviana/sema 0.9.0 → 0.9.2
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 +7 -7
- package/dist/src/alu/src/index.d.ts +1 -1
- package/dist/src/alu/src/index.js +1 -1
- package/dist/src/alu/src/parser.js +2 -6
- package/dist/src/alu/src/resonance.d.ts +13 -0
- package/dist/src/alu/src/resonance.js +41 -0
- package/dist/src/alu/test/alu.test.js +39 -0
- package/dist/src/bytes.d.ts +6 -2
- package/dist/src/bytes.js +10 -4
- package/dist/src/canon.js +44 -0
- package/dist/src/geometry.d.ts +19 -1
- package/dist/src/geometry.js +125 -141
- package/dist/src/meter.d.ts +33 -0
- package/dist/src/meter.js +34 -1
- package/dist/src/mind/articulation.js +14 -1
- package/dist/src/mind/attention.d.ts +12 -0
- package/dist/src/mind/attention.js +44 -16
- package/dist/src/mind/bridge.js +3 -3
- package/dist/src/mind/derivation.d.ts +40 -0
- package/dist/src/mind/derivation.js +34 -0
- package/dist/src/mind/evidence.d.ts +24 -0
- package/dist/src/mind/evidence.js +90 -0
- package/dist/src/mind/graph-search.d.ts +89 -15
- package/dist/src/mind/graph-search.js +345 -174
- package/dist/src/mind/learning.js +1 -1
- package/dist/src/mind/mechanisms/cover.d.ts +19 -3
- package/dist/src/mind/mechanisms/cover.js +142 -61
- package/dist/src/mind/mechanisms/recall.js +10 -3
- package/dist/src/mind/mind.d.ts +6 -0
- package/dist/src/mind/mind.js +5 -2
- package/dist/src/mind/pipeline.d.ts +5 -1
- package/dist/src/mind/pipeline.js +220 -90
- package/dist/src/mind/primitives.d.ts +25 -5
- package/dist/src/mind/primitives.js +107 -44
- package/dist/src/mind/reasoning.d.ts +18 -4
- package/dist/src/mind/reasoning.js +487 -328
- package/dist/src/mind/recognition.js +29 -13
- package/dist/src/mind/resonance.js +1 -11
- package/dist/src/mind/traverse.d.ts +45 -5
- package/dist/src/mind/traverse.js +285 -8
- package/dist/src/mind/types.d.ts +16 -1
- package/dist/src/store-sqlite.d.ts +25 -0
- package/dist/src/store-sqlite.js +89 -1
- package/dist/src/store.d.ts +48 -4
- package/dist/src/store.js +86 -6
- package/docs/INDEX.md +20 -19
- package/docs/INVARIANTS.md +17 -16
- package/docs/architecture/bounded-reads.md +1 -1
- package/docs/architecture/caches.md +5 -4
- package/docs/architecture/closure.md +45 -5
- package/docs/architecture/cost-model.md +16 -0
- package/docs/architecture/evidence.md +113 -0
- package/docs/architecture/exact-vs-approximate.md +10 -9
- package/docs/architecture/factored-machinery.md +14 -13
- package/docs/architecture/fold-contract.md +51 -1
- package/docs/architecture/mechanism-market.md +21 -0
- package/docs/architecture/memoization.md +3 -3
- package/docs/architecture/meter.md +2 -1
- package/docs/architecture/saturation.md +12 -0
- package/docs/architecture/store.md +25 -2
- package/docs/failures/tempting-but-wrong.md +13 -2
- package/docs/harness/gates.md +12 -10
- package/docs/mechanisms/cover.md +23 -6
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alu/README.md +10 -2
- package/src/alu/src/index.ts +1 -0
- package/src/alu/src/parser.ts +6 -6
- package/src/alu/src/resonance.ts +42 -0
- package/src/alu/test/alu.test.ts +40 -0
- package/src/bytes.ts +13 -3
- package/src/canon.ts +40 -0
- package/src/geometry.ts +183 -154
- package/src/meter.ts +34 -1
- package/src/mind/articulation.ts +14 -2
- package/src/mind/attention.ts +47 -25
- package/src/mind/bridge.ts +3 -3
- package/src/mind/derivation.ts +77 -0
- package/src/mind/evidence.ts +107 -0
- package/src/mind/graph-search.ts +449 -221
- package/src/mind/learning.ts +1 -7
- package/src/mind/match.ts +1 -2
- package/src/mind/mechanisms/cast.ts +1 -2
- package/src/mind/mechanisms/cover.ts +207 -87
- package/src/mind/mechanisms/extraction.ts +1 -2
- package/src/mind/mechanisms/prefix-completion.ts +1 -1
- package/src/mind/mechanisms/recall.ts +17 -5
- package/src/mind/mechanisms/reference.ts +1 -1
- package/src/mind/mind.ts +9 -30
- package/src/mind/pipeline.ts +263 -104
- package/src/mind/primitives.ts +119 -43
- package/src/mind/reasoning.ts +611 -419
- package/src/mind/recognition.ts +24 -9
- package/src/mind/resonance.ts +2 -16
- package/src/mind/trace.ts +1 -1
- package/src/mind/traverse.ts +321 -8
- package/src/mind/types.ts +15 -11
- package/src/store-sqlite.ts +92 -1
- package/src/store.ts +113 -7
- package/test/105-derive-through-reports-its-refusal.test.mjs +8 -5
- package/test/106-the-join-fires.test.mjs +21 -0
- package/test/111-the-cover-assembly-is-counted.test.mjs +8 -5
- package/test/128-the-leads-somewhere-pair-agrees.test.mjs +18 -12
- package/test/136-the-two-named-limits.test.mjs +3 -2
- package/test/137-the-law-lives-once-and-below.test.mjs +21 -0
- package/test/148-exact-shortcuts-agree.test.mjs +188 -0
- package/test/149-the-closure-engine.test.mjs +138 -0
- package/test/150-the-join-is-output-sensitive.test.mjs +66 -0
- package/test/151-the-cover-pays-for-what-it-reaches.test.mjs +142 -0
- package/test/152-the-read-side-names-as-the-write-side.test.mjs +146 -0
- package/test/153-a-cheaper-bound-is-looked-at-first.test.mjs +155 -0
- package/test/154-the-question-names-the-step.test.mjs +281 -0
- package/test/24-generalization.test.mjs +32 -0
- package/test/36-bloom.test.mjs +53 -0
- package/test/37-cluster-dispersion-fusion.test.mjs +75 -0
- package/test/48-recognise-turn-connective.test.mjs +3 -2
- package/test/55-cost-meter.test.mjs +4 -4
- package/test/90-connector-read-cap.test.mjs +7 -7
package/src/mind/recognition.ts
CHANGED
|
@@ -11,13 +11,13 @@ import {
|
|
|
11
11
|
canonResolve,
|
|
12
12
|
foldTree,
|
|
13
13
|
gistOf,
|
|
14
|
-
latin1Key,
|
|
15
14
|
perceive,
|
|
16
15
|
resolve,
|
|
17
16
|
} from "./primitives.js";
|
|
18
17
|
import { atomIsHub, bearsEdge, corpusN, leadsSomewhere } from "./traverse.js";
|
|
19
18
|
import { chainReach, leafIdAt, leafIdRun } from "./canonical.js";
|
|
20
19
|
import { canonHash } from "../canon.js";
|
|
20
|
+
import { latin1 } from "../bytes.js";
|
|
21
21
|
import { isChunk, type Sema } from "../sema.js";
|
|
22
22
|
import type { Leaf, Site } from "./graph-search.js";
|
|
23
23
|
|
|
@@ -91,7 +91,7 @@ export function recognise(
|
|
|
91
91
|
// not silent), so it is emitted here directly rather than only inside
|
|
92
92
|
// recogniseImpl.
|
|
93
93
|
if (ctx.recogniseMemo) {
|
|
94
|
-
const key =
|
|
94
|
+
const key = latin1(bytes);
|
|
95
95
|
const hit = ctx.recogniseMemo.get(key);
|
|
96
96
|
if (hit !== undefined) {
|
|
97
97
|
if (ctx.meter) ctx.meter.recogniseHits++;
|
|
@@ -495,8 +495,20 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
|
495
495
|
// encoding is the identity, so the span's bytes ARE the branch key.
|
|
496
496
|
// `subarray` is a view — this allocates nothing per probe, and the
|
|
497
497
|
// bloom filter answers the misses without touching the database.
|
|
498
|
+
//
|
|
499
|
+
// Through ONE span prober (Store.flatSpans), because hashing the key was
|
|
500
|
+
// itself the quadratic: every probe hashed its span from the start, and
|
|
501
|
+
// the interior pass below sweeps up to `reach` ends past each endpoint —
|
|
502
|
+
// O(n · reach²) bytes hashed. Measured on the 31.7M-node store, one
|
|
503
|
+
// composition-regime response (#97 of the battery) probed 2,079,400
|
|
504
|
+
// spans and hashed 251,660,406 bytes for them. The prober extends each
|
|
505
|
+
// start's hash instead, so the same probes, answered identically, cost
|
|
506
|
+
// O(n · reach).
|
|
507
|
+
const spans = store.flatSpans?.(bytes) ?? null;
|
|
498
508
|
const flatProbe = (start: number, end: number): number | null =>
|
|
499
|
-
|
|
509
|
+
spans !== null
|
|
510
|
+
? spans(start, end)
|
|
511
|
+
: store.findFlatBranch
|
|
500
512
|
? store.findFlatBranch(bytes.subarray(start, end))
|
|
501
513
|
: store.findBranch(allLeafIds.slice(start, end));
|
|
502
514
|
// THE TWO ROUTES COST DIFFERENT THINGS, SO THEY ARE PRICED SEPARATELY.
|
|
@@ -635,11 +647,15 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
|
635
647
|
// simpler form won on that measurement.
|
|
636
648
|
const reach = chainReach(W) * W * W + 2 * radius;
|
|
637
649
|
if (ctx.meter) ctx.meter.recogniseInteriorGaps += ordered.length;
|
|
650
|
+
// Each end pairs only with the starts in [end − reach, end − W], in
|
|
651
|
+
// ascending order — the pairs, and the order the budget is spent in,
|
|
652
|
+
// of the all-pairs scan, without its O(n²) enumeration.
|
|
653
|
+
let lo = 0;
|
|
638
654
|
for (const end of ordered) {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
if (
|
|
655
|
+
while (ordered[lo] < end - reach) lo++;
|
|
656
|
+
for (let i = lo; i < ordered.length; i++) {
|
|
657
|
+
const start = ordered[i];
|
|
658
|
+
if (end - start < W) break;
|
|
643
659
|
if (ctx.meter) ctx.meter.recogniseInteriorPairs++;
|
|
644
660
|
spend(start, end);
|
|
645
661
|
}
|
|
@@ -679,8 +695,7 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
|
679
695
|
// arithmetic, not evidence. Removing it wholesale was measured and
|
|
680
696
|
// REVERTED: it also drops legitimate multi-byte chains (the 12-byte
|
|
681
697
|
// "Eiffel Tower" site vanished with it). The premise is wrong but the
|
|
682
|
-
// trust it stood in for is real;
|
|
683
|
-
// See bench/README.md.
|
|
698
|
+
// trust it stood in for is real; the replacement signal follows.
|
|
684
699
|
//
|
|
685
700
|
// THE REPLACEMENT SIGNAL (2026-08-13): `leadsSomewhere` on the BYTE-EXACT
|
|
686
701
|
// branch the chain already found. The blanket off-boundary suppression is
|
package/src/mind/resonance.ts
CHANGED
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
// Address → Resonate → filter by Traverse/Read predicates → transform.
|
|
4
4
|
// Used by bridge, recallByResonance, pivotInto, meaningOf.
|
|
5
5
|
// (The graded locate() matcher formerly here lives in match.ts.)
|
|
6
|
-
import { rItem
|
|
7
|
-
import { decodeText } from "./rationale.js";
|
|
6
|
+
import { rItem } from "./trace.js";
|
|
8
7
|
|
|
9
8
|
import { cosine, Vec } from "../vec.js";
|
|
10
9
|
import { mergeThreshold } from "../geometry.js";
|
|
11
|
-
import { concat2, concatBytes, indexOf } from "../bytes.js";
|
|
10
|
+
import { concat2, concatBytes, indexOf, latin1 } from "../bytes.js";
|
|
12
11
|
import type { MindContext } from "./types.js";
|
|
13
12
|
import { gistOf, read, resolve, walkTree } from "./primitives.js";
|
|
14
13
|
import { perceive } from "./primitives.js";
|
|
@@ -17,8 +16,6 @@ import {
|
|
|
17
16
|
cachedRead,
|
|
18
17
|
type Junction,
|
|
19
18
|
junctionContainers,
|
|
20
|
-
junctionContainersFrom,
|
|
21
|
-
junctionSeeds,
|
|
22
19
|
junctionSynonyms,
|
|
23
20
|
walkCache,
|
|
24
21
|
} from "./junction.js";
|
|
@@ -128,17 +125,6 @@ function junctionEdges(
|
|
|
128
125
|
return out;
|
|
129
126
|
}
|
|
130
127
|
|
|
131
|
-
/** A byte string as a string, ONE code unit per byte — injective, so it is
|
|
132
|
-
* safe to build a cache key from. Chunked to keep the spread within the
|
|
133
|
-
* engine's argument limit on long contexts. */
|
|
134
|
-
function latin1(b: Uint8Array): string {
|
|
135
|
-
let s = "";
|
|
136
|
-
for (let i = 0; i < b.length; i += 4096) {
|
|
137
|
-
s += String.fromCharCode(...b.subarray(i, i + 4096));
|
|
138
|
-
}
|
|
139
|
-
return s;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
128
|
/** Per-response memo of bridge results, keyed by the response's lifecycle
|
|
143
129
|
* object (ctx.climbMemo — created fresh by respond() and nulled after, so
|
|
144
130
|
* entries can never outlive the read-only window they are valid in). The
|
package/src/mind/trace.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { MindContext } from "./types.js";
|
|
8
8
|
import { read } from "./primitives.js";
|
|
9
|
-
import type {
|
|
9
|
+
import type { DerivationStep } from "./graph-search.js";
|
|
10
10
|
import { decodeText } from "./rationale.js";
|
|
11
11
|
import type { RationaleItem } from "./rationale.js";
|
|
12
12
|
|
package/src/mind/traverse.ts
CHANGED
|
@@ -9,13 +9,20 @@
|
|
|
9
9
|
import { cosine, Vec } from "../vec.js";
|
|
10
10
|
import type { AncestorReach, MindContext, SaturationStop } from "./types.js";
|
|
11
11
|
import { gistOf, read } from "./primitives.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
canonicalWindows,
|
|
14
|
+
chainReach,
|
|
15
|
+
leafIdPrefix,
|
|
16
|
+
leafIdRun,
|
|
17
|
+
} from "./canonical.js";
|
|
13
18
|
// Imported at the TOP, where every other import is. They used to sit 800 lines
|
|
14
19
|
// down under a note claiming the position mattered ("before trace module is
|
|
15
20
|
// loaded") — it does not: an ES module's static imports are HOISTED, so the
|
|
16
21
|
// file's line order never decides load order. The note described an intention
|
|
17
22
|
// the runtime does not honour; the imports move and the claim goes.
|
|
18
23
|
import { decodeText } from "./rationale.js";
|
|
24
|
+
import { latin1 } from "../bytes.js";
|
|
25
|
+
import { type WindowIndex, windowIndex, witness } from "./evidence.js";
|
|
19
26
|
import type { RationaleItem } from "./rationale.js";
|
|
20
27
|
|
|
21
28
|
// ── Session structural memo ─────────────────────────────────────────────
|
|
@@ -488,9 +495,9 @@ export function bearsEdge(ctx: MindContext, id: number): boolean {
|
|
|
488
495
|
return cachedHasNext(ctx, id, getStructCache(ctx));
|
|
489
496
|
}
|
|
490
497
|
|
|
491
|
-
/** Whether a node LEADS SOMEWHERE —
|
|
492
|
-
*
|
|
493
|
-
* that
|
|
498
|
+
/** Whether a node LEADS SOMEWHERE — the store's admission predicate
|
|
499
|
+
* ({@link Store.leadsSomewhere}: edge or halo) with its edge tier memoised for
|
|
500
|
+
* the response. Recognition filters sites with it (cover.md): a form that
|
|
494
501
|
* leads nowhere contributes nothing to any derivation. Runs once per candidate
|
|
495
502
|
* span on the recognition hot path — `hasNext` is cached per response (the same
|
|
496
503
|
* flat-branch ids are probed across prefix variants by canonicalChunkId).
|
|
@@ -696,8 +703,10 @@ export function guidedNext(
|
|
|
696
703
|
return pick;
|
|
697
704
|
}
|
|
698
705
|
|
|
699
|
-
/** Disambiguate among a node's learnt continuations by
|
|
700
|
-
*
|
|
706
|
+
/** Disambiguate among a node's learnt continuations: first by the question's
|
|
707
|
+
* own witness of an establishing context (the exact tier, see
|
|
708
|
+
* {@link askedContinuations}), then by distributional support. NOTE the
|
|
709
|
+
* `guide` contract: its VALUE is deliberately unused —
|
|
701
710
|
* only its PRESENCE gates disambiguation (a null guide means no query is in
|
|
702
711
|
* flight, so structural walkers keep plain first-edge behaviour). The
|
|
703
712
|
* gist-cosine of short answer candidates against a query guide is dominated
|
|
@@ -721,13 +730,30 @@ export function chooseNext(
|
|
|
721
730
|
if (nx.length === 0) return undefined;
|
|
722
731
|
if (nx.length === 1 || !guide) return nx[0];
|
|
723
732
|
|
|
733
|
+
// THE EXACT TIER — the continuation the QUESTION names. Every other
|
|
734
|
+
// disambiguation below reads popularity, and is right to refuse the gist (see
|
|
735
|
+
// the doc above); but the corpus also wrote down, for each continuation,
|
|
736
|
+
// WHICH QUESTIONS IT ANSWERS — its establishing contexts — and a question is
|
|
737
|
+
// not a gist. When one of them is witnessed by the asker's bytes plus the
|
|
738
|
+
// node's own, that continuation is the one being asked for. Measured on the
|
|
739
|
+
// 31.7M-node store: `Who is the father of Frederick II?` answered the
|
|
740
|
+
// citizenship fact (the most-poured of eight) while `Frederick II father` —
|
|
741
|
+
// one of the father fact's own establishing contexts — lay wholly inside the
|
|
742
|
+
// question. Exact, so it ranks first (exact-vs-approximate.md); when nothing
|
|
743
|
+
// is witnessed the ladder below decides exactly as before.
|
|
744
|
+
const asked = ctx._edgeAsked;
|
|
745
|
+
const named = asked === null ? null : askedContinuations(ctx, id, nx, asked);
|
|
746
|
+
if (named !== null && named.length === 1) return named[0];
|
|
747
|
+
|
|
724
748
|
// Cap candidates at √N — the same bound the original chooseAmong used.
|
|
725
749
|
// A hub context can accumulate thousands of continuations; the best-fit
|
|
726
750
|
// one is among the first √N by insertion order (edges are never deleted,
|
|
727
751
|
// so the oldest are the most established). A strongly-supported edge
|
|
728
752
|
// inserted beyond the cap is invisible here — the deliberate trade
|
|
729
|
-
// against paying O(fan-out) count reads on every disambiguation.
|
|
730
|
-
|
|
753
|
+
// against paying O(fan-out) count reads on every disambiguation. Several
|
|
754
|
+
// continuations named EQUALLY by the question are told apart by the same
|
|
755
|
+
// ladder, over them alone.
|
|
756
|
+
const capped = named ?? nx; // already the hub-capped prefix, by the read above
|
|
731
757
|
|
|
732
758
|
// Distributional-evidence disambiguation, consulting BOTH read-outs of the
|
|
733
759
|
// evidence the training poured:
|
|
@@ -802,6 +828,213 @@ export function chooseNext(
|
|
|
802
828
|
return best;
|
|
803
829
|
}
|
|
804
830
|
|
|
831
|
+
/** The response canonicalizer's reading of `bytes` when it keeps every offset
|
|
832
|
+
* — so a window found in the canonical bytes sits at the same place in the
|
|
833
|
+
* asker's — else the bytes themselves. Text canon is offset-preserving on
|
|
834
|
+
* ASCII without interior whitespace runs; where it is not, the raw bytes are
|
|
835
|
+
* read and a case-variant window simply does not match. */
|
|
836
|
+
export function offsetCanon(ctx: MindContext, bytes: Uint8Array): Uint8Array {
|
|
837
|
+
if (ctx.canon === null) return bytes;
|
|
838
|
+
const c = ctx.canon(bytes);
|
|
839
|
+
return c.length === bytes.length ? c : bytes;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/** The continuations of `id` that `asked` NAMES (see {@link
|
|
843
|
+
* askedContinuations}) — for a caller holding material other than the whole
|
|
844
|
+
* question: the multi-hop walk asks with what of the question no product has
|
|
845
|
+
* restated yet. null when none is named. */
|
|
846
|
+
export function namedContinuations(
|
|
847
|
+
ctx: MindContext,
|
|
848
|
+
id: number,
|
|
849
|
+
asked: { bytes: Uint8Array; index: WindowIndex },
|
|
850
|
+
): number[] | null {
|
|
851
|
+
const nx = ctx.store.nextFirst(id, hubBound(ctx));
|
|
852
|
+
return nx.length === 0 ? null : askedContinuations(ctx, id, nx, asked);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/** The continuations of `id` (among `nx`) that the question NAMES: one of
|
|
856
|
+
* their establishing contexts — a predecessor other than `id` itself — is
|
|
857
|
+
* wholly witnessed by the question plus `id`'s own bytes (evidence.ts), with
|
|
858
|
+
* the question supplying at least one window the node does not. Ranked by
|
|
859
|
+
* how much of the question witnesses it; null when none is named.
|
|
860
|
+
*
|
|
861
|
+
* THE NODE'S OWN BYTES ARE MATERIAL because a derivation stands on them. On
|
|
862
|
+
* the second hop of `Where was the place of death of the director of film
|
|
863
|
+
* Beat Girl?` the node is `Edmond T. Gréville` — reached, never written — and
|
|
864
|
+
* its fact's establishing context `Edmond T. Gréville place of death` is held
|
|
865
|
+
* by neither the question nor the node, only by both. Measured over 5,236
|
|
866
|
+
* held-out 2Wiki questions: such a context is wholly witnessed by the
|
|
867
|
+
* question alone 69 times, by the question and the first hop 2,153 times.
|
|
868
|
+
*
|
|
869
|
+
* BOUNDED: predecessor reads share one √N budget across the candidates (see
|
|
870
|
+
* the loop below), asked cheapest first; past it the tier abstains for the
|
|
871
|
+
* rest, metered, and the ladder decides as before.
|
|
872
|
+
* Each form is read at most to the material's length — a form longer than
|
|
873
|
+
* everything at hand cannot be wholly witnessed without repeating it. */
|
|
874
|
+
function askedContinuations(
|
|
875
|
+
ctx: MindContext,
|
|
876
|
+
id: number,
|
|
877
|
+
nx: readonly number[],
|
|
878
|
+
asked: { bytes: Uint8Array; index: WindowIndex },
|
|
879
|
+
): number[] | null {
|
|
880
|
+
return askedEntry(ctx, id, nx, asked).named;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/** The question spans that NAMED `pick` among `id`'s continuations — the
|
|
884
|
+
* evidence a projection through that pick stands on, so a mechanism can
|
|
885
|
+
* account for what the question said about it (mechanism-market.md:
|
|
886
|
+
* evidence travels). Empty when the question names no continuation of `id`
|
|
887
|
+
* or names others. */
|
|
888
|
+
export function askedEvidence(
|
|
889
|
+
ctx: MindContext,
|
|
890
|
+
id: number,
|
|
891
|
+
pick: number,
|
|
892
|
+
): Array<[number, number]> {
|
|
893
|
+
const asked = ctx._edgeAsked;
|
|
894
|
+
if (asked === null) return [];
|
|
895
|
+
const nx = ctx.store.nextFirst(id, hubBound(ctx));
|
|
896
|
+
const entry = askedEntry(ctx, id, nx, asked);
|
|
897
|
+
return entry.named?.includes(pick) ? entry.spans.get(pick) ?? [] : [];
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
interface AskedEntry {
|
|
901
|
+
named: number[] | null;
|
|
902
|
+
/** Per named continuation, the question spans that witnessed it. */
|
|
903
|
+
spans: Map<number, Array<[number, number]>>;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function askedEntry(
|
|
907
|
+
ctx: MindContext,
|
|
908
|
+
id: number,
|
|
909
|
+
nx: readonly number[],
|
|
910
|
+
asked: { bytes: Uint8Array; index: WindowIndex },
|
|
911
|
+
): AskedEntry {
|
|
912
|
+
let memo = askedMemo.get(asked);
|
|
913
|
+
if (memo === undefined) askedMemo.set(asked, memo = new Map());
|
|
914
|
+
const hit = memo.get(id);
|
|
915
|
+
if (hit !== undefined && !ctx.trace) return hit;
|
|
916
|
+
const entry = askedContinuationsImpl(ctx, id, nx, asked);
|
|
917
|
+
memo.set(id, entry);
|
|
918
|
+
return entry;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/** One pick per node per question — every mechanism of a response asks the
|
|
922
|
+
* same node about the same question (the guided-pick memo's own reason). */
|
|
923
|
+
const askedMemo = new WeakMap<object, Map<number, AskedEntry>>();
|
|
924
|
+
|
|
925
|
+
function askedContinuationsImpl(
|
|
926
|
+
ctx: MindContext,
|
|
927
|
+
id: number,
|
|
928
|
+
nx: readonly number[],
|
|
929
|
+
asked: { bytes: Uint8Array; index: WindowIndex },
|
|
930
|
+
): AskedEntry {
|
|
931
|
+
const none: AskedEntry = { named: null, spans: new Map() };
|
|
932
|
+
const W = ctx.space.maxGroup;
|
|
933
|
+
// A SATURATED READ IS NOT A CANDIDATE SET. When the continuations came back
|
|
934
|
+
// at the √N cap the read may have cut the named one off, so "none of these is
|
|
935
|
+
// named" and "this is the named one" are both unfounded — and this is exactly
|
|
936
|
+
// where witnessing would read most. The tier abstains, metered, and the
|
|
937
|
+
// distributional ladder decides as it always has.
|
|
938
|
+
if (nx.length >= hubBound(ctx)) {
|
|
939
|
+
if (ctx.meter) ctx.meter.askedReadsSaturated++;
|
|
940
|
+
return none;
|
|
941
|
+
}
|
|
942
|
+
const cache = getStructCache(ctx);
|
|
943
|
+
const ownCap = asked.bytes.length * W;
|
|
944
|
+
const own = offsetCanon(ctx, read(ctx, id, ownCap));
|
|
945
|
+
const ownIndex = windowIndex(own, W);
|
|
946
|
+
// Naming needs the question to say at least one window the node does not:
|
|
947
|
+
// when the node already holds every window of the question (the question IS
|
|
948
|
+
// this context, or a piece of it), nothing can be named, and nothing is read.
|
|
949
|
+
let beyond = false;
|
|
950
|
+
for (const key of asked.index.keys()) {
|
|
951
|
+
if (!ownIndex.has(key)) {
|
|
952
|
+
beyond = true;
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (!beyond) return none;
|
|
957
|
+
const indexes = [asked.index, ownIndex];
|
|
958
|
+
const formCap = asked.bytes.length + own.length;
|
|
959
|
+
// BOUNDED READS (bounded-reads.md): the decision reads at most √N
|
|
960
|
+
// establishing contexts — floored at the write side's own arity `chainReach(W)`
|
|
961
|
+
// so a store too small for √N to cover one fact's questions still decides.
|
|
962
|
+
// Candidates are asked CHEAPEST FIRST (fewest establishing contexts): a common
|
|
963
|
+
// reply established by hundreds of contexts would otherwise spend the whole
|
|
964
|
+
// allowance alone. The order changes what is READ, never what wins: scores
|
|
965
|
+
// are compared afterwards in the continuations' own order.
|
|
966
|
+
let budget = Math.max(hubBound(ctx), chainReach(W));
|
|
967
|
+
const order = nx
|
|
968
|
+
.map((n, at) => ({ n, at, support: cachedPrevCount(ctx, n, cache) }))
|
|
969
|
+
.filter((c) => c.support >= 2) // only `id` establishes the rest
|
|
970
|
+
.sort((a, b) => a.support - b.support || a.at - b.at);
|
|
971
|
+
const scored: Array<
|
|
972
|
+
{
|
|
973
|
+
n: number;
|
|
974
|
+
at: number;
|
|
975
|
+
score: number;
|
|
976
|
+
by: number;
|
|
977
|
+
spans: Array<[number, number]>;
|
|
978
|
+
}
|
|
979
|
+
> = [];
|
|
980
|
+
for (const { n, at, support } of order) {
|
|
981
|
+
if (support > budget) {
|
|
982
|
+
if (ctx.meter) ctx.meter.askedReadsSaturated++;
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
985
|
+
budget -= support;
|
|
986
|
+
if (ctx.meter) ctx.meter.askedPredecessorReads += support;
|
|
987
|
+
let score = 0;
|
|
988
|
+
let by = -1;
|
|
989
|
+
let spans: Array<[number, number]> = [];
|
|
990
|
+
for (const c of ctx.store.prevFirst(n, support)) {
|
|
991
|
+
if (c === id) continue;
|
|
992
|
+
// The form's FIRST window decides most refusals: one short prefix read
|
|
993
|
+
// before the whole form is reconstructed (a conversation-length
|
|
994
|
+
// predecessor would otherwise be read in full to fail on its opening).
|
|
995
|
+
const head = offsetCanon(ctx, read(ctx, c, W));
|
|
996
|
+
if (head.length < W) continue;
|
|
997
|
+
if (!indexes.some((ix) => ix.has(latin1(head)))) continue;
|
|
998
|
+
const form = read(ctx, c, formCap + 1);
|
|
999
|
+
if (form.length < W || form.length > formCap) continue;
|
|
1000
|
+
const w = witness(offsetCanon(ctx, form), indexes, W);
|
|
1001
|
+
if (!w.complete || w.bytes < W) continue;
|
|
1002
|
+
if (w.bytes > score) {
|
|
1003
|
+
score = w.bytes;
|
|
1004
|
+
by = c;
|
|
1005
|
+
spans = w.spans;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (score > 0) scored.push({ n, at, score, by, spans });
|
|
1009
|
+
}
|
|
1010
|
+
scored.sort((a, b) => a.at - b.at);
|
|
1011
|
+
let best: number[] = [];
|
|
1012
|
+
let bestBytes = 0;
|
|
1013
|
+
let witnessed: number | null = null;
|
|
1014
|
+
for (const { n, score, by } of scored) {
|
|
1015
|
+
if (score > bestBytes) {
|
|
1016
|
+
best = [n];
|
|
1017
|
+
bestBytes = score;
|
|
1018
|
+
witnessed = by;
|
|
1019
|
+
} else if (score === bestBytes) best.push(n);
|
|
1020
|
+
}
|
|
1021
|
+
if (best.length === 0) return none;
|
|
1022
|
+
if (ctx.meter) ctx.meter.askedContinuations++;
|
|
1023
|
+
if (ctx.trace && witnessed !== null) {
|
|
1024
|
+
ctx.trace.step(
|
|
1025
|
+
"askedContinuation",
|
|
1026
|
+
[rItemShort(ctx, id, "node"), rItemShort(ctx, witnessed, "asked")],
|
|
1027
|
+
best.map((n) => rItemShort(ctx, n, "named")),
|
|
1028
|
+
`${nx.length} continuations — the question witnesses ` +
|
|
1029
|
+
`${best.length === 1 ? "one's" : `${best.length}'`} own establishing ` +
|
|
1030
|
+
`context (${bestBytes} question byte(s) beyond the node)`,
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
const evidence = new Map<number, Array<[number, number]>>();
|
|
1034
|
+
for (const c of scored) if (best.includes(c.n)) evidence.set(c.n, c.spans);
|
|
1035
|
+
return { named: best, spans: evidence };
|
|
1036
|
+
}
|
|
1037
|
+
|
|
805
1038
|
/** The perceived gist of a candidate node, through the session gist cache.
|
|
806
1039
|
* Re-gisting a candidate is a full river fold of its bytes — the measured
|
|
807
1040
|
* recall bottleneck (a hub context offers up to √N continuations, EACH
|
|
@@ -903,6 +1136,86 @@ export function allWindowsAreScaffolding(
|
|
|
903
1136
|
return sawOne;
|
|
904
1137
|
}
|
|
905
1138
|
|
|
1139
|
+
/** Per offset of `bytes`: 1 when the W-window there is a stored form contained
|
|
1140
|
+
* in more than √N places (corpus-global scaffolding; see the floor below),
|
|
1141
|
+
* else 0. Memoised per
|
|
1142
|
+
* byte array for the life of the store's read-only response. */
|
|
1143
|
+
export function hubWindows(ctx: MindContext, bytes: Uint8Array): Uint8Array {
|
|
1144
|
+
const hit = hubWindowMemo.get(bytes);
|
|
1145
|
+
if (hit !== undefined) return hit;
|
|
1146
|
+
const W = ctx.space.maxGroup;
|
|
1147
|
+
// Floored at the write side's arity: inside ONE deposit's fold a window is
|
|
1148
|
+
// already contained by up to `chainReach(W)` chunks and branches, so on a
|
|
1149
|
+
// store of a few facts the √N reading would call every window frame — that
|
|
1150
|
+
// is fold structure, not corpus commonality.
|
|
1151
|
+
const bound = Math.max(hubBound(ctx), chainReach(W));
|
|
1152
|
+
const hub = new Uint8Array(Math.max(0, bytes.length - W + 1));
|
|
1153
|
+
for (let o = 0; o < hub.length; o++) {
|
|
1154
|
+
const ids = leafIdRun(ctx, bytes, o, o + W);
|
|
1155
|
+
const id = ids === null ? null : ctx.store.findBranch(ids);
|
|
1156
|
+
if (id !== null && ctx.store.containersSlice(id, bound, 1).length > 0) {
|
|
1157
|
+
hub[o] = 1;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
hubWindowMemo.set(bytes, hub);
|
|
1161
|
+
return hub;
|
|
1162
|
+
}
|
|
1163
|
+
const hubWindowMemo = new WeakMap<Uint8Array, Uint8Array>();
|
|
1164
|
+
|
|
1165
|
+
/** The query's SCAFFOLDING CORE, as spans: the bytes every W-window over which
|
|
1166
|
+
* is a hub (see {@link hubWindows}) — what is nothing but frame, where
|
|
1167
|
+
* {@link scaffoldExtents} is what a frame window reaches. */
|
|
1168
|
+
export function scaffoldSpans(
|
|
1169
|
+
ctx: MindContext,
|
|
1170
|
+
query: Uint8Array,
|
|
1171
|
+
): Array<[number, number]> {
|
|
1172
|
+
const W = ctx.space.maxGroup;
|
|
1173
|
+
const hub = hubWindows(ctx, query);
|
|
1174
|
+
const n = hub.length;
|
|
1175
|
+
if (n <= 0) return [];
|
|
1176
|
+
const spans: Array<[number, number]> = [];
|
|
1177
|
+
let start = -1;
|
|
1178
|
+
for (let i = 0; i < query.length; i++) {
|
|
1179
|
+
let all = true;
|
|
1180
|
+
for (let o = Math.max(0, i - W + 1); o <= Math.min(i, n - 1); o++) {
|
|
1181
|
+
if (!hub[o]) {
|
|
1182
|
+
all = false;
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (all && start < 0) start = i;
|
|
1187
|
+
if (!all && start >= 0) {
|
|
1188
|
+
spans.push([start, i]);
|
|
1189
|
+
start = -1;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (start >= 0) spans.push([start, query.length]);
|
|
1193
|
+
return spans;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/** The EXTENTS of the query's SCAFFOLDING windows, merged: every byte some
|
|
1197
|
+
* W-window reaches that is a stored form contained in more than √N places —
|
|
1198
|
+
* corpus-global commonality (commonality.md), the same "hub" reading as
|
|
1199
|
+
* {@link allWindowsAreScaffolding} and the bridge's `explainedSpan`. A window
|
|
1200
|
+
* the store never saw is NOT scaffolding. The extent, not the core, is what
|
|
1201
|
+
* a coverage test needs: a span that still holds one hub window can be
|
|
1202
|
+
* "carried" by any fact that holds that window. */
|
|
1203
|
+
export function scaffoldExtents(
|
|
1204
|
+
ctx: MindContext,
|
|
1205
|
+
query: Uint8Array,
|
|
1206
|
+
): Array<[number, number]> {
|
|
1207
|
+
const W = ctx.space.maxGroup;
|
|
1208
|
+
const hub = hubWindows(ctx, query);
|
|
1209
|
+
const spans: Array<[number, number]> = [];
|
|
1210
|
+
for (let o = 0; o < hub.length; o++) {
|
|
1211
|
+
if (!hub[o]) continue;
|
|
1212
|
+
const last = spans[spans.length - 1];
|
|
1213
|
+
if (last !== undefined && o <= last[1]) last[1] = o + W;
|
|
1214
|
+
else spans.push([o, o + W]);
|
|
1215
|
+
}
|
|
1216
|
+
return spans;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
906
1219
|
// ── THE PREFIX SUPPLY ───────────────────────────────────────────────────────
|
|
907
1220
|
//
|
|
908
1221
|
// A RETRIEVAL capability, not a grounding one: "which trained forms does this
|
package/src/mind/types.ts
CHANGED
|
@@ -10,16 +10,9 @@ import type { Space } from "../sema.js";
|
|
|
10
10
|
import type { Alphabet } from "../alphabet.js";
|
|
11
11
|
import type { MindConfig } from "../config.js";
|
|
12
12
|
import type { Meter } from "../meter.js";
|
|
13
|
-
import type {
|
|
14
|
-
ComputedResult,
|
|
15
|
-
DerivationItem,
|
|
16
|
-
DerivationStep,
|
|
17
|
-
GraphSearch,
|
|
18
|
-
Leaf,
|
|
19
|
-
Seg,
|
|
20
|
-
Site,
|
|
21
|
-
} from "./graph-search.js";
|
|
13
|
+
import type { GraphSearch, Leaf, Seg, Site } from "./graph-search.js";
|
|
22
14
|
import type { Rationale } from "./rationale.js";
|
|
15
|
+
import type { WindowIndex } from "./evidence.js";
|
|
23
16
|
import type { ContentFold, Grid } from "../geometry.js";
|
|
24
17
|
|
|
25
18
|
/** One {@link MindContext._depositTrees} entry — see that field's doc.
|
|
@@ -33,7 +26,7 @@ export interface DepositCacheEntry {
|
|
|
33
26
|
/** The plain content fold's reusable segment state. */
|
|
34
27
|
content: ContentFold;
|
|
35
28
|
}
|
|
36
|
-
import { bytesEqual, concatBytes
|
|
29
|
+
import { bytesEqual, concatBytes } from "../bytes.js";
|
|
37
30
|
import { restates } from "./derivation.js";
|
|
38
31
|
import { dominates } from "../geometry.js";
|
|
39
32
|
|
|
@@ -199,7 +192,13 @@ export interface Attention {
|
|
|
199
192
|
* a genuine further topic is named in its own distinctive wording
|
|
200
193
|
* somewhere the query's scaffolding does not reach, always a SEPARATE
|
|
201
194
|
* cluster from whatever else corroborates it. See
|
|
202
|
-
* test/37-cluster-dispersion-fusion.test.mjs.
|
|
195
|
+
* test/37-cluster-dispersion-fusion.test.mjs.
|
|
196
|
+
*
|
|
197
|
+
* Read from the VOTES, this is a lossy witness: a region votes once, for its
|
|
198
|
+
* top anchor, so a place can be lost to a tie or won through an accident.
|
|
199
|
+
* Fusion therefore also asks the root's CONTEXT the same question at window
|
|
200
|
+
* scale (reasoning.ts `sharedPlaces`) and trusts a root that either reading
|
|
201
|
+
* finds in two places. */
|
|
203
202
|
clusters: number;
|
|
204
203
|
}
|
|
205
204
|
|
|
@@ -413,6 +412,11 @@ export interface MindContext extends GraphSearchHost {
|
|
|
413
412
|
* ordinary respond() and for the first turn of a conversation. */
|
|
414
413
|
currentTurnStart: number;
|
|
415
414
|
_edgeGuide: Vec | null;
|
|
415
|
+
/** The question currently being answered, as the material `chooseNext`'s
|
|
416
|
+
* exact tier witnesses a continuation's establishing contexts against —
|
|
417
|
+
* its bytes (canonical when the response's canon preserves offsets) and
|
|
418
|
+
* their window index. Set and cleared with `_edgeGuide`. */
|
|
419
|
+
_edgeAsked: { bytes: Uint8Array; index: WindowIndex } | null;
|
|
416
420
|
_edgeChoice: Map<number, number>;
|
|
417
421
|
_prevSeen: Set<number> | null;
|
|
418
422
|
/** Session cache of node-id → perceived gist, for candidate scoring
|