@hviana/sema 0.3.0 → 0.4.1
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/CONTRIBUTING.md +92 -10
- package/LICENSE.md +2 -2
- 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 +0 -4
- package/dist/src/mind/attention.js +251 -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/junction.d.ts +10 -0
- package/dist/src/mind/junction.js +14 -0
- 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 +55 -4
- package/dist/src/mind/mind.js +110 -91
- package/dist/src/mind/pipeline-mechanism.d.ts +33 -3
- package/dist/src/mind/pipeline-mechanism.js +179 -10
- package/dist/src/mind/pipeline.js +52 -10
- 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 +58 -5
- package/dist/src/mind/types.d.ts +28 -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 +268 -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/junction.ts +12 -0
- 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 +139 -98
- package/src/mind/pipeline-mechanism.ts +203 -13
- package/src/mind/pipeline.ts +65 -8
- 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 +59 -5
- package/src/mind/types.ts +28 -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/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/dist/src/mind/bridge.js
CHANGED
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
import { cosine } from "../vec.js";
|
|
93
93
|
import { conceptThreshold, dominates } from "../geometry.js";
|
|
94
94
|
import { bytesEqual, indexOf } from "../bytes.js";
|
|
95
|
-
import { foldTree, perceive } from "./primitives.js";
|
|
95
|
+
import { foldTree, perceive, read } from "./primitives.js";
|
|
96
96
|
import { chainReach, leafIdRun } from "./canonical.js";
|
|
97
|
-
import { corpusN, edgeAncestors, hubBound } from "./traverse.js";
|
|
97
|
+
import { corpusN, edgeAncestors, hubBound, sharedReachMemo, } from "./traverse.js";
|
|
98
98
|
import { rItem, rNode } from "./trace.js";
|
|
99
99
|
/** True when some query byte-range left UNACCOUNTED by `spans` contains a
|
|
100
100
|
* STORED window — content the store has seen that the proposed reading
|
|
@@ -217,7 +217,22 @@ function align(ctx, q, c, qo, co) {
|
|
|
217
217
|
}
|
|
218
218
|
/** Recall's corroborated-substitution bridge — see the module comment.
|
|
219
219
|
* Returns the best bridged grounding proposal, or null. */
|
|
220
|
-
|
|
220
|
+
/** `proposed` is a THUNK, not a list: the bridge's own cheap gates (the
|
|
221
|
+
* two-quantum query floor and the O(|query|) stored-window anchor scan)
|
|
222
|
+
* decide whether ANY candidate can be aligned, and they need no proposals
|
|
223
|
+
* to do it. Resolving the caller's proposals eagerly meant recall paid its
|
|
224
|
+
* exhaustive whole-index resonance — the most expensive single act on the
|
|
225
|
+
* refusal path — for every query, including the ones whose windows the
|
|
226
|
+
* store has never seen and which the anchor scan rejects outright. Same
|
|
227
|
+
* investment discipline the mechanism floors follow (AGENTS §2.6): never
|
|
228
|
+
* compute a shared analysis just to discard it. */
|
|
229
|
+
export async function substitutionBridge(ctx, query, proposed = async () => []) {
|
|
230
|
+
const meter = ctx.meter;
|
|
231
|
+
return meter
|
|
232
|
+
? meter.time("substitutionBridge", () => bridgeImpl(ctx, query, proposed))
|
|
233
|
+
: bridgeImpl(ctx, query, proposed);
|
|
234
|
+
}
|
|
235
|
+
async function bridgeImpl(ctx, query, proposed) {
|
|
221
236
|
const W = ctx.space.maxGroup;
|
|
222
237
|
if (query.length < 2 * W)
|
|
223
238
|
return null;
|
|
@@ -225,6 +240,37 @@ export async function substitutionBridge(ctx, query, proposed = []) {
|
|
|
225
240
|
const N = corpusN(ctx);
|
|
226
241
|
const bar = conceptThreshold(ctx.store.D);
|
|
227
242
|
const reachCap = chainReach(W);
|
|
243
|
+
// PHRASE-SCALE CANDIDATE CAP — the same |content|·W bound the weave
|
|
244
|
+
// (pipeline-mechanism.ts), the cross-region junction ladder's
|
|
245
|
+
// `maxInterior`, and structural resonance's `maxSiblingBytes` all apply,
|
|
246
|
+
// for the same reason and now at the one remaining place that read
|
|
247
|
+
// candidate contexts WHOLE.
|
|
248
|
+
//
|
|
249
|
+
// The bridge accepts a candidate only when the query is DOMINATED by its
|
|
250
|
+
// matched runs plus substitutions, with at most one window W of slack at
|
|
251
|
+
// each edge and at most one chain reach (W²) per interior gap — so the
|
|
252
|
+
// candidate region an accepted alignment can ever consume is bounded by
|
|
253
|
+
// |query|·W. Content beyond that cannot participate in any alignment
|
|
254
|
+
// this function would accept; reading it is pure cost. And a candidate
|
|
255
|
+
// an order of magnitude past the query is not a paraphrase of it: it is a
|
|
256
|
+
// document or a whole conversation that merely quotes a phrase, and
|
|
257
|
+
// grounding through ITS learnt edge voices that document's continuation,
|
|
258
|
+
// not a phrase answer.
|
|
259
|
+
//
|
|
260
|
+
// Measured on the 17.7M-node / 325K-context store: uncapped, the refusal
|
|
261
|
+
// path materialised up to ~1 MB of candidate bytes per query (up to √N
|
|
262
|
+
// proposals plus √N climbed contexts, each read in full), and the frame-
|
|
263
|
+
// unanimity scan — which walks EVERY collected candidate's bytes, inside
|
|
264
|
+
// the per-gap expansion loop — paid that volume back tens of times per
|
|
265
|
+
// substitution. Recall's run() was 0.7–2.6 s per refusing query.
|
|
266
|
+
const capBytes = query.length * W;
|
|
267
|
+
/** A candidate's bytes, phrase-scale capped: null when it exceeds the cap
|
|
268
|
+
* (read one byte past it, so "too long" is decided without materialising
|
|
269
|
+
* the rest) or has no content. */
|
|
270
|
+
const candidateBytes = (sid) => {
|
|
271
|
+
const b = read(ctx, sid, capBytes + 1);
|
|
272
|
+
return b.length === 0 || b.length > capBytes ? null : b;
|
|
273
|
+
};
|
|
228
274
|
// 1. The query's stored windows, rarest first (fewest containers — the
|
|
229
275
|
// most discriminative anchors; hub-clamped like every fan-out read).
|
|
230
276
|
// The scan doubles as the ONE store probe of every query window: the
|
|
@@ -281,6 +327,55 @@ export async function substitutionBridge(ctx, query, proposed = []) {
|
|
|
281
327
|
}
|
|
282
328
|
return false;
|
|
283
329
|
};
|
|
330
|
+
// ── EXPLAINED SPANS — the scaffolding judgement, corpus-global ──────────
|
|
331
|
+
//
|
|
332
|
+
// The question every gap poses is "may the two forms differ HERE without
|
|
333
|
+
// differing in what they SAY?", and that is the discriminative-vs-
|
|
334
|
+
// scaffolding question AGENTS §2.7 names, over the CORPUS-GLOBAL
|
|
335
|
+
// population. It already has one definition — `dominates(reachOf(...), N)`,
|
|
336
|
+
// the same gate confluence's filler test uses ("scaffolding never binds").
|
|
337
|
+
// Nothing new is derived here; the bar is read, not invented.
|
|
338
|
+
//
|
|
339
|
+
// A span is explained when EITHER
|
|
340
|
+
// • it is sub-quantum (< W) — typographic glue, the tolerance identityBar
|
|
341
|
+
// already prices ("below one river window, byte overlap is chance"); or
|
|
342
|
+
// • every full W-window inside it is COMMON by the store's own climb:
|
|
343
|
+
// the ascent SATURATES (the window sits in more places than √N — the
|
|
344
|
+
// climb's own definition of non-discriminative), or it resolves to a
|
|
345
|
+
// majority of the corpus's contexts. "the process of ", " is the ".
|
|
346
|
+
//
|
|
347
|
+
// THE READING MATTERS, not just the population (AGENTS §2.7). This
|
|
348
|
+
// deliberately does NOT go through `reachOf`, which maps BOTH "saturated"
|
|
349
|
+
// and "reaches nothing" to Infinity. For IDF weighting those are the same
|
|
350
|
+
// thing (no usable identity evidence); for THIS question they are
|
|
351
|
+
// opposites — a window reaching nothing is novel content, the most
|
|
352
|
+
// discriminative material there is, and reading it as Infinity would call
|
|
353
|
+
// it scaffolding. Measured: with `reachOf`, "Is water wet?" was answered
|
|
354
|
+
// with "No, heavy water is not wet." — "heav"/"eavy" occur once, reach no
|
|
355
|
+
// edge-bearing ancestor, and were written off as filler. So an
|
|
356
|
+
// empty-rooted window is NEVER explained, and neither is an untrained one
|
|
357
|
+
// (the same principle attestedQ applies to the query side).
|
|
358
|
+
const reachMemo = sharedReachMemo(ctx);
|
|
359
|
+
const explainedSpan = (bytes, from, to) => {
|
|
360
|
+
if (to - from < W)
|
|
361
|
+
return true;
|
|
362
|
+
for (let o = from; o + W <= to; o++) {
|
|
363
|
+
const ids = leafIdRun(ctx, bytes, o, o + W);
|
|
364
|
+
if (ids === null)
|
|
365
|
+
return false;
|
|
366
|
+
const wid = ctx.store.findBranch(ids);
|
|
367
|
+
if (wid === null)
|
|
368
|
+
return false;
|
|
369
|
+
const r = edgeAncestors(ctx, wid, N, reachMemo);
|
|
370
|
+
if (r.saturated)
|
|
371
|
+
continue; // in too many places to discriminate
|
|
372
|
+
if (r.roots.length === 0)
|
|
373
|
+
return false; // reaches nothing: novel content
|
|
374
|
+
if (!dominates(r.contextsReached, N))
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
return true;
|
|
378
|
+
};
|
|
284
379
|
anchors.sort((a, b) => a.rarity - b.rarity);
|
|
285
380
|
// Up to W anchors, at least one window apart — the quantum's own count.
|
|
286
381
|
const picked = [];
|
|
@@ -311,12 +406,14 @@ export async function substitutionBridge(ctx, query, proposed = []) {
|
|
|
311
406
|
// that could align at all: alignment can only seed at a picked anchor
|
|
312
407
|
// window occurring literally in the candidate (measured: unconditional
|
|
313
408
|
// re-folds multiplied the refusal-path latency several-fold).
|
|
314
|
-
|
|
409
|
+
// FIRST TOUCH of the caller's proposals — past every gate that could have
|
|
410
|
+
// refused without them (see substitutionBridge's doc).
|
|
411
|
+
for (const sid of await proposed()) {
|
|
315
412
|
if (seen.has(sid))
|
|
316
413
|
continue;
|
|
317
414
|
seen.add(sid);
|
|
318
|
-
const tb =
|
|
319
|
-
if (tb
|
|
415
|
+
const tb = candidateBytes(sid);
|
|
416
|
+
if (tb === null)
|
|
320
417
|
continue;
|
|
321
418
|
if (!picked.some((a) => indexOf(tb, query.subarray(a.off, a.off + W), 0) >= 0))
|
|
322
419
|
continue;
|
|
@@ -353,9 +450,15 @@ export async function substitutionBridge(ctx, query, proposed = []) {
|
|
|
353
450
|
break;
|
|
354
451
|
}
|
|
355
452
|
// 3. Align each candidate; gate its mismatches; keep the best.
|
|
453
|
+
// Over-cap candidates are dropped here rather than earlier: the climb
|
|
454
|
+
// channel deliberately reads no bytes while collecting (the climb visits
|
|
455
|
+
// hundreds of roots), so this is where its proposals are first sized.
|
|
356
456
|
const allBytes = new Map();
|
|
357
|
-
for (const sid of candidates)
|
|
358
|
-
|
|
457
|
+
for (const sid of candidates) {
|
|
458
|
+
const b = candidateBytes(sid);
|
|
459
|
+
if (b !== null)
|
|
460
|
+
allBytes.set(sid, b);
|
|
461
|
+
}
|
|
359
462
|
// FRAME UNANIMITY: a substitution U → C inside the frame (Lf, Rf) is
|
|
360
463
|
// groundable only when the collected candidates — the store's own sample
|
|
361
464
|
// of contexts sharing the query's content — are unanimous about the
|
|
@@ -408,7 +511,7 @@ export async function substitutionBridge(ctx, query, proposed = []) {
|
|
|
408
511
|
let bestAccounted = 0;
|
|
409
512
|
for (const sid of candidates) {
|
|
410
513
|
const cBytes = allBytes.get(sid);
|
|
411
|
-
if (cBytes
|
|
514
|
+
if (cBytes === undefined)
|
|
412
515
|
continue;
|
|
413
516
|
// Seed at the rarest picked anchor that literally occurs in this
|
|
414
517
|
// candidate.
|
|
@@ -520,7 +623,7 @@ export async function substitutionBridge(ctx, query, proposed = []) {
|
|
|
520
623
|
// resonance rank but has no outgoing edge to bridge through). This
|
|
521
624
|
// mechanism exists to explain SUBSTITUTIONS; a query needing none is
|
|
522
625
|
// recall's job, not the bridge's.
|
|
523
|
-
if (!ok
|
|
626
|
+
if (!ok)
|
|
524
627
|
continue;
|
|
525
628
|
// Coverage: matched runs plus accepted substitutions must dominate the
|
|
526
629
|
// query, every interior gap already proved ≤ W above, and the EDGES
|
|
@@ -546,6 +649,72 @@ export async function substitutionBridge(ctx, query, proposed = []) {
|
|
|
546
649
|
continue;
|
|
547
650
|
if (!dominates(covered, query.length))
|
|
548
651
|
continue;
|
|
652
|
+
// ZERO-SUBSTITUTION ADMISSION — an IDENTITY claim, not a substitution.
|
|
653
|
+
//
|
|
654
|
+
// A candidate needing no substitution is normally refused (see the trap
|
|
655
|
+
// above), and that refusal is right for the case it was written for: the
|
|
656
|
+
// query is a strict byte-PREFIX of several candidates, each of which
|
|
657
|
+
// continues differently, and nothing here corroborates picking one
|
|
658
|
+
// continuation over another. But that trap has a signature — the
|
|
659
|
+
// candidate carries substantial content BEYOND the alignment, and that
|
|
660
|
+
// surplus is exactly the "answer" the bridge would be inventing.
|
|
661
|
+
//
|
|
662
|
+
// The opposite shape is not ambiguous at all: the alignment explains BOTH
|
|
663
|
+
// strings end to end, and the only thing between them is sub-quantum glue
|
|
664
|
+
// — typographic punctuation the fold treats as structure. Then the two
|
|
665
|
+
// are the SAME learnt form, and grounding through its edge returns that
|
|
666
|
+
// form's own trained answer, never a chosen-among-many completion.
|
|
667
|
+
//
|
|
668
|
+
// Why the ladder cannot reach these otherwise: the gist is a STRUCTURAL
|
|
669
|
+
// signature, so a mid-string insertion shifts every fold boundary after
|
|
670
|
+
// it. Measured: `Who wrote Romeo and Juliet?` against the trained
|
|
671
|
+
// `Who wrote "Romeo and Juliet"?` — two inserted quote characters — scores
|
|
672
|
+
// cos 0.377, BELOW unrelated neighbours like "Who wrote the opera
|
|
673
|
+
// Carmen??" (0.603). Recall's identity tiers gate on identityBar (0.969
|
|
674
|
+
// here) and its reach tiers on 0.875, so no gist-based tier can ever see
|
|
675
|
+
// it; only byte-exact alignment can, which is what this function does.
|
|
676
|
+
//
|
|
677
|
+
// The claim is deliberately strict, in three parts:
|
|
678
|
+
//
|
|
679
|
+
// • QUERY SIDE — EXACT. Every byte of the query must be a literal
|
|
680
|
+
// match against the candidate: covered === query.length, no slack at
|
|
681
|
+
// all, not even sub-quantum. The query is what we are answering, so
|
|
682
|
+
// an identity claim about it may write off NOTHING. This is stricter
|
|
683
|
+
// than the ≤ W edge tolerance the substituted path uses, and it has
|
|
684
|
+
// to be: with a one-window allowance, `what is 2^10?` matched the
|
|
685
|
+
// trained `what is 2+2?` — "^10" against "+2", four bytes, both sides
|
|
686
|
+
// below W — and answered "2+2 is 4.", outweighing cover's authoritative
|
|
687
|
+
// ALU result. Below W, byte OVERLAP is chance rather than evidence;
|
|
688
|
+
// that never made a below-W DIFFERENCE meaningless, and digits are the
|
|
689
|
+
// case that proves it.
|
|
690
|
+
// • CANDIDATE SIDE, INTERIOR — each gap must be an EXPLAINED span (see
|
|
691
|
+
// explainedSpan): sub-quantum glue, or corpus-global scaffolding.
|
|
692
|
+
// This side is asymmetric ON PURPOSE. Material the CANDIDATE has and
|
|
693
|
+
// the query omits is not something the asker asked about: if it is
|
|
694
|
+
// scaffolding, dropping it changes nothing ("What is *the process of*
|
|
695
|
+
// photosynthesis?"); if it is discriminative, the candidate answers a
|
|
696
|
+
// DIFFERENT, narrower question ("Is *heavy* water wet?") and must be
|
|
697
|
+
// refused. Only the corpus can tell those apart, and it does.
|
|
698
|
+
// • CANDIDATE SIDE, SURPLUS — its bytes are the matched runs
|
|
699
|
+
// (byte-identical to the query's, hence the same total length) plus
|
|
700
|
+
// its own gap spans; anything past that is surplus, and surplus is
|
|
701
|
+
// the prefix trap. A prefix-completion candidate fails here by the
|
|
702
|
+
// whole length of the completion it wanted to supply — which is why
|
|
703
|
+
// admitting scaffolding interiors does not reopen that trap.
|
|
704
|
+
//
|
|
705
|
+
// Ordered cheapest-first: the two arithmetic tests run before
|
|
706
|
+
// explainedSpan, whose per-window `reachOf` climbs are the only costly
|
|
707
|
+
// part (shared through the response/conversation reach memo, and reached
|
|
708
|
+
// only by a candidate that already survived every structural gate).
|
|
709
|
+
if (subs.length === 0) {
|
|
710
|
+
if (covered !== query.length)
|
|
711
|
+
continue;
|
|
712
|
+
const cGap = gaps.reduce((n, g) => n + (g.ce - g.cs), 0);
|
|
713
|
+
if (cBytes.length - covered - cGap > W)
|
|
714
|
+
continue;
|
|
715
|
+
if (!gaps.every((g) => explainedSpan(cBytes, g.cs, g.ce)))
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
549
718
|
// KNOWN content may never be dismissed — see dismissedKnownContent
|
|
550
719
|
// (the live case: "what is the capital of france" aligning into a
|
|
551
720
|
// Matrix synopsis by writing off "ance" — a stored window of the
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { MindContext } from "./types.js";
|
|
2
2
|
/** The two sliding-window lengths the WRITE side interns over a stream's leaf
|
|
3
3
|
* ids: W−1 and W (the river's grouping quantum and its off-by-one neighbour,
|
|
4
|
-
* so a form straddling a group boundary is reachable from either cut).
|
|
4
|
+
* so a form straddling a group boundary is reachable from either cut).
|
|
5
|
+
*
|
|
6
|
+
* Widening this to cover the reader's full segment scale (W−1..2W) was
|
|
7
|
+
* measured and REFUTED: it tripled the store (3,160 → 8,980 nodes on a
|
|
8
|
+
* 200-pair corpus), slowed ingest 80%, and fixed not one test. Unknown
|
|
9
|
+
* regions are not caused by the index's scale. */
|
|
5
10
|
export declare function canonicalWindows(W: number): [number, number];
|
|
6
11
|
/** The READ side's chain reach: how many leaf ids a canonical chain may grow
|
|
7
12
|
* to from one position — W², the deepest two-level composite the write side's
|
|
@@ -24,7 +24,12 @@
|
|
|
24
24
|
// what training indexed, which no type checker catches.
|
|
25
25
|
/** The two sliding-window lengths the WRITE side interns over a stream's leaf
|
|
26
26
|
* ids: W−1 and W (the river's grouping quantum and its off-by-one neighbour,
|
|
27
|
-
* so a form straddling a group boundary is reachable from either cut).
|
|
27
|
+
* so a form straddling a group boundary is reachable from either cut).
|
|
28
|
+
*
|
|
29
|
+
* Widening this to cover the reader's full segment scale (W−1..2W) was
|
|
30
|
+
* measured and REFUTED: it tripled the store (3,160 → 8,980 nodes on a
|
|
31
|
+
* 200-pair corpus), slowed ingest 80%, and fixed not one test. Unknown
|
|
32
|
+
* regions are not caused by the index's scale. */
|
|
28
33
|
export function canonicalWindows(W) {
|
|
29
34
|
return [W - 1, W];
|
|
30
35
|
}
|
|
@@ -143,6 +143,15 @@ export declare class GraphSearch {
|
|
|
143
143
|
* recursive completion), and chooseNext (distributional-evidence edge
|
|
144
144
|
* disambiguation when a recognised form has multiple continuations). */
|
|
145
145
|
host: GraphSearchHost);
|
|
146
|
+
/** The hub bound √N (AGENTS §2.8) — the ONE fan-out cap, stated here
|
|
147
|
+
* rather than imported from `traverse.ts` because this module is
|
|
148
|
+
* deliberately host-based (it holds a bare Store, never a MindContext).
|
|
149
|
+
* That is the same write/read-side duplication convention canonical.ts's
|
|
150
|
+
* header documents: if the formula changes it must change in BOTH places.
|
|
151
|
+
* It is stated ONCE per side, though — the expression used to be spelled
|
|
152
|
+
* out at three call sites here, one of them inside a per-item rules
|
|
153
|
+
* generator, and they had already drifted on the `Math.max(2, …)` floor. */
|
|
154
|
+
private hubBound;
|
|
146
155
|
/** Explore the Sema graph for the lightest cover of the query and return its
|
|
147
156
|
* chosen spans left-to-right — WITH the derivation's total weight (the g
|
|
148
157
|
* value of the goal item, in the exported cost ladder), which think's
|
|
@@ -190,6 +190,17 @@ export class GraphSearch {
|
|
|
190
190
|
this.maxGroup = maxGroup;
|
|
191
191
|
this.host = host;
|
|
192
192
|
}
|
|
193
|
+
/** The hub bound √N (AGENTS §2.8) — the ONE fan-out cap, stated here
|
|
194
|
+
* rather than imported from `traverse.ts` because this module is
|
|
195
|
+
* deliberately host-based (it holds a bare Store, never a MindContext).
|
|
196
|
+
* That is the same write/read-side duplication convention canonical.ts's
|
|
197
|
+
* header documents: if the formula changes it must change in BOTH places.
|
|
198
|
+
* It is stated ONCE per side, though — the expression used to be spelled
|
|
199
|
+
* out at three call sites here, one of them inside a per-item rules
|
|
200
|
+
* generator, and they had already drifted on the `Math.max(2, …)` floor. */
|
|
201
|
+
hubBound() {
|
|
202
|
+
return Math.ceil(Math.sqrt(Math.max(2, this.store.edgeSourceCount())));
|
|
203
|
+
}
|
|
193
204
|
/** Explore the Sema graph for the lightest cover of the query and return its
|
|
194
205
|
* chosen spans left-to-right — WITH the derivation's total weight (the g
|
|
195
206
|
* value of the goal item, in the exported cost ladder), which think's
|
|
@@ -240,7 +251,17 @@ export class GraphSearch {
|
|
|
240
251
|
* that leads nowhere new, never at an arbitrary count. */
|
|
241
252
|
solve(spanLen, recognition, conceptTarget, substitutions, connectors, computedResults, onDerivation) {
|
|
242
253
|
const system = this.buildSearch(spanLen, recognition.sites, conceptTarget, recognition.leaves, recognition.splits, recognition.starts, substitutions, connectors, computedResults);
|
|
243
|
-
|
|
254
|
+
// Search-effort accounting (src/meter.ts): the chart's pops/pushes are
|
|
255
|
+
// the cover's real cost, and a heuristic that stops being admissible
|
|
256
|
+
// shows up as a pop count that explodes while the answer stays the same.
|
|
257
|
+
const meter = this.host.meter;
|
|
258
|
+
const stats = meter ? { pops: 0, pushes: 0 } : undefined;
|
|
259
|
+
const derivation = lightestDerivation(system, stats);
|
|
260
|
+
if (meter && stats) {
|
|
261
|
+
meter.searches++;
|
|
262
|
+
meter.searchPops += stats.pops;
|
|
263
|
+
meter.searchPushes += stats.pushes;
|
|
264
|
+
}
|
|
244
265
|
// When covering under a substitution map (articulation), a form→out rule is
|
|
245
266
|
// the form EMITTING the asker's voice, not grounding to its own answer — so
|
|
246
267
|
// tell the reader to name those moves `voice` rather than `ground`.
|
|
@@ -262,12 +283,9 @@ export class GraphSearch {
|
|
|
262
283
|
buildSearch(queryLen, sites, conceptTarget, leaves, splits, starts, substitutions, connectors, computedResults) {
|
|
263
284
|
const W = this.maxGroup; // fusible span ceiling (shortest composite bound)
|
|
264
285
|
// Same corpus-scale hub floor {@link atomIsHub}/{@link atomReach} (traverse.ts)
|
|
265
|
-
// derive for byte atoms —
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
// the formula ever changes, it must change in BOTH places (see canonical.ts's
|
|
269
|
-
// header for the same write/read-side duplication convention).
|
|
270
|
-
const atomsAreHubs = Math.max(1, Math.ceil((this.store.edgeSourceCount() * W) / 256)) > Math.ceil(Math.sqrt(Math.max(2, this.store.edgeSourceCount())));
|
|
286
|
+
// derive for byte atoms — see {@link hubBound} below for why this module
|
|
287
|
+
// states the formula itself instead of importing it.
|
|
288
|
+
const atomsAreHubs = Math.max(1, Math.ceil((this.store.edgeSourceCount() * W) / 256)) > this.hubBound();
|
|
271
289
|
const nodeBytes = (n) => this.store.bytesPrefix(n, ALL);
|
|
272
290
|
// Content-addressed probes over the store's hash-cons maps — the same keys
|
|
273
291
|
// training filled. No byte-by-byte trie walk.
|
|
@@ -511,8 +529,7 @@ export class GraphSearch {
|
|
|
511
529
|
// guard then dead-ends it) with no way to reach the forward edge.
|
|
512
530
|
// Forking offers every continuation as its own rule so the one that
|
|
513
531
|
// genuinely advances (not a duplicate) is still reachable.
|
|
514
|
-
const
|
|
515
|
-
const nx = this.store.nextFirst(it.node, bound);
|
|
532
|
+
const nx = this.store.nextFirst(it.node, this.hubBound());
|
|
516
533
|
if (nx.length) {
|
|
517
534
|
// The SAME evidence-weighted disambiguation the first hop uses
|
|
518
535
|
// (below) identifies the most-corroborated continuation. Yielding
|
|
@@ -861,16 +878,39 @@ export class GraphSearch {
|
|
|
861
878
|
// is opportunistic cross-leaf recovery exactly like recognition.ts's own
|
|
862
879
|
// canonical chain — findLeaf/findBranch here have no idea WHY these two
|
|
863
880
|
// leaves are adjacent, only that their concatenation happens to spell a
|
|
864
|
-
// trained form ("hi" recovered from "W[hi]ch").
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
//
|
|
868
|
-
//
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
|
|
881
|
+
// trained form ("hi" recovered from "W[hi]ch"). At hub scale, where
|
|
882
|
+
// atoms themselves no longer discriminate, that coincidence is noise.
|
|
883
|
+
//
|
|
884
|
+
// A FOLD BOUNDARY IS NOT EVIDENCE. This gate used to exempt any fuse
|
|
885
|
+
// starting at `starts.has(l.i)` — documented as "a position the query's
|
|
886
|
+
// OWN fold chose as a boundary (real structural evidence)". The plain
|
|
887
|
+
// river fold CHOOSES NOTHING: `riverFold` groups fixed-arity
|
|
888
|
+
// (`for (i = 0; i < complete; i += mg)`), so `starts` is exactly
|
|
889
|
+
// {0, W, 2W, 3W, …} for every query. Measured on three unrelated
|
|
890
|
+
// queries: starts = 0,4,8,12,16,20,24,28[,32] in every case — the set
|
|
891
|
+
// carries ZERO content information, and the exemption therefore fired at
|
|
892
|
+
// a quarter of all offsets by arithmetic alone.
|
|
893
|
+
//
|
|
894
|
+
// What it cost (17.9M-node store, W=4): "In which country is the Eiffel
|
|
895
|
+
// Tower?" fused the atoms "h"+"i" at offset 4 — trusted only because
|
|
896
|
+
// 4 ≡ 0 (mod 4) — into the trained form "hi", followed its continuation
|
|
897
|
+
// edge, and grounded "Hello there. If you are looking for Open
|
|
898
|
+
// Assistant, look no further." as a FACT, explaining 2 of 37 bytes
|
|
899
|
+
// (density 0.054, a fifth of the 1/W honesty bar `thinGrounding`
|
|
900
|
+
// reports and does not enforce).
|
|
901
|
+
//
|
|
902
|
+
// So the exemption is removed rather than replaced: there is no cheap
|
|
903
|
+
// signal here that means what it claimed to mean, and inventing one
|
|
904
|
+
// would be worse than admitting the absence. Genuine cross-leaf forms
|
|
905
|
+
// are not lost — recognition.ts's canonical pass already probes EVERY
|
|
906
|
+
// byte offset for the write side's interned windows and emits them as
|
|
907
|
+
// sites, which arrive here as `l.rec`/`r.rec` and stay exempt. What
|
|
908
|
+
// remains excluded at hub scale is precisely the case with no evidence
|
|
909
|
+
// behind it: two byte atoms that happen to spell something.
|
|
910
|
+
//
|
|
911
|
+
// Below hub scale nothing changes (`!atomsAreHubs`): on a small store
|
|
912
|
+
// coincidence is rare and every chain is real evidence.
|
|
913
|
+
const trusted = l.rec || r.rec || !ctx.atomsAreHubs;
|
|
874
914
|
let node = (trusted && bytes.length <= ctx.W)
|
|
875
915
|
? ctx.findLeafU(bytes)
|
|
876
916
|
: undefined;
|
|
@@ -85,6 +85,16 @@ export declare function cachedRead(ctx: MindContext, cache: WalkCache | null, id
|
|
|
85
85
|
* few levels of its parts). A side too common to decide within the
|
|
86
86
|
* budget abstains here and falls through to the resonance tier (the
|
|
87
87
|
* climb's own saturation semantics).
|
|
88
|
+
*
|
|
89
|
+
* REFUTED TIGHTENING (measured, 325K contexts): giving this walk
|
|
90
|
+
* edgeAncestors' cumulative LATERAL-CONE limit — abstain once the
|
|
91
|
+
* accumulated lateral entries pass √N — would cut most of the pops (47
|
|
92
|
+
* of 56 walks exceed √N lateral, and 47 exhaust the budget), but TWO OF
|
|
93
|
+
* THE FOUR walks that actually found a container had lateral spread of
|
|
94
|
+
* 1425 and 1426, far past √N. The cone limit is sound for
|
|
95
|
+
* edgeAncestors' question and wrong for this one: a junction container
|
|
96
|
+
* is legitimately reached across many containing structures. Half the
|
|
97
|
+
* successful junctions would be lost.
|
|
88
98
|
* • per-node hub guards — parent fan-outs beyond √N are hubs (not
|
|
89
99
|
* expanded); each node contributes at most one √N page of containers;
|
|
90
100
|
* √N collected candidates decide. */
|
|
@@ -115,6 +115,16 @@ function cachedContainers(ctx, cache, id, limit) {
|
|
|
115
115
|
* few levels of its parts). A side too common to decide within the
|
|
116
116
|
* budget abstains here and falls through to the resonance tier (the
|
|
117
117
|
* climb's own saturation semantics).
|
|
118
|
+
*
|
|
119
|
+
* REFUTED TIGHTENING (measured, 325K contexts): giving this walk
|
|
120
|
+
* edgeAncestors' cumulative LATERAL-CONE limit — abstain once the
|
|
121
|
+
* accumulated lateral entries pass √N — would cut most of the pops (47
|
|
122
|
+
* of 56 walks exceed √N lateral, and 47 exhaust the budget), but TWO OF
|
|
123
|
+
* THE FOUR walks that actually found a container had lateral spread of
|
|
124
|
+
* 1425 and 1426, far past √N. The cone limit is sound for
|
|
125
|
+
* edgeAncestors' question and wrong for this one: a junction container
|
|
126
|
+
* is legitimately reached across many containing structures. Half the
|
|
127
|
+
* successful junctions would be lost.
|
|
118
128
|
* • per-node hub guards — parent fan-outs beyond √N are hubs (not
|
|
119
129
|
* expanded); each node contributes at most one √N page of containers;
|
|
120
130
|
* √N collected candidates decide. */
|
|
@@ -137,6 +147,8 @@ unordered = false) {
|
|
|
137
147
|
if (seeds.length === 0)
|
|
138
148
|
return [];
|
|
139
149
|
const b = budget ?? { n: bound * ctx.space.maxGroup };
|
|
150
|
+
if (ctx.meter)
|
|
151
|
+
ctx.meter.junctionWalks++;
|
|
140
152
|
// DEPTH CAP: perception trees are W-ary and a junction container is
|
|
141
153
|
// phrase-scale, so it sits within ~log_W(maxContainer) structural levels
|
|
142
154
|
// of its parts — at most W levels for any practical W (plus the
|
|
@@ -153,6 +165,8 @@ unordered = false) {
|
|
|
153
165
|
}));
|
|
154
166
|
while (stack.length > 0 && out.length < bound && b.n-- > 0) {
|
|
155
167
|
const { id: x, d } = stack.pop();
|
|
168
|
+
if (ctx.meter)
|
|
169
|
+
ctx.meter.junctionPops++;
|
|
156
170
|
const f = cachedRead(ctx, cache, x, maxContainer);
|
|
157
171
|
if (f.length > maxContainer)
|
|
158
172
|
continue; // beyond phrase scale: prune branch
|
package/dist/src/mind/match.d.ts
CHANGED
|
@@ -34,6 +34,13 @@ export declare function alignRuns(ctx: MindContext, query: Uint8Array, ct: Uint8
|
|
|
34
34
|
* kind, so the substitution/redirection schemas work unchanged on conceptual
|
|
35
35
|
* alignment. */
|
|
36
36
|
export interface GradedRun {
|
|
37
|
+
/** True for a run the CLIMB proposed rather than the byte matcher found — see
|
|
38
|
+
* {@link computeWeave}'s phase 2. Provenance only: a proposed run reaches
|
|
39
|
+
* CAST already gated (dominant literal agreement, not frame, and over bytes
|
|
40
|
+
* no literal run claimed), so every consumer reads it exactly like any other
|
|
41
|
+
* run. Filtering seat-shaped reads by this flag was tried and is NOT
|
|
42
|
+
* needed — the gates, not the flag, are what make it safe. */
|
|
43
|
+
proposed?: boolean;
|
|
37
44
|
qs: number;
|
|
38
45
|
qe: number;
|
|
39
46
|
cs: number;
|
|
@@ -124,3 +131,36 @@ export declare function reverseContext(ctx: MindContext, id: number, guide?: Vec
|
|
|
124
131
|
* the context it follows. This is the direction ladder every mechanism's
|
|
125
132
|
* final grounding step reduces to. */
|
|
126
133
|
export declare function project(ctx: MindContext, id: number, guide?: Vec | null): Promise<Uint8Array | null>;
|
|
134
|
+
/** Check whether an anchor is a span-shaped skill exemplar: it represents a
|
|
135
|
+
* fact whose context and answer together form a span-in-context pattern.
|
|
136
|
+
* If the anchor has a nextOf continuation, that is the answer and the anchor
|
|
137
|
+
* itself is the context. Otherwise the anchor's prevOf parents provide
|
|
138
|
+
* candidate contexts, and the longest one whose span is span-shaped wins. */
|
|
139
|
+
export declare function skillExemplar(ctx: MindContext, anchor: number, guide?: Vec | null): Promise<{
|
|
140
|
+
contextBytes: Uint8Array;
|
|
141
|
+
answerBytes: Uint8Array;
|
|
142
|
+
} | null>;
|
|
143
|
+
/** Whether the answer is a SPARSE subsequence of the context (bytes in
|
|
144
|
+
* order, arbitrary gaps) — the OPEN span-shape reading (see the section
|
|
145
|
+
* note above). This is what lets extraction validate a MULTI-PIECE
|
|
146
|
+
* exemplar whose answer is stitched from several context runs — but it is
|
|
147
|
+
* deliberately permissive, so it must never be used as evidence that one
|
|
148
|
+
* span was "drawn from" another (see {@link containsSpan} for that).
|
|
149
|
+
*
|
|
150
|
+
* There is deliberately NO containsSpan pre-check here: strict containment
|
|
151
|
+
* IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
|
|
152
|
+
* whose content-addressed identity means its bytes occur contiguously — is
|
|
153
|
+
* an in-order embedding with zero gaps), so the scan below decides alone,
|
|
154
|
+
* with the same truth value. The old pre-check re-perceived the context
|
|
155
|
+
* (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
|
|
156
|
+
* pure cost, no discrimination. */
|
|
157
|
+
export declare function isSpanShaped(_ctx: MindContext, context: Uint8Array, answer: Uint8Array): boolean;
|
|
158
|
+
/** STRICT containment: the answer's resolved node appears in the context's
|
|
159
|
+
* folded tree, or the answer occurs as one CONTIGUOUS byte run of the
|
|
160
|
+
* context. This is real evidence the answer was drawn from the context.
|
|
161
|
+
* Fusion gates on this — the sparse-subsequence reading of
|
|
162
|
+
* {@link isSpanShaped} is trivially satisfied by short answers over long
|
|
163
|
+
* queries ("cold" is a gap-tolerant subsequence of most sentences holding
|
|
164
|
+
* c…o…l…d in order), and gating fusion on it silently starved multi-topic
|
|
165
|
+
* queries of their further points of attention. */
|
|
166
|
+
export declare function containsSpan(ctx: MindContext, context: Uint8Array, answer: Uint8Array): boolean;
|