@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
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
// that leads somewhere (has a continuation edge or a halo).
|
|
6
6
|
// segment — leaf-parent segmentation using the geometry's own groupings.
|
|
7
7
|
import { rItem } from "./trace.js";
|
|
8
|
-
import { canonResolve, foldTree, gistOf,
|
|
8
|
+
import { canonResolve, foldTree, gistOf, perceive, resolve, } from "./primitives.js";
|
|
9
9
|
import { atomIsHub, bearsEdge, corpusN, leadsSomewhere } from "./traverse.js";
|
|
10
10
|
import { chainReach, leafIdAt, leafIdRun } from "./canonical.js";
|
|
11
11
|
import { canonHash } from "../canon.js";
|
|
12
|
+
import { latin1 } from "../bytes.js";
|
|
12
13
|
import { isChunk } from "../sema.js";
|
|
13
14
|
/** Decompose a byte stream into every stored form that leads somewhere
|
|
14
15
|
* (has a continuation edge or a halo). Two complementary readings:
|
|
@@ -77,7 +78,7 @@ export function recognise(ctx, bytes) {
|
|
|
77
78
|
// not silent), so it is emitted here directly rather than only inside
|
|
78
79
|
// recogniseImpl.
|
|
79
80
|
if (ctx.recogniseMemo) {
|
|
80
|
-
const key =
|
|
81
|
+
const key = latin1(bytes);
|
|
81
82
|
const hit = ctx.recogniseMemo.get(key);
|
|
82
83
|
if (hit !== undefined) {
|
|
83
84
|
if (ctx.meter)
|
|
@@ -483,9 +484,21 @@ function recogniseImpl(ctx, bytes) {
|
|
|
483
484
|
// encoding is the identity, so the span's bytes ARE the branch key.
|
|
484
485
|
// `subarray` is a view — this allocates nothing per probe, and the
|
|
485
486
|
// bloom filter answers the misses without touching the database.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
487
|
+
//
|
|
488
|
+
// Through ONE span prober (Store.flatSpans), because hashing the key was
|
|
489
|
+
// itself the quadratic: every probe hashed its span from the start, and
|
|
490
|
+
// the interior pass below sweeps up to `reach` ends past each endpoint —
|
|
491
|
+
// O(n · reach²) bytes hashed. Measured on the 31.7M-node store, one
|
|
492
|
+
// composition-regime response (#97 of the battery) probed 2,079,400
|
|
493
|
+
// spans and hashed 251,660,406 bytes for them. The prober extends each
|
|
494
|
+
// start's hash instead, so the same probes, answered identically, cost
|
|
495
|
+
// O(n · reach).
|
|
496
|
+
const spans = store.flatSpans?.(bytes) ?? null;
|
|
497
|
+
const flatProbe = (start, end) => spans !== null
|
|
498
|
+
? spans(start, end)
|
|
499
|
+
: store.findFlatBranch
|
|
500
|
+
? store.findFlatBranch(bytes.subarray(start, end))
|
|
501
|
+
: store.findBranch(allLeafIds.slice(start, end));
|
|
489
502
|
// THE TWO ROUTES COST DIFFERENT THINGS, SO THEY ARE PRICED SEPARATELY.
|
|
490
503
|
//
|
|
491
504
|
// The exact route is a bloom-gated hash over a subarray VIEW: no
|
|
@@ -627,13 +640,17 @@ function recogniseImpl(ctx, bytes) {
|
|
|
627
640
|
const reach = chainReach(W) * W * W + 2 * radius;
|
|
628
641
|
if (ctx.meter)
|
|
629
642
|
ctx.meter.recogniseInteriorGaps += ordered.length;
|
|
643
|
+
// Each end pairs only with the starts in [end − reach, end − W], in
|
|
644
|
+
// ascending order — the pairs, and the order the budget is spent in,
|
|
645
|
+
// of the all-pairs scan, without its O(n²) enumeration.
|
|
646
|
+
let lo = 0;
|
|
630
647
|
for (const end of ordered) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
if (
|
|
636
|
-
|
|
648
|
+
while (ordered[lo] < end - reach)
|
|
649
|
+
lo++;
|
|
650
|
+
for (let i = lo; i < ordered.length; i++) {
|
|
651
|
+
const start = ordered[i];
|
|
652
|
+
if (end - start < W)
|
|
653
|
+
break;
|
|
637
654
|
if (ctx.meter)
|
|
638
655
|
ctx.meter.recogniseInteriorPairs++;
|
|
639
656
|
spend(start, end);
|
|
@@ -672,8 +689,7 @@ function recogniseImpl(ctx, bytes) {
|
|
|
672
689
|
// arithmetic, not evidence. Removing it wholesale was measured and
|
|
673
690
|
// REVERTED: it also drops legitimate multi-byte chains (the 12-byte
|
|
674
691
|
// "Eiffel Tower" site vanished with it). The premise is wrong but the
|
|
675
|
-
// trust it stood in for is real;
|
|
676
|
-
// See bench/README.md.
|
|
692
|
+
// trust it stood in for is real; the replacement signal follows.
|
|
677
693
|
//
|
|
678
694
|
// THE REPLACEMENT SIGNAL (2026-08-13): `leadsSomewhere` on the BYTE-EXACT
|
|
679
695
|
// branch the chain already found. The blanket off-boundary suppression is
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { rItem } from "./trace.js";
|
|
7
7
|
import { cosine } from "../vec.js";
|
|
8
8
|
import { mergeThreshold } from "../geometry.js";
|
|
9
|
-
import { concat2, concatBytes, indexOf } from "../bytes.js";
|
|
9
|
+
import { concat2, concatBytes, indexOf, latin1 } from "../bytes.js";
|
|
10
10
|
import { gistOf, read, resolve, walkTree } from "./primitives.js";
|
|
11
11
|
import { perceive } from "./primitives.js";
|
|
12
12
|
import { argmaxCosine, candidateGist, hubBound } from "./traverse.js";
|
|
@@ -107,16 +107,6 @@ function junctionEdges(ctx, left, right, maxContainer) {
|
|
|
107
107
|
}
|
|
108
108
|
return out;
|
|
109
109
|
}
|
|
110
|
-
/** A byte string as a string, ONE code unit per byte — injective, so it is
|
|
111
|
-
* safe to build a cache key from. Chunked to keep the spread within the
|
|
112
|
-
* engine's argument limit on long contexts. */
|
|
113
|
-
function latin1(b) {
|
|
114
|
-
let s = "";
|
|
115
|
-
for (let i = 0; i < b.length; i += 4096) {
|
|
116
|
-
s += String.fromCharCode(...b.subarray(i, i + 4096));
|
|
117
|
-
}
|
|
118
|
-
return s;
|
|
119
|
-
}
|
|
120
110
|
/** Per-response memo of bridge results, keyed by the response's lifecycle
|
|
121
111
|
* object (ctx.climbMemo — created fresh by respond() and nulled after, so
|
|
122
112
|
* entries can never outlive the read-only window they are valid in). The
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Vec } from "../vec.js";
|
|
2
2
|
import type { AncestorReach, MindContext } from "./types.js";
|
|
3
|
+
import { type WindowIndex } from "./evidence.js";
|
|
3
4
|
/** The reach memo this ask should use — see the note above.
|
|
4
5
|
*
|
|
5
6
|
* A TRACED response always gets a fresh, empty one. `AncestorReach`'s
|
|
@@ -58,9 +59,9 @@ export declare function atomIsHub(ctx: MindContext, contextCount: number): boole
|
|
|
58
59
|
* it is sound as a pre-filter before a consumer that applies the full
|
|
59
60
|
* predicate, and never as a replacement for it. */
|
|
60
61
|
export declare function bearsEdge(ctx: MindContext, id: number): boolean;
|
|
61
|
-
/** Whether a node LEADS SOMEWHERE —
|
|
62
|
-
*
|
|
63
|
-
* that
|
|
62
|
+
/** Whether a node LEADS SOMEWHERE — the store's admission predicate
|
|
63
|
+
* ({@link Store.leadsSomewhere}: edge or halo) with its edge tier memoised for
|
|
64
|
+
* the response. Recognition filters sites with it (cover.md): a form that
|
|
64
65
|
* leads nowhere contributes nothing to any derivation. Runs once per candidate
|
|
65
66
|
* span on the recognition hot path — `hasNext` is cached per response (the same
|
|
66
67
|
* flat-branch ids are probed across prefix variants by canonicalChunkId).
|
|
@@ -140,8 +141,10 @@ export declare function argmaxCosine<T>(query: Vec, items: Iterable<T>, vecOf: (
|
|
|
140
141
|
* formRules all share. undefined when the node has no continuation. */
|
|
141
142
|
export declare function guidedFirst(ctx: MindContext, id: number): number | undefined;
|
|
142
143
|
export declare function guidedNext(ctx: MindContext, node: number): number | undefined;
|
|
143
|
-
/** Disambiguate among a node's learnt continuations by
|
|
144
|
-
*
|
|
144
|
+
/** Disambiguate among a node's learnt continuations: first by the question's
|
|
145
|
+
* own witness of an establishing context (the exact tier, see
|
|
146
|
+
* {@link askedContinuations}), then by distributional support. NOTE the
|
|
147
|
+
* `guide` contract: its VALUE is deliberately unused —
|
|
145
148
|
* only its PRESENCE gates disambiguation (a null guide means no query is in
|
|
146
149
|
* flight, so structural walkers keep plain first-edge behaviour). The
|
|
147
150
|
* gist-cosine of short answer candidates against a query guide is dominated
|
|
@@ -153,6 +156,26 @@ export declare function guidedNext(ctx: MindContext, node: number): number | und
|
|
|
153
156
|
* therefore scores by guide cosine. The two directions consult different
|
|
154
157
|
* halves of the evidence on purpose. */
|
|
155
158
|
export declare function chooseNext(ctx: MindContext, id: number, guide?: Vec | null): number | undefined;
|
|
159
|
+
/** The response canonicalizer's reading of `bytes` when it keeps every offset
|
|
160
|
+
* — so a window found in the canonical bytes sits at the same place in the
|
|
161
|
+
* asker's — else the bytes themselves. Text canon is offset-preserving on
|
|
162
|
+
* ASCII without interior whitespace runs; where it is not, the raw bytes are
|
|
163
|
+
* read and a case-variant window simply does not match. */
|
|
164
|
+
export declare function offsetCanon(ctx: MindContext, bytes: Uint8Array): Uint8Array;
|
|
165
|
+
/** The continuations of `id` that `asked` NAMES (see {@link
|
|
166
|
+
* askedContinuations}) — for a caller holding material other than the whole
|
|
167
|
+
* question: the multi-hop walk asks with what of the question no product has
|
|
168
|
+
* restated yet. null when none is named. */
|
|
169
|
+
export declare function namedContinuations(ctx: MindContext, id: number, asked: {
|
|
170
|
+
bytes: Uint8Array;
|
|
171
|
+
index: WindowIndex;
|
|
172
|
+
}): number[] | null;
|
|
173
|
+
/** The question spans that NAMED `pick` among `id`'s continuations — the
|
|
174
|
+
* evidence a projection through that pick stands on, so a mechanism can
|
|
175
|
+
* account for what the question said about it (mechanism-market.md:
|
|
176
|
+
* evidence travels). Empty when the question names no continuation of `id`
|
|
177
|
+
* or names others. */
|
|
178
|
+
export declare function askedEvidence(ctx: MindContext, id: number, pick: number): Array<[number, number]>;
|
|
156
179
|
/** The perceived gist of a candidate node, through the session gist cache.
|
|
157
180
|
* Re-gisting a candidate is a full river fold of its bytes — the measured
|
|
158
181
|
* recall bottleneck (a hub context offers up to √N continuations, EACH
|
|
@@ -198,6 +221,23 @@ export declare function chooseAmong(ctx: MindContext, candidates: readonly numbe
|
|
|
198
221
|
* NOT scaffolding-only — it has no evidence either way, and its callers already
|
|
199
222
|
* refuse it on their own terms. */
|
|
200
223
|
export declare function allWindowsAreScaffolding(ctx: MindContext, query: Uint8Array): boolean;
|
|
224
|
+
/** Per offset of `bytes`: 1 when the W-window there is a stored form contained
|
|
225
|
+
* in more than √N places (corpus-global scaffolding; see the floor below),
|
|
226
|
+
* else 0. Memoised per
|
|
227
|
+
* byte array for the life of the store's read-only response. */
|
|
228
|
+
export declare function hubWindows(ctx: MindContext, bytes: Uint8Array): Uint8Array;
|
|
229
|
+
/** The query's SCAFFOLDING CORE, as spans: the bytes every W-window over which
|
|
230
|
+
* is a hub (see {@link hubWindows}) — what is nothing but frame, where
|
|
231
|
+
* {@link scaffoldExtents} is what a frame window reaches. */
|
|
232
|
+
export declare function scaffoldSpans(ctx: MindContext, query: Uint8Array): Array<[number, number]>;
|
|
233
|
+
/** The EXTENTS of the query's SCAFFOLDING windows, merged: every byte some
|
|
234
|
+
* W-window reaches that is a stored form contained in more than √N places —
|
|
235
|
+
* corpus-global commonality (commonality.md), the same "hub" reading as
|
|
236
|
+
* {@link allWindowsAreScaffolding} and the bridge's `explainedSpan`. A window
|
|
237
|
+
* the store never saw is NOT scaffolding. The extent, not the core, is what
|
|
238
|
+
* a coverage test needs: a span that still holds one hub window can be
|
|
239
|
+
* "carried" by any fact that holds that window. */
|
|
240
|
+
export declare function scaffoldExtents(ctx: MindContext, query: Uint8Array): Array<[number, number]>;
|
|
201
241
|
/** Trained forms the query may OPEN, proposed from the write side's own
|
|
202
242
|
* leaf-id window index — the supply of last resort for prefix completion.
|
|
203
243
|
*
|
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
// project) live in match.ts — the elementary match-and-project operation.
|
|
8
8
|
import { cosine } from "../vec.js";
|
|
9
9
|
import { gistOf, read } from "./primitives.js";
|
|
10
|
-
import { canonicalWindows, leafIdPrefix, leafIdRun } from "./canonical.js";
|
|
10
|
+
import { canonicalWindows, chainReach, leafIdPrefix, leafIdRun, } from "./canonical.js";
|
|
11
11
|
// Imported at the TOP, where every other import is. They used to sit 800 lines
|
|
12
12
|
// down under a note claiming the position mattered ("before trace module is
|
|
13
13
|
// loaded") — it does not: an ES module's static imports are HOISTED, so the
|
|
14
14
|
// file's line order never decides load order. The note described an intention
|
|
15
15
|
// the runtime does not honour; the imports move and the claim goes.
|
|
16
16
|
import { decodeText } from "./rationale.js";
|
|
17
|
+
import { latin1 } from "../bytes.js";
|
|
18
|
+
import { windowIndex, witness } from "./evidence.js";
|
|
17
19
|
//
|
|
18
20
|
// Budgeted on the same terms as the reach memo below (caches.md): these three
|
|
19
21
|
// maps are cleared on every write, but a long read-only session over a large
|
|
@@ -432,9 +434,9 @@ export function atomIsHub(ctx, contextCount) {
|
|
|
432
434
|
export function bearsEdge(ctx, id) {
|
|
433
435
|
return cachedHasNext(ctx, id, getStructCache(ctx));
|
|
434
436
|
}
|
|
435
|
-
/** Whether a node LEADS SOMEWHERE —
|
|
436
|
-
*
|
|
437
|
-
* that
|
|
437
|
+
/** Whether a node LEADS SOMEWHERE — the store's admission predicate
|
|
438
|
+
* ({@link Store.leadsSomewhere}: edge or halo) with its edge tier memoised for
|
|
439
|
+
* the response. Recognition filters sites with it (cover.md): a form that
|
|
438
440
|
* leads nowhere contributes nothing to any derivation. Runs once per candidate
|
|
439
441
|
* span on the recognition hot path — `hasNext` is cached per response (the same
|
|
440
442
|
* flat-branch ids are probed across prefix variants by canonicalChunkId).
|
|
@@ -599,8 +601,10 @@ export function guidedNext(ctx, node) {
|
|
|
599
601
|
ctx._edgeChoice.set(node, pick ?? -1);
|
|
600
602
|
return pick;
|
|
601
603
|
}
|
|
602
|
-
/** Disambiguate among a node's learnt continuations by
|
|
603
|
-
*
|
|
604
|
+
/** Disambiguate among a node's learnt continuations: first by the question's
|
|
605
|
+
* own witness of an establishing context (the exact tier, see
|
|
606
|
+
* {@link askedContinuations}), then by distributional support. NOTE the
|
|
607
|
+
* `guide` contract: its VALUE is deliberately unused —
|
|
604
608
|
* only its PRESENCE gates disambiguation (a null guide means no query is in
|
|
605
609
|
* flight, so structural walkers keep plain first-edge behaviour). The
|
|
606
610
|
* gist-cosine of short answer candidates against a query guide is dominated
|
|
@@ -621,13 +625,30 @@ export function chooseNext(ctx, id, guide) {
|
|
|
621
625
|
return undefined;
|
|
622
626
|
if (nx.length === 1 || !guide)
|
|
623
627
|
return nx[0];
|
|
628
|
+
// THE EXACT TIER — the continuation the QUESTION names. Every other
|
|
629
|
+
// disambiguation below reads popularity, and is right to refuse the gist (see
|
|
630
|
+
// the doc above); but the corpus also wrote down, for each continuation,
|
|
631
|
+
// WHICH QUESTIONS IT ANSWERS — its establishing contexts — and a question is
|
|
632
|
+
// not a gist. When one of them is witnessed by the asker's bytes plus the
|
|
633
|
+
// node's own, that continuation is the one being asked for. Measured on the
|
|
634
|
+
// 31.7M-node store: `Who is the father of Frederick II?` answered the
|
|
635
|
+
// citizenship fact (the most-poured of eight) while `Frederick II father` —
|
|
636
|
+
// one of the father fact's own establishing contexts — lay wholly inside the
|
|
637
|
+
// question. Exact, so it ranks first (exact-vs-approximate.md); when nothing
|
|
638
|
+
// is witnessed the ladder below decides exactly as before.
|
|
639
|
+
const asked = ctx._edgeAsked;
|
|
640
|
+
const named = asked === null ? null : askedContinuations(ctx, id, nx, asked);
|
|
641
|
+
if (named !== null && named.length === 1)
|
|
642
|
+
return named[0];
|
|
624
643
|
// Cap candidates at √N — the same bound the original chooseAmong used.
|
|
625
644
|
// A hub context can accumulate thousands of continuations; the best-fit
|
|
626
645
|
// one is among the first √N by insertion order (edges are never deleted,
|
|
627
646
|
// so the oldest are the most established). A strongly-supported edge
|
|
628
647
|
// inserted beyond the cap is invisible here — the deliberate trade
|
|
629
|
-
// against paying O(fan-out) count reads on every disambiguation.
|
|
630
|
-
|
|
648
|
+
// against paying O(fan-out) count reads on every disambiguation. Several
|
|
649
|
+
// continuations named EQUALLY by the question are told apart by the same
|
|
650
|
+
// ladder, over them alone.
|
|
651
|
+
const capped = named ?? nx; // already the hub-capped prefix, by the read above
|
|
631
652
|
// Distributional-evidence disambiguation, consulting BOTH read-outs of the
|
|
632
653
|
// evidence the training poured:
|
|
633
654
|
// 1. prevCount — how many DISTINCT contexts predict this candidate (one
|
|
@@ -691,6 +712,184 @@ export function chooseNext(ctx, id, guide) {
|
|
|
691
712
|
}
|
|
692
713
|
return best;
|
|
693
714
|
}
|
|
715
|
+
/** The response canonicalizer's reading of `bytes` when it keeps every offset
|
|
716
|
+
* — so a window found in the canonical bytes sits at the same place in the
|
|
717
|
+
* asker's — else the bytes themselves. Text canon is offset-preserving on
|
|
718
|
+
* ASCII without interior whitespace runs; where it is not, the raw bytes are
|
|
719
|
+
* read and a case-variant window simply does not match. */
|
|
720
|
+
export function offsetCanon(ctx, bytes) {
|
|
721
|
+
if (ctx.canon === null)
|
|
722
|
+
return bytes;
|
|
723
|
+
const c = ctx.canon(bytes);
|
|
724
|
+
return c.length === bytes.length ? c : bytes;
|
|
725
|
+
}
|
|
726
|
+
/** The continuations of `id` that `asked` NAMES (see {@link
|
|
727
|
+
* askedContinuations}) — for a caller holding material other than the whole
|
|
728
|
+
* question: the multi-hop walk asks with what of the question no product has
|
|
729
|
+
* restated yet. null when none is named. */
|
|
730
|
+
export function namedContinuations(ctx, id, asked) {
|
|
731
|
+
const nx = ctx.store.nextFirst(id, hubBound(ctx));
|
|
732
|
+
return nx.length === 0 ? null : askedContinuations(ctx, id, nx, asked);
|
|
733
|
+
}
|
|
734
|
+
/** The continuations of `id` (among `nx`) that the question NAMES: one of
|
|
735
|
+
* their establishing contexts — a predecessor other than `id` itself — is
|
|
736
|
+
* wholly witnessed by the question plus `id`'s own bytes (evidence.ts), with
|
|
737
|
+
* the question supplying at least one window the node does not. Ranked by
|
|
738
|
+
* how much of the question witnesses it; null when none is named.
|
|
739
|
+
*
|
|
740
|
+
* THE NODE'S OWN BYTES ARE MATERIAL because a derivation stands on them. On
|
|
741
|
+
* the second hop of `Where was the place of death of the director of film
|
|
742
|
+
* Beat Girl?` the node is `Edmond T. Gréville` — reached, never written — and
|
|
743
|
+
* its fact's establishing context `Edmond T. Gréville place of death` is held
|
|
744
|
+
* by neither the question nor the node, only by both. Measured over 5,236
|
|
745
|
+
* held-out 2Wiki questions: such a context is wholly witnessed by the
|
|
746
|
+
* question alone 69 times, by the question and the first hop 2,153 times.
|
|
747
|
+
*
|
|
748
|
+
* BOUNDED: predecessor reads share one √N budget across the candidates (see
|
|
749
|
+
* the loop below), asked cheapest first; past it the tier abstains for the
|
|
750
|
+
* rest, metered, and the ladder decides as before.
|
|
751
|
+
* Each form is read at most to the material's length — a form longer than
|
|
752
|
+
* everything at hand cannot be wholly witnessed without repeating it. */
|
|
753
|
+
function askedContinuations(ctx, id, nx, asked) {
|
|
754
|
+
return askedEntry(ctx, id, nx, asked).named;
|
|
755
|
+
}
|
|
756
|
+
/** The question spans that NAMED `pick` among `id`'s continuations — the
|
|
757
|
+
* evidence a projection through that pick stands on, so a mechanism can
|
|
758
|
+
* account for what the question said about it (mechanism-market.md:
|
|
759
|
+
* evidence travels). Empty when the question names no continuation of `id`
|
|
760
|
+
* or names others. */
|
|
761
|
+
export function askedEvidence(ctx, id, pick) {
|
|
762
|
+
const asked = ctx._edgeAsked;
|
|
763
|
+
if (asked === null)
|
|
764
|
+
return [];
|
|
765
|
+
const nx = ctx.store.nextFirst(id, hubBound(ctx));
|
|
766
|
+
const entry = askedEntry(ctx, id, nx, asked);
|
|
767
|
+
return entry.named?.includes(pick) ? entry.spans.get(pick) ?? [] : [];
|
|
768
|
+
}
|
|
769
|
+
function askedEntry(ctx, id, nx, asked) {
|
|
770
|
+
let memo = askedMemo.get(asked);
|
|
771
|
+
if (memo === undefined)
|
|
772
|
+
askedMemo.set(asked, memo = new Map());
|
|
773
|
+
const hit = memo.get(id);
|
|
774
|
+
if (hit !== undefined && !ctx.trace)
|
|
775
|
+
return hit;
|
|
776
|
+
const entry = askedContinuationsImpl(ctx, id, nx, asked);
|
|
777
|
+
memo.set(id, entry);
|
|
778
|
+
return entry;
|
|
779
|
+
}
|
|
780
|
+
/** One pick per node per question — every mechanism of a response asks the
|
|
781
|
+
* same node about the same question (the guided-pick memo's own reason). */
|
|
782
|
+
const askedMemo = new WeakMap();
|
|
783
|
+
function askedContinuationsImpl(ctx, id, nx, asked) {
|
|
784
|
+
const none = { named: null, spans: new Map() };
|
|
785
|
+
const W = ctx.space.maxGroup;
|
|
786
|
+
// A SATURATED READ IS NOT A CANDIDATE SET. When the continuations came back
|
|
787
|
+
// at the √N cap the read may have cut the named one off, so "none of these is
|
|
788
|
+
// named" and "this is the named one" are both unfounded — and this is exactly
|
|
789
|
+
// where witnessing would read most. The tier abstains, metered, and the
|
|
790
|
+
// distributional ladder decides as it always has.
|
|
791
|
+
if (nx.length >= hubBound(ctx)) {
|
|
792
|
+
if (ctx.meter)
|
|
793
|
+
ctx.meter.askedReadsSaturated++;
|
|
794
|
+
return none;
|
|
795
|
+
}
|
|
796
|
+
const cache = getStructCache(ctx);
|
|
797
|
+
const ownCap = asked.bytes.length * W;
|
|
798
|
+
const own = offsetCanon(ctx, read(ctx, id, ownCap));
|
|
799
|
+
const ownIndex = windowIndex(own, W);
|
|
800
|
+
// Naming needs the question to say at least one window the node does not:
|
|
801
|
+
// when the node already holds every window of the question (the question IS
|
|
802
|
+
// this context, or a piece of it), nothing can be named, and nothing is read.
|
|
803
|
+
let beyond = false;
|
|
804
|
+
for (const key of asked.index.keys()) {
|
|
805
|
+
if (!ownIndex.has(key)) {
|
|
806
|
+
beyond = true;
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (!beyond)
|
|
811
|
+
return none;
|
|
812
|
+
const indexes = [asked.index, ownIndex];
|
|
813
|
+
const formCap = asked.bytes.length + own.length;
|
|
814
|
+
// BOUNDED READS (bounded-reads.md): the decision reads at most √N
|
|
815
|
+
// establishing contexts — floored at the write side's own arity `chainReach(W)`
|
|
816
|
+
// so a store too small for √N to cover one fact's questions still decides.
|
|
817
|
+
// Candidates are asked CHEAPEST FIRST (fewest establishing contexts): a common
|
|
818
|
+
// reply established by hundreds of contexts would otherwise spend the whole
|
|
819
|
+
// allowance alone. The order changes what is READ, never what wins: scores
|
|
820
|
+
// are compared afterwards in the continuations' own order.
|
|
821
|
+
let budget = Math.max(hubBound(ctx), chainReach(W));
|
|
822
|
+
const order = nx
|
|
823
|
+
.map((n, at) => ({ n, at, support: cachedPrevCount(ctx, n, cache) }))
|
|
824
|
+
.filter((c) => c.support >= 2) // only `id` establishes the rest
|
|
825
|
+
.sort((a, b) => a.support - b.support || a.at - b.at);
|
|
826
|
+
const scored = [];
|
|
827
|
+
for (const { n, at, support } of order) {
|
|
828
|
+
if (support > budget) {
|
|
829
|
+
if (ctx.meter)
|
|
830
|
+
ctx.meter.askedReadsSaturated++;
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
budget -= support;
|
|
834
|
+
if (ctx.meter)
|
|
835
|
+
ctx.meter.askedPredecessorReads += support;
|
|
836
|
+
let score = 0;
|
|
837
|
+
let by = -1;
|
|
838
|
+
let spans = [];
|
|
839
|
+
for (const c of ctx.store.prevFirst(n, support)) {
|
|
840
|
+
if (c === id)
|
|
841
|
+
continue;
|
|
842
|
+
// The form's FIRST window decides most refusals: one short prefix read
|
|
843
|
+
// before the whole form is reconstructed (a conversation-length
|
|
844
|
+
// predecessor would otherwise be read in full to fail on its opening).
|
|
845
|
+
const head = offsetCanon(ctx, read(ctx, c, W));
|
|
846
|
+
if (head.length < W)
|
|
847
|
+
continue;
|
|
848
|
+
if (!indexes.some((ix) => ix.has(latin1(head))))
|
|
849
|
+
continue;
|
|
850
|
+
const form = read(ctx, c, formCap + 1);
|
|
851
|
+
if (form.length < W || form.length > formCap)
|
|
852
|
+
continue;
|
|
853
|
+
const w = witness(offsetCanon(ctx, form), indexes, W);
|
|
854
|
+
if (!w.complete || w.bytes < W)
|
|
855
|
+
continue;
|
|
856
|
+
if (w.bytes > score) {
|
|
857
|
+
score = w.bytes;
|
|
858
|
+
by = c;
|
|
859
|
+
spans = w.spans;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (score > 0)
|
|
863
|
+
scored.push({ n, at, score, by, spans });
|
|
864
|
+
}
|
|
865
|
+
scored.sort((a, b) => a.at - b.at);
|
|
866
|
+
let best = [];
|
|
867
|
+
let bestBytes = 0;
|
|
868
|
+
let witnessed = null;
|
|
869
|
+
for (const { n, score, by } of scored) {
|
|
870
|
+
if (score > bestBytes) {
|
|
871
|
+
best = [n];
|
|
872
|
+
bestBytes = score;
|
|
873
|
+
witnessed = by;
|
|
874
|
+
}
|
|
875
|
+
else if (score === bestBytes)
|
|
876
|
+
best.push(n);
|
|
877
|
+
}
|
|
878
|
+
if (best.length === 0)
|
|
879
|
+
return none;
|
|
880
|
+
if (ctx.meter)
|
|
881
|
+
ctx.meter.askedContinuations++;
|
|
882
|
+
if (ctx.trace && witnessed !== null) {
|
|
883
|
+
ctx.trace.step("askedContinuation", [rItemShort(ctx, id, "node"), rItemShort(ctx, witnessed, "asked")], best.map((n) => rItemShort(ctx, n, "named")), `${nx.length} continuations — the question witnesses ` +
|
|
884
|
+
`${best.length === 1 ? "one's" : `${best.length}'`} own establishing ` +
|
|
885
|
+
`context (${bestBytes} question byte(s) beyond the node)`);
|
|
886
|
+
}
|
|
887
|
+
const evidence = new Map();
|
|
888
|
+
for (const c of scored)
|
|
889
|
+
if (best.includes(c.n))
|
|
890
|
+
evidence.set(c.n, c.spans);
|
|
891
|
+
return { named: best, spans: evidence };
|
|
892
|
+
}
|
|
694
893
|
/** The perceived gist of a candidate node, through the session gist cache.
|
|
695
894
|
* Re-gisting a candidate is a full river fold of its bytes — the measured
|
|
696
895
|
* recall bottleneck (a hub context offers up to √N continuations, EACH
|
|
@@ -776,6 +975,84 @@ export function allWindowsAreScaffolding(ctx, query) {
|
|
|
776
975
|
}
|
|
777
976
|
return sawOne;
|
|
778
977
|
}
|
|
978
|
+
/** Per offset of `bytes`: 1 when the W-window there is a stored form contained
|
|
979
|
+
* in more than √N places (corpus-global scaffolding; see the floor below),
|
|
980
|
+
* else 0. Memoised per
|
|
981
|
+
* byte array for the life of the store's read-only response. */
|
|
982
|
+
export function hubWindows(ctx, bytes) {
|
|
983
|
+
const hit = hubWindowMemo.get(bytes);
|
|
984
|
+
if (hit !== undefined)
|
|
985
|
+
return hit;
|
|
986
|
+
const W = ctx.space.maxGroup;
|
|
987
|
+
// Floored at the write side's arity: inside ONE deposit's fold a window is
|
|
988
|
+
// already contained by up to `chainReach(W)` chunks and branches, so on a
|
|
989
|
+
// store of a few facts the √N reading would call every window frame — that
|
|
990
|
+
// is fold structure, not corpus commonality.
|
|
991
|
+
const bound = Math.max(hubBound(ctx), chainReach(W));
|
|
992
|
+
const hub = new Uint8Array(Math.max(0, bytes.length - W + 1));
|
|
993
|
+
for (let o = 0; o < hub.length; o++) {
|
|
994
|
+
const ids = leafIdRun(ctx, bytes, o, o + W);
|
|
995
|
+
const id = ids === null ? null : ctx.store.findBranch(ids);
|
|
996
|
+
if (id !== null && ctx.store.containersSlice(id, bound, 1).length > 0) {
|
|
997
|
+
hub[o] = 1;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
hubWindowMemo.set(bytes, hub);
|
|
1001
|
+
return hub;
|
|
1002
|
+
}
|
|
1003
|
+
const hubWindowMemo = new WeakMap();
|
|
1004
|
+
/** The query's SCAFFOLDING CORE, as spans: the bytes every W-window over which
|
|
1005
|
+
* is a hub (see {@link hubWindows}) — what is nothing but frame, where
|
|
1006
|
+
* {@link scaffoldExtents} is what a frame window reaches. */
|
|
1007
|
+
export function scaffoldSpans(ctx, query) {
|
|
1008
|
+
const W = ctx.space.maxGroup;
|
|
1009
|
+
const hub = hubWindows(ctx, query);
|
|
1010
|
+
const n = hub.length;
|
|
1011
|
+
if (n <= 0)
|
|
1012
|
+
return [];
|
|
1013
|
+
const spans = [];
|
|
1014
|
+
let start = -1;
|
|
1015
|
+
for (let i = 0; i < query.length; i++) {
|
|
1016
|
+
let all = true;
|
|
1017
|
+
for (let o = Math.max(0, i - W + 1); o <= Math.min(i, n - 1); o++) {
|
|
1018
|
+
if (!hub[o]) {
|
|
1019
|
+
all = false;
|
|
1020
|
+
break;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (all && start < 0)
|
|
1024
|
+
start = i;
|
|
1025
|
+
if (!all && start >= 0) {
|
|
1026
|
+
spans.push([start, i]);
|
|
1027
|
+
start = -1;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (start >= 0)
|
|
1031
|
+
spans.push([start, query.length]);
|
|
1032
|
+
return spans;
|
|
1033
|
+
}
|
|
1034
|
+
/** The EXTENTS of the query's SCAFFOLDING windows, merged: every byte some
|
|
1035
|
+
* W-window reaches that is a stored form contained in more than √N places —
|
|
1036
|
+
* corpus-global commonality (commonality.md), the same "hub" reading as
|
|
1037
|
+
* {@link allWindowsAreScaffolding} and the bridge's `explainedSpan`. A window
|
|
1038
|
+
* the store never saw is NOT scaffolding. The extent, not the core, is what
|
|
1039
|
+
* a coverage test needs: a span that still holds one hub window can be
|
|
1040
|
+
* "carried" by any fact that holds that window. */
|
|
1041
|
+
export function scaffoldExtents(ctx, query) {
|
|
1042
|
+
const W = ctx.space.maxGroup;
|
|
1043
|
+
const hub = hubWindows(ctx, query);
|
|
1044
|
+
const spans = [];
|
|
1045
|
+
for (let o = 0; o < hub.length; o++) {
|
|
1046
|
+
if (!hub[o])
|
|
1047
|
+
continue;
|
|
1048
|
+
const last = spans[spans.length - 1];
|
|
1049
|
+
if (last !== undefined && o <= last[1])
|
|
1050
|
+
last[1] = o + W;
|
|
1051
|
+
else
|
|
1052
|
+
spans.push([o, o + W]);
|
|
1053
|
+
}
|
|
1054
|
+
return spans;
|
|
1055
|
+
}
|
|
779
1056
|
// ── THE PREFIX SUPPLY ───────────────────────────────────────────────────────
|
|
780
1057
|
//
|
|
781
1058
|
// A RETRIEVAL capability, not a grounding one: "which trained forms does this
|
package/dist/src/mind/types.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { MindConfig } from "../config.js";
|
|
|
7
7
|
import type { Meter } from "../meter.js";
|
|
8
8
|
import type { GraphSearch, Leaf, Seg, Site } from "./graph-search.js";
|
|
9
9
|
import type { Rationale } from "./rationale.js";
|
|
10
|
+
import type { WindowIndex } from "./evidence.js";
|
|
10
11
|
import type { ContentFold, Grid } from "../geometry.js";
|
|
11
12
|
/** One {@link MindContext._depositTrees} entry — see that field's doc.
|
|
12
13
|
*
|
|
@@ -157,7 +158,13 @@ export interface Attention {
|
|
|
157
158
|
* a genuine further topic is named in its own distinctive wording
|
|
158
159
|
* somewhere the query's scaffolding does not reach, always a SEPARATE
|
|
159
160
|
* cluster from whatever else corroborates it. See
|
|
160
|
-
* test/37-cluster-dispersion-fusion.test.mjs.
|
|
161
|
+
* test/37-cluster-dispersion-fusion.test.mjs.
|
|
162
|
+
*
|
|
163
|
+
* Read from the VOTES, this is a lossy witness: a region votes once, for its
|
|
164
|
+
* top anchor, so a place can be lost to a tie or won through an accident.
|
|
165
|
+
* Fusion therefore also asks the root's CONTEXT the same question at window
|
|
166
|
+
* scale (reasoning.ts `sharedPlaces`) and trusts a root that either reading
|
|
167
|
+
* finds in two places. */
|
|
161
168
|
clusters: number;
|
|
162
169
|
}
|
|
163
170
|
/** Both read-outs of one consensus climb. */
|
|
@@ -364,6 +371,14 @@ export interface MindContext extends GraphSearchHost {
|
|
|
364
371
|
* ordinary respond() and for the first turn of a conversation. */
|
|
365
372
|
currentTurnStart: number;
|
|
366
373
|
_edgeGuide: Vec | null;
|
|
374
|
+
/** The question currently being answered, as the material `chooseNext`'s
|
|
375
|
+
* exact tier witnesses a continuation's establishing contexts against —
|
|
376
|
+
* its bytes (canonical when the response's canon preserves offsets) and
|
|
377
|
+
* their window index. Set and cleared with `_edgeGuide`. */
|
|
378
|
+
_edgeAsked: {
|
|
379
|
+
bytes: Uint8Array;
|
|
380
|
+
index: WindowIndex;
|
|
381
|
+
} | null;
|
|
367
382
|
_edgeChoice: Map<number, number>;
|
|
368
383
|
_prevSeen: Set<number> | null;
|
|
369
384
|
/** Session cache of node-id → perceived gist, for candidate scoring
|
|
@@ -28,6 +28,20 @@ export declare class SQliteStore extends AbstractStore implements Store {
|
|
|
28
28
|
private _bloom;
|
|
29
29
|
/** Dedup probes answered by the filter alone this session (observability). */
|
|
30
30
|
bloomSkips: number;
|
|
31
|
+
/** The same negative filter over the CANON index's key hashes. Recognition's
|
|
32
|
+
* canonical admission and the join's canonical entity scan ask `canonFind`
|
|
33
|
+
* once per probed span, and almost every answer is "no such key" — on an
|
|
34
|
+
* index that is often EMPTY (it is built only by `buildCanonIndex`).
|
|
35
|
+
* Loaded on first use from `canon_bloom` when its stamp matches the meta,
|
|
36
|
+
* else built by one sequential scan of the h column; kept exact on
|
|
37
|
+
* `canonAdd` (rebuilt bigger from the table, uncommitted rows included,
|
|
38
|
+
* when growth saturates it) and persisted with the commit that wrote the
|
|
39
|
+
* rows. The canon table is never deleted from, so the filter can never
|
|
40
|
+
* hold a false negative: a miss it reports is a miss the query would have
|
|
41
|
+
* returned. */
|
|
42
|
+
private _canonBloom;
|
|
43
|
+
/** The in-memory canon filter differs from the persisted one. */
|
|
44
|
+
private _canonBloomDirty;
|
|
31
45
|
private _insertNode;
|
|
32
46
|
private _insertKid;
|
|
33
47
|
private _selContain;
|
|
@@ -79,6 +93,9 @@ export declare class SQliteStore extends AbstractStore implements Store {
|
|
|
79
93
|
protected _dbGetNode(id: NodeId): NodeRec | null;
|
|
80
94
|
protected _dbFindLeaf(h: number, bytes: Uint8Array): NodeId | null;
|
|
81
95
|
protected _dbFindBranchByLeaf(h: number, bytes: Uint8Array): NodeId | null;
|
|
96
|
+
/** The node filter alone: never a false negative (every inserted hash is
|
|
97
|
+
* added before any probe can see it), so `false` is exact. */
|
|
98
|
+
protected _dbFlatMayExist(h: number, bytes: Uint8Array): boolean;
|
|
82
99
|
protected _dbFindBranchByKids(h: number, packed: Uint8Array): NodeId | null;
|
|
83
100
|
protected _dbInsertKid(child: NodeId, parent: NodeId): void;
|
|
84
101
|
protected _dbGetParents(id: NodeId): NodeId[];
|
|
@@ -137,6 +154,14 @@ export declare class SQliteStore extends AbstractStore implements Store {
|
|
|
137
154
|
protected _dbSetMeta(key: string, val: string): void;
|
|
138
155
|
protected _dbDeleteMeta(key: string): void;
|
|
139
156
|
canonAdd(h: number, id: number): void;
|
|
157
|
+
/** The canon filter: the persisted one when its stamp matches the meta,
|
|
158
|
+
* else built from the index as it stands. */
|
|
159
|
+
private _canonFilter;
|
|
160
|
+
private _canonScan;
|
|
161
|
+
/** What a persisted canon filter is stamped with: the incremental build's
|
|
162
|
+
* cursor, which every canon writer advances in the transaction it writes. */
|
|
163
|
+
private _canonStamp;
|
|
164
|
+
private _canonBloomPersist;
|
|
140
165
|
canonFind(h: number): number[];
|
|
141
166
|
sketchGet(id: number): number[] | null;
|
|
142
167
|
sketchPut(id: number, ids: readonly number[]): void;
|