@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/src/mind/bridge.ts
CHANGED
|
@@ -94,9 +94,14 @@ import { cosine } from "../vec.js";
|
|
|
94
94
|
import { conceptThreshold, dominates } from "../geometry.js";
|
|
95
95
|
import { bytesEqual, indexOf } from "../bytes.js";
|
|
96
96
|
import type { MindContext } from "./types.js";
|
|
97
|
-
import { foldTree, perceive } from "./primitives.js";
|
|
97
|
+
import { foldTree, perceive, read } from "./primitives.js";
|
|
98
98
|
import { chainReach, leafIdRun } from "./canonical.js";
|
|
99
|
-
import {
|
|
99
|
+
import {
|
|
100
|
+
corpusN,
|
|
101
|
+
edgeAncestors,
|
|
102
|
+
hubBound,
|
|
103
|
+
sharedReachMemo,
|
|
104
|
+
} from "./traverse.js";
|
|
100
105
|
import { rItem, rNode } from "./trace.js";
|
|
101
106
|
|
|
102
107
|
/** One accepted substitution: query span [qs,qe) stands in for the
|
|
@@ -242,10 +247,30 @@ function align(
|
|
|
242
247
|
|
|
243
248
|
/** Recall's corroborated-substitution bridge — see the module comment.
|
|
244
249
|
* Returns the best bridged grounding proposal, or null. */
|
|
250
|
+
/** `proposed` is a THUNK, not a list: the bridge's own cheap gates (the
|
|
251
|
+
* two-quantum query floor and the O(|query|) stored-window anchor scan)
|
|
252
|
+
* decide whether ANY candidate can be aligned, and they need no proposals
|
|
253
|
+
* to do it. Resolving the caller's proposals eagerly meant recall paid its
|
|
254
|
+
* exhaustive whole-index resonance — the most expensive single act on the
|
|
255
|
+
* refusal path — for every query, including the ones whose windows the
|
|
256
|
+
* store has never seen and which the anchor scan rejects outright. Same
|
|
257
|
+
* investment discipline the mechanism floors follow (AGENTS §2.6): never
|
|
258
|
+
* compute a shared analysis just to discard it. */
|
|
245
259
|
export async function substitutionBridge(
|
|
246
260
|
ctx: MindContext,
|
|
247
261
|
query: Uint8Array,
|
|
248
|
-
proposed: ReadonlyArray<number
|
|
262
|
+
proposed: () => Promise<ReadonlyArray<number>> = async () => [],
|
|
263
|
+
): Promise<BridgeHit | null> {
|
|
264
|
+
const meter = ctx.meter;
|
|
265
|
+
return meter
|
|
266
|
+
? meter.time("substitutionBridge", () => bridgeImpl(ctx, query, proposed))
|
|
267
|
+
: bridgeImpl(ctx, query, proposed);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function bridgeImpl(
|
|
271
|
+
ctx: MindContext,
|
|
272
|
+
query: Uint8Array,
|
|
273
|
+
proposed: () => Promise<ReadonlyArray<number>>,
|
|
249
274
|
): Promise<BridgeHit | null> {
|
|
250
275
|
const W = ctx.space.maxGroup;
|
|
251
276
|
if (query.length < 2 * W) return null;
|
|
@@ -254,6 +279,38 @@ export async function substitutionBridge(
|
|
|
254
279
|
const bar = conceptThreshold(ctx.store.D);
|
|
255
280
|
const reachCap = chainReach(W);
|
|
256
281
|
|
|
282
|
+
// PHRASE-SCALE CANDIDATE CAP — the same |content|·W bound the weave
|
|
283
|
+
// (pipeline-mechanism.ts), the cross-region junction ladder's
|
|
284
|
+
// `maxInterior`, and structural resonance's `maxSiblingBytes` all apply,
|
|
285
|
+
// for the same reason and now at the one remaining place that read
|
|
286
|
+
// candidate contexts WHOLE.
|
|
287
|
+
//
|
|
288
|
+
// The bridge accepts a candidate only when the query is DOMINATED by its
|
|
289
|
+
// matched runs plus substitutions, with at most one window W of slack at
|
|
290
|
+
// each edge and at most one chain reach (W²) per interior gap — so the
|
|
291
|
+
// candidate region an accepted alignment can ever consume is bounded by
|
|
292
|
+
// |query|·W. Content beyond that cannot participate in any alignment
|
|
293
|
+
// this function would accept; reading it is pure cost. And a candidate
|
|
294
|
+
// an order of magnitude past the query is not a paraphrase of it: it is a
|
|
295
|
+
// document or a whole conversation that merely quotes a phrase, and
|
|
296
|
+
// grounding through ITS learnt edge voices that document's continuation,
|
|
297
|
+
// not a phrase answer.
|
|
298
|
+
//
|
|
299
|
+
// Measured on the 17.7M-node / 325K-context store: uncapped, the refusal
|
|
300
|
+
// path materialised up to ~1 MB of candidate bytes per query (up to √N
|
|
301
|
+
// proposals plus √N climbed contexts, each read in full), and the frame-
|
|
302
|
+
// unanimity scan — which walks EVERY collected candidate's bytes, inside
|
|
303
|
+
// the per-gap expansion loop — paid that volume back tens of times per
|
|
304
|
+
// substitution. Recall's run() was 0.7–2.6 s per refusing query.
|
|
305
|
+
const capBytes = query.length * W;
|
|
306
|
+
/** A candidate's bytes, phrase-scale capped: null when it exceeds the cap
|
|
307
|
+
* (read one byte past it, so "too long" is decided without materialising
|
|
308
|
+
* the rest) or has no content. */
|
|
309
|
+
const candidateBytes = (sid: number): Uint8Array | null => {
|
|
310
|
+
const b = read(ctx, sid, capBytes + 1);
|
|
311
|
+
return b.length === 0 || b.length > capBytes ? null : b;
|
|
312
|
+
};
|
|
313
|
+
|
|
257
314
|
// 1. The query's stored windows, rarest first (fewest containers — the
|
|
258
315
|
// most discriminative anchors; hub-clamped like every fan-out read).
|
|
259
316
|
// The scan doubles as the ONE store probe of every query window: the
|
|
@@ -302,6 +359,54 @@ export async function substitutionBridge(
|
|
|
302
359
|
}
|
|
303
360
|
return false;
|
|
304
361
|
};
|
|
362
|
+
// ── EXPLAINED SPANS — the scaffolding judgement, corpus-global ──────────
|
|
363
|
+
//
|
|
364
|
+
// The question every gap poses is "may the two forms differ HERE without
|
|
365
|
+
// differing in what they SAY?", and that is the discriminative-vs-
|
|
366
|
+
// scaffolding question AGENTS §2.7 names, over the CORPUS-GLOBAL
|
|
367
|
+
// population. It already has one definition — `dominates(reachOf(...), N)`,
|
|
368
|
+
// the same gate confluence's filler test uses ("scaffolding never binds").
|
|
369
|
+
// Nothing new is derived here; the bar is read, not invented.
|
|
370
|
+
//
|
|
371
|
+
// A span is explained when EITHER
|
|
372
|
+
// • it is sub-quantum (< W) — typographic glue, the tolerance identityBar
|
|
373
|
+
// already prices ("below one river window, byte overlap is chance"); or
|
|
374
|
+
// • every full W-window inside it is COMMON by the store's own climb:
|
|
375
|
+
// the ascent SATURATES (the window sits in more places than √N — the
|
|
376
|
+
// climb's own definition of non-discriminative), or it resolves to a
|
|
377
|
+
// majority of the corpus's contexts. "the process of ", " is the ".
|
|
378
|
+
//
|
|
379
|
+
// THE READING MATTERS, not just the population (AGENTS §2.7). This
|
|
380
|
+
// deliberately does NOT go through `reachOf`, which maps BOTH "saturated"
|
|
381
|
+
// and "reaches nothing" to Infinity. For IDF weighting those are the same
|
|
382
|
+
// thing (no usable identity evidence); for THIS question they are
|
|
383
|
+
// opposites — a window reaching nothing is novel content, the most
|
|
384
|
+
// discriminative material there is, and reading it as Infinity would call
|
|
385
|
+
// it scaffolding. Measured: with `reachOf`, "Is water wet?" was answered
|
|
386
|
+
// with "No, heavy water is not wet." — "heav"/"eavy" occur once, reach no
|
|
387
|
+
// edge-bearing ancestor, and were written off as filler. So an
|
|
388
|
+
// empty-rooted window is NEVER explained, and neither is an untrained one
|
|
389
|
+
// (the same principle attestedQ applies to the query side).
|
|
390
|
+
const reachMemo = sharedReachMemo(ctx);
|
|
391
|
+
const explainedSpan = (
|
|
392
|
+
bytes: Uint8Array,
|
|
393
|
+
from: number,
|
|
394
|
+
to: number,
|
|
395
|
+
): boolean => {
|
|
396
|
+
if (to - from < W) return true;
|
|
397
|
+
for (let o = from; o + W <= to; o++) {
|
|
398
|
+
const ids = leafIdRun(ctx, bytes, o, o + W);
|
|
399
|
+
if (ids === null) return false;
|
|
400
|
+
const wid = ctx.store.findBranch(ids);
|
|
401
|
+
if (wid === null) return false;
|
|
402
|
+
const r = edgeAncestors(ctx, wid, N, reachMemo);
|
|
403
|
+
if (r.saturated) continue; // in too many places to discriminate
|
|
404
|
+
if (r.roots.length === 0) return false; // reaches nothing: novel content
|
|
405
|
+
if (!dominates(r.contextsReached, N)) return false;
|
|
406
|
+
}
|
|
407
|
+
return true;
|
|
408
|
+
};
|
|
409
|
+
|
|
305
410
|
anchors.sort((a, b) => a.rarity - b.rarity);
|
|
306
411
|
// Up to W anchors, at least one window apart — the quantum's own count.
|
|
307
412
|
const picked: typeof anchors = [];
|
|
@@ -331,11 +436,13 @@ export async function substitutionBridge(
|
|
|
331
436
|
// that could align at all: alignment can only seed at a picked anchor
|
|
332
437
|
// window occurring literally in the candidate (measured: unconditional
|
|
333
438
|
// re-folds multiplied the refusal-path latency several-fold).
|
|
334
|
-
|
|
439
|
+
// FIRST TOUCH of the caller's proposals — past every gate that could have
|
|
440
|
+
// refused without them (see substitutionBridge's doc).
|
|
441
|
+
for (const sid of await proposed()) {
|
|
335
442
|
if (seen.has(sid)) continue;
|
|
336
443
|
seen.add(sid);
|
|
337
|
-
const tb =
|
|
338
|
-
if (tb
|
|
444
|
+
const tb = candidateBytes(sid);
|
|
445
|
+
if (tb === null) continue;
|
|
339
446
|
if (
|
|
340
447
|
!picked.some((a) => indexOf(tb, query.subarray(a.off, a.off + W), 0) >= 0)
|
|
341
448
|
) continue;
|
|
@@ -368,8 +475,14 @@ export async function substitutionBridge(
|
|
|
368
475
|
}
|
|
369
476
|
|
|
370
477
|
// 3. Align each candidate; gate its mismatches; keep the best.
|
|
478
|
+
// Over-cap candidates are dropped here rather than earlier: the climb
|
|
479
|
+
// channel deliberately reads no bytes while collecting (the climb visits
|
|
480
|
+
// hundreds of roots), so this is where its proposals are first sized.
|
|
371
481
|
const allBytes = new Map<number, Uint8Array>();
|
|
372
|
-
for (const sid of candidates)
|
|
482
|
+
for (const sid of candidates) {
|
|
483
|
+
const b = candidateBytes(sid);
|
|
484
|
+
if (b !== null) allBytes.set(sid, b);
|
|
485
|
+
}
|
|
373
486
|
|
|
374
487
|
// FRAME UNANIMITY: a substitution U → C inside the frame (Lf, Rf) is
|
|
375
488
|
// groundable only when the collected candidates — the store's own sample
|
|
@@ -428,8 +541,8 @@ export async function substitutionBridge(
|
|
|
428
541
|
let best: BridgeHit | null = null;
|
|
429
542
|
let bestAccounted = 0;
|
|
430
543
|
for (const sid of candidates) {
|
|
431
|
-
const cBytes = allBytes.get(sid)
|
|
432
|
-
if (cBytes
|
|
544
|
+
const cBytes = allBytes.get(sid);
|
|
545
|
+
if (cBytes === undefined) continue;
|
|
433
546
|
// Seed at the rarest picked anchor that literally occurs in this
|
|
434
547
|
// candidate.
|
|
435
548
|
let seed: { qo: number; co: number } | null = null;
|
|
@@ -541,7 +654,7 @@ export async function substitutionBridge(
|
|
|
541
654
|
// resonance rank but has no outgoing edge to bridge through). This
|
|
542
655
|
// mechanism exists to explain SUBSTITUTIONS; a query needing none is
|
|
543
656
|
// recall's job, not the bridge's.
|
|
544
|
-
if (!ok
|
|
657
|
+
if (!ok) continue;
|
|
545
658
|
|
|
546
659
|
// Coverage: matched runs plus accepted substitutions must dominate the
|
|
547
660
|
// query, every interior gap already proved ≤ W above, and the EDGES
|
|
@@ -565,6 +678,70 @@ export async function substitutionBridge(
|
|
|
565
678
|
if (spans[0][0] > W || query.length - reachEnd > W) continue;
|
|
566
679
|
if (!dominates(covered, query.length)) continue;
|
|
567
680
|
|
|
681
|
+
// ZERO-SUBSTITUTION ADMISSION — an IDENTITY claim, not a substitution.
|
|
682
|
+
//
|
|
683
|
+
// A candidate needing no substitution is normally refused (see the trap
|
|
684
|
+
// above), and that refusal is right for the case it was written for: the
|
|
685
|
+
// query is a strict byte-PREFIX of several candidates, each of which
|
|
686
|
+
// continues differently, and nothing here corroborates picking one
|
|
687
|
+
// continuation over another. But that trap has a signature — the
|
|
688
|
+
// candidate carries substantial content BEYOND the alignment, and that
|
|
689
|
+
// surplus is exactly the "answer" the bridge would be inventing.
|
|
690
|
+
//
|
|
691
|
+
// The opposite shape is not ambiguous at all: the alignment explains BOTH
|
|
692
|
+
// strings end to end, and the only thing between them is sub-quantum glue
|
|
693
|
+
// — typographic punctuation the fold treats as structure. Then the two
|
|
694
|
+
// are the SAME learnt form, and grounding through its edge returns that
|
|
695
|
+
// form's own trained answer, never a chosen-among-many completion.
|
|
696
|
+
//
|
|
697
|
+
// Why the ladder cannot reach these otherwise: the gist is a STRUCTURAL
|
|
698
|
+
// signature, so a mid-string insertion shifts every fold boundary after
|
|
699
|
+
// it. Measured: `Who wrote Romeo and Juliet?` against the trained
|
|
700
|
+
// `Who wrote "Romeo and Juliet"?` — two inserted quote characters — scores
|
|
701
|
+
// cos 0.377, BELOW unrelated neighbours like "Who wrote the opera
|
|
702
|
+
// Carmen??" (0.603). Recall's identity tiers gate on identityBar (0.969
|
|
703
|
+
// here) and its reach tiers on 0.875, so no gist-based tier can ever see
|
|
704
|
+
// it; only byte-exact alignment can, which is what this function does.
|
|
705
|
+
//
|
|
706
|
+
// The claim is deliberately strict, in three parts:
|
|
707
|
+
//
|
|
708
|
+
// • QUERY SIDE — EXACT. Every byte of the query must be a literal
|
|
709
|
+
// match against the candidate: covered === query.length, no slack at
|
|
710
|
+
// all, not even sub-quantum. The query is what we are answering, so
|
|
711
|
+
// an identity claim about it may write off NOTHING. This is stricter
|
|
712
|
+
// than the ≤ W edge tolerance the substituted path uses, and it has
|
|
713
|
+
// to be: with a one-window allowance, `what is 2^10?` matched the
|
|
714
|
+
// trained `what is 2+2?` — "^10" against "+2", four bytes, both sides
|
|
715
|
+
// below W — and answered "2+2 is 4.", outweighing cover's authoritative
|
|
716
|
+
// ALU result. Below W, byte OVERLAP is chance rather than evidence;
|
|
717
|
+
// that never made a below-W DIFFERENCE meaningless, and digits are the
|
|
718
|
+
// case that proves it.
|
|
719
|
+
// • CANDIDATE SIDE, INTERIOR — each gap must be an EXPLAINED span (see
|
|
720
|
+
// explainedSpan): sub-quantum glue, or corpus-global scaffolding.
|
|
721
|
+
// This side is asymmetric ON PURPOSE. Material the CANDIDATE has and
|
|
722
|
+
// the query omits is not something the asker asked about: if it is
|
|
723
|
+
// scaffolding, dropping it changes nothing ("What is *the process of*
|
|
724
|
+
// photosynthesis?"); if it is discriminative, the candidate answers a
|
|
725
|
+
// DIFFERENT, narrower question ("Is *heavy* water wet?") and must be
|
|
726
|
+
// refused. Only the corpus can tell those apart, and it does.
|
|
727
|
+
// • CANDIDATE SIDE, SURPLUS — its bytes are the matched runs
|
|
728
|
+
// (byte-identical to the query's, hence the same total length) plus
|
|
729
|
+
// its own gap spans; anything past that is surplus, and surplus is
|
|
730
|
+
// the prefix trap. A prefix-completion candidate fails here by the
|
|
731
|
+
// whole length of the completion it wanted to supply — which is why
|
|
732
|
+
// admitting scaffolding interiors does not reopen that trap.
|
|
733
|
+
//
|
|
734
|
+
// Ordered cheapest-first: the two arithmetic tests run before
|
|
735
|
+
// explainedSpan, whose per-window `reachOf` climbs are the only costly
|
|
736
|
+
// part (shared through the response/conversation reach memo, and reached
|
|
737
|
+
// only by a candidate that already survived every structural gate).
|
|
738
|
+
if (subs.length === 0) {
|
|
739
|
+
if (covered !== query.length) continue;
|
|
740
|
+
const cGap = gaps.reduce((n, g) => n + (g.ce - g.cs), 0);
|
|
741
|
+
if (cBytes.length - covered - cGap > W) continue;
|
|
742
|
+
if (!gaps.every((g) => explainedSpan(cBytes, g.cs, g.ce))) continue;
|
|
743
|
+
}
|
|
744
|
+
|
|
568
745
|
// KNOWN content may never be dismissed — see dismissedKnownContent
|
|
569
746
|
// (the live case: "what is the capital of france" aligning into a
|
|
570
747
|
// Matrix synopsis by writing off "ance" — a stored window of the
|
package/src/mind/canonical.ts
CHANGED
|
@@ -27,7 +27,12 @@ import type { MindContext } from "./types.js";
|
|
|
27
27
|
|
|
28
28
|
/** The two sliding-window lengths the WRITE side interns over a stream's leaf
|
|
29
29
|
* ids: W−1 and W (the river's grouping quantum and its off-by-one neighbour,
|
|
30
|
-
* so a form straddling a group boundary is reachable from either cut).
|
|
30
|
+
* so a form straddling a group boundary is reachable from either cut).
|
|
31
|
+
*
|
|
32
|
+
* Widening this to cover the reader's full segment scale (W−1..2W) was
|
|
33
|
+
* measured and REFUTED: it tripled the store (3,160 → 8,980 nodes on a
|
|
34
|
+
* 200-pair corpus), slowed ingest 80%, and fixed not one test. Unknown
|
|
35
|
+
* regions are not caused by the index's scale. */
|
|
31
36
|
export function canonicalWindows(W: number): [number, number] {
|
|
32
37
|
return [W - 1, W];
|
|
33
38
|
}
|
package/src/mind/graph-search.ts
CHANGED
|
@@ -350,6 +350,18 @@ export class GraphSearch {
|
|
|
350
350
|
private readonly host: GraphSearchHost,
|
|
351
351
|
) {}
|
|
352
352
|
|
|
353
|
+
/** The hub bound √N (AGENTS §2.8) — the ONE fan-out cap, stated here
|
|
354
|
+
* rather than imported from `traverse.ts` because this module is
|
|
355
|
+
* deliberately host-based (it holds a bare Store, never a MindContext).
|
|
356
|
+
* That is the same write/read-side duplication convention canonical.ts's
|
|
357
|
+
* header documents: if the formula changes it must change in BOTH places.
|
|
358
|
+
* It is stated ONCE per side, though — the expression used to be spelled
|
|
359
|
+
* out at three call sites here, one of them inside a per-item rules
|
|
360
|
+
* generator, and they had already drifted on the `Math.max(2, …)` floor. */
|
|
361
|
+
private hubBound(): number {
|
|
362
|
+
return Math.ceil(Math.sqrt(Math.max(2, this.store.edgeSourceCount())));
|
|
363
|
+
}
|
|
364
|
+
|
|
353
365
|
/** Explore the Sema graph for the lightest cover of the query and return its
|
|
354
366
|
* chosen spans left-to-right — WITH the derivation's total weight (the g
|
|
355
367
|
* value of the goal item, in the exported cost ladder), which think's
|
|
@@ -442,7 +454,17 @@ export class GraphSearch {
|
|
|
442
454
|
connectors,
|
|
443
455
|
computedResults,
|
|
444
456
|
);
|
|
445
|
-
|
|
457
|
+
// Search-effort accounting (src/meter.ts): the chart's pops/pushes are
|
|
458
|
+
// the cover's real cost, and a heuristic that stops being admissible
|
|
459
|
+
// shows up as a pop count that explodes while the answer stays the same.
|
|
460
|
+
const meter = this.host.meter;
|
|
461
|
+
const stats = meter ? { pops: 0, pushes: 0 } : undefined;
|
|
462
|
+
const derivation = lightestDerivation(system, stats);
|
|
463
|
+
if (meter && stats) {
|
|
464
|
+
meter.searches++;
|
|
465
|
+
meter.searchPops += stats.pops;
|
|
466
|
+
meter.searchPushes += stats.pushes;
|
|
467
|
+
}
|
|
446
468
|
// When covering under a substitution map (articulation), a form→out rule is
|
|
447
469
|
// the form EMITTING the asker's voice, not grounding to its own answer — so
|
|
448
470
|
// tell the reader to name those moves `voice` rather than `ground`.
|
|
@@ -475,15 +497,12 @@ export class GraphSearch {
|
|
|
475
497
|
): DeductionSystem<GItem> {
|
|
476
498
|
const W = this.maxGroup; // fusible span ceiling (shortest composite bound)
|
|
477
499
|
// Same corpus-scale hub floor {@link atomIsHub}/{@link atomReach} (traverse.ts)
|
|
478
|
-
// derive for byte atoms —
|
|
479
|
-
//
|
|
480
|
-
// (maxGroup, edgeSourceCount) are already in scope with no ctx needed. If
|
|
481
|
-
// the formula ever changes, it must change in BOTH places (see canonical.ts's
|
|
482
|
-
// header for the same write/read-side duplication convention).
|
|
500
|
+
// derive for byte atoms — see {@link hubBound} below for why this module
|
|
501
|
+
// states the formula itself instead of importing it.
|
|
483
502
|
const atomsAreHubs = Math.max(
|
|
484
503
|
1,
|
|
485
504
|
Math.ceil((this.store.edgeSourceCount() * W) / 256),
|
|
486
|
-
) >
|
|
505
|
+
) > this.hubBound();
|
|
487
506
|
const nodeBytes = (n: number) => this.store.bytesPrefix(n, ALL);
|
|
488
507
|
// Content-addressed probes over the store's hash-cons maps — the same keys
|
|
489
508
|
// training filled. No byte-by-byte trie walk.
|
|
@@ -753,10 +772,7 @@ export class GraphSearch {
|
|
|
753
772
|
// guard then dead-ends it) with no way to reach the forward edge.
|
|
754
773
|
// Forking offers every continuation as its own rule so the one that
|
|
755
774
|
// genuinely advances (not a duplicate) is still reachable.
|
|
756
|
-
const
|
|
757
|
-
Math.sqrt(Math.max(2, this.store.edgeSourceCount())),
|
|
758
|
-
);
|
|
759
|
-
const nx = this.store.nextFirst(it.node, bound);
|
|
775
|
+
const nx = this.store.nextFirst(it.node, this.hubBound());
|
|
760
776
|
if (nx.length) {
|
|
761
777
|
// The SAME evidence-weighted disambiguation the first hop uses
|
|
762
778
|
// (below) identifies the most-corroborated continuation. Yielding
|
|
@@ -1142,16 +1158,39 @@ export class GraphSearch {
|
|
|
1142
1158
|
// is opportunistic cross-leaf recovery exactly like recognition.ts's own
|
|
1143
1159
|
// canonical chain — findLeaf/findBranch here have no idea WHY these two
|
|
1144
1160
|
// leaves are adjacent, only that their concatenation happens to spell a
|
|
1145
|
-
// trained form ("hi" recovered from "W[hi]ch").
|
|
1146
|
-
//
|
|
1147
|
-
//
|
|
1148
|
-
//
|
|
1149
|
-
//
|
|
1150
|
-
//
|
|
1151
|
-
//
|
|
1152
|
-
//
|
|
1153
|
-
|
|
1154
|
-
|
|
1161
|
+
// trained form ("hi" recovered from "W[hi]ch"). At hub scale, where
|
|
1162
|
+
// atoms themselves no longer discriminate, that coincidence is noise.
|
|
1163
|
+
//
|
|
1164
|
+
// A FOLD BOUNDARY IS NOT EVIDENCE. This gate used to exempt any fuse
|
|
1165
|
+
// starting at `starts.has(l.i)` — documented as "a position the query's
|
|
1166
|
+
// OWN fold chose as a boundary (real structural evidence)". The plain
|
|
1167
|
+
// river fold CHOOSES NOTHING: `riverFold` groups fixed-arity
|
|
1168
|
+
// (`for (i = 0; i < complete; i += mg)`), so `starts` is exactly
|
|
1169
|
+
// {0, W, 2W, 3W, …} for every query. Measured on three unrelated
|
|
1170
|
+
// queries: starts = 0,4,8,12,16,20,24,28[,32] in every case — the set
|
|
1171
|
+
// carries ZERO content information, and the exemption therefore fired at
|
|
1172
|
+
// a quarter of all offsets by arithmetic alone.
|
|
1173
|
+
//
|
|
1174
|
+
// What it cost (17.9M-node store, W=4): "In which country is the Eiffel
|
|
1175
|
+
// Tower?" fused the atoms "h"+"i" at offset 4 — trusted only because
|
|
1176
|
+
// 4 ≡ 0 (mod 4) — into the trained form "hi", followed its continuation
|
|
1177
|
+
// edge, and grounded "Hello there. If you are looking for Open
|
|
1178
|
+
// Assistant, look no further." as a FACT, explaining 2 of 37 bytes
|
|
1179
|
+
// (density 0.054, a fifth of the 1/W honesty bar `thinGrounding`
|
|
1180
|
+
// reports and does not enforce).
|
|
1181
|
+
//
|
|
1182
|
+
// So the exemption is removed rather than replaced: there is no cheap
|
|
1183
|
+
// signal here that means what it claimed to mean, and inventing one
|
|
1184
|
+
// would be worse than admitting the absence. Genuine cross-leaf forms
|
|
1185
|
+
// are not lost — recognition.ts's canonical pass already probes EVERY
|
|
1186
|
+
// byte offset for the write side's interned windows and emits them as
|
|
1187
|
+
// sites, which arrive here as `l.rec`/`r.rec` and stay exempt. What
|
|
1188
|
+
// remains excluded at hub scale is precisely the case with no evidence
|
|
1189
|
+
// behind it: two byte atoms that happen to spell something.
|
|
1190
|
+
//
|
|
1191
|
+
// Below hub scale nothing changes (`!atomsAreHubs`): on a small store
|
|
1192
|
+
// coincidence is rare and every chain is real evidence.
|
|
1193
|
+
const trusted = l.rec || r.rec || !ctx.atomsAreHubs;
|
|
1155
1194
|
let node = (trusted && bytes.length <= ctx.W)
|
|
1156
1195
|
? ctx.findLeafU(bytes)
|
|
1157
1196
|
: undefined;
|
package/src/mind/junction.ts
CHANGED
|
@@ -186,6 +186,16 @@ function cachedContainers(
|
|
|
186
186
|
* few levels of its parts). A side too common to decide within the
|
|
187
187
|
* budget abstains here and falls through to the resonance tier (the
|
|
188
188
|
* climb's own saturation semantics).
|
|
189
|
+
*
|
|
190
|
+
* REFUTED TIGHTENING (measured, 325K contexts): giving this walk
|
|
191
|
+
* edgeAncestors' cumulative LATERAL-CONE limit — abstain once the
|
|
192
|
+
* accumulated lateral entries pass √N — would cut most of the pops (47
|
|
193
|
+
* of 56 walks exceed √N lateral, and 47 exhaust the budget), but TWO OF
|
|
194
|
+
* THE FOUR walks that actually found a container had lateral spread of
|
|
195
|
+
* 1425 and 1426, far past √N. The cone limit is sound for
|
|
196
|
+
* edgeAncestors' question and wrong for this one: a junction container
|
|
197
|
+
* is legitimately reached across many containing structures. Half the
|
|
198
|
+
* successful junctions would be lost.
|
|
189
199
|
* • per-node hub guards — parent fan-outs beyond √N are hubs (not
|
|
190
200
|
* expanded); each node contributes at most one √N page of containers;
|
|
191
201
|
* √N collected candidates decide. */
|
|
@@ -215,6 +225,7 @@ export function junctionContainersFrom(
|
|
|
215
225
|
if (seeds.length === 0) return [];
|
|
216
226
|
|
|
217
227
|
const b = budget ?? { n: bound * ctx.space.maxGroup };
|
|
228
|
+
if (ctx.meter) ctx.meter.junctionWalks++;
|
|
218
229
|
// DEPTH CAP: perception trees are W-ary and a junction container is
|
|
219
230
|
// phrase-scale, so it sits within ~log_W(maxContainer) structural levels
|
|
220
231
|
// of its parts — at most W levels for any practical W (plus the
|
|
@@ -231,6 +242,7 @@ export function junctionContainersFrom(
|
|
|
231
242
|
}));
|
|
232
243
|
while (stack.length > 0 && out.length < bound && b.n-- > 0) {
|
|
233
244
|
const { id: x, d } = stack.pop()!;
|
|
245
|
+
if (ctx.meter) ctx.meter.junctionPops++;
|
|
234
246
|
const f = cachedRead(ctx, cache, x, maxContainer);
|
|
235
247
|
if (f.length > maxContainer) continue; // beyond phrase scale: prune branch
|
|
236
248
|
if (unordered) {
|
package/src/mind/match.ts
CHANGED
|
@@ -31,7 +31,7 @@ import { conceptThreshold, identityBar, significanceBar } from "../geometry.js";
|
|
|
31
31
|
import { indexOf } from "../bytes.js";
|
|
32
32
|
import type { MindContext } from "./types.js";
|
|
33
33
|
import { leafIdRun } from "./canonical.js";
|
|
34
|
-
import { gistOf, read, resolve } from "./primitives.js";
|
|
34
|
+
import { foldTree, gistOf, perceive, read, resolve } from "./primitives.js";
|
|
35
35
|
import {
|
|
36
36
|
argmaxCosine,
|
|
37
37
|
chooseAmong,
|
|
@@ -119,6 +119,25 @@ export function alignRuns(
|
|
|
119
119
|
query: Uint8Array,
|
|
120
120
|
ct: Uint8Array,
|
|
121
121
|
): Array<{ qs: number; qe: number; cs: number }> {
|
|
122
|
+
if (ctx.meter) {
|
|
123
|
+
ctx.meter.alignments++;
|
|
124
|
+
// The alignment family's honest unit: the seed index is O(|query|) but
|
|
125
|
+
// the run extension is O(|query|·|ct|) in the worst case, and a weave
|
|
126
|
+
// that starts scanning conversation-length contexts shows up HERE long
|
|
127
|
+
// before it shows up in a call count.
|
|
128
|
+
ctx.meter.alignCells += query.length * ct.length;
|
|
129
|
+
}
|
|
130
|
+
// MEASURED AND REFUTED — seeding at the write side's unit floor W−1 instead
|
|
131
|
+
// of W. `canonicalWindows` interns both lengths precisely so a form
|
|
132
|
+
// straddling a group boundary is reachable from either cut, and the runs
|
|
133
|
+
// found here are exactly such remnants: test/29 C1's query shares only `ce `
|
|
134
|
+
// with `Ice is cold` — three bytes, never seeded at W, so that structure
|
|
135
|
+
// enters the weave carrying nothing but the scaffolding run every exemplar
|
|
136
|
+
// shares. But this is a byte MATCHER between two streams, not an index, and
|
|
137
|
+
// at W−1 the corpus is dense with spurious 3-byte agreements: the extra runs
|
|
138
|
+
// reshuffle which point claims which span, and test/29 A2 loses its analog.
|
|
139
|
+
// C1 does not pass either way. The store's unit floor does not transfer to
|
|
140
|
+
// the aligner's seed length.
|
|
122
141
|
const quantum = Math.min(ctx.space.maxGroup, ct.length);
|
|
123
142
|
if (quantum < 1 || query.length < quantum) return [];
|
|
124
143
|
const gram = (b: Uint8Array, at: number): string => {
|
|
@@ -167,6 +186,13 @@ export function alignRuns(
|
|
|
167
186
|
* kind, so the substitution/redirection schemas work unchanged on conceptual
|
|
168
187
|
* alignment. */
|
|
169
188
|
export interface GradedRun {
|
|
189
|
+
/** True for a run the CLIMB proposed rather than the byte matcher found — see
|
|
190
|
+
* {@link computeWeave}'s phase 2. Provenance only: a proposed run reaches
|
|
191
|
+
* CAST already gated (dominant literal agreement, not frame, and over bytes
|
|
192
|
+
* no literal run claimed), so every consumer reads it exactly like any other
|
|
193
|
+
* run. Filtering seat-shaped reads by this flag was tried and is NOT
|
|
194
|
+
* needed — the gates, not the flag, are what make it safe. */
|
|
195
|
+
proposed?: boolean;
|
|
170
196
|
qs: number;
|
|
171
197
|
qe: number;
|
|
172
198
|
cs: number;
|
|
@@ -542,3 +568,122 @@ export async function project(
|
|
|
542
568
|
if (fc) return fc;
|
|
543
569
|
return reverseContext(ctx, id, guide);
|
|
544
570
|
}
|
|
571
|
+
|
|
572
|
+
// ── The span-shape family ───────────────────────────────────────────────────
|
|
573
|
+
//
|
|
574
|
+
// "Is this answer drawn from this context?" has TWO formally distinct
|
|
575
|
+
// readings, and the pair plus the anchor classifier built on them are SHARED
|
|
576
|
+
// machinery — extraction proposes span-shaped exemplars with them, the
|
|
577
|
+
// shared `Precomputed.spanShapedOf` container computes them, and fusion
|
|
578
|
+
// (reasoning.ts) gates on the strict one. They lived inside
|
|
579
|
+
// mechanisms/extraction.ts, so `pipeline-mechanism.ts` and `reasoning.ts`
|
|
580
|
+
// both had to import back OUT of a specific mechanism — an inversion the
|
|
581
|
+
// mechanism market forbids (AGENTS §2.6: the shared contract may not depend
|
|
582
|
+
// on any one mechanism; §2.5: a shared matcher belongs to this family, never
|
|
583
|
+
// to a mechanism's private helpers). Deleting extraction must not break the
|
|
584
|
+
// shared container, so they live here.
|
|
585
|
+
//
|
|
586
|
+
// • isSpanShaped — the OPEN reading (sparse in-order embedding).
|
|
587
|
+
// • containsSpan — the STRICT reading (contiguous run or resolved node).
|
|
588
|
+
// • skillExemplar — classify one anchor into (context, answer) using them.
|
|
589
|
+
//
|
|
590
|
+
// The two readings are NOT interchangeable; AGENTS §2.5 pins the distinction
|
|
591
|
+
// and each function's own doc states what breaks if it is substituted.
|
|
592
|
+
|
|
593
|
+
/** Check whether an anchor is a span-shaped skill exemplar: it represents a
|
|
594
|
+
* fact whose context and answer together form a span-in-context pattern.
|
|
595
|
+
* If the anchor has a nextOf continuation, that is the answer and the anchor
|
|
596
|
+
* itself is the context. Otherwise the anchor's prevOf parents provide
|
|
597
|
+
* candidate contexts, and the longest one whose span is span-shaped wins. */
|
|
598
|
+
export async function skillExemplar(
|
|
599
|
+
ctx: MindContext,
|
|
600
|
+
anchor: number,
|
|
601
|
+
guide?: Vec | null,
|
|
602
|
+
): Promise<{ contextBytes: Uint8Array; answerBytes: Uint8Array } | null> {
|
|
603
|
+
if (ctx.store.hasNext(anchor)) {
|
|
604
|
+
const contextBytes = read(ctx, anchor);
|
|
605
|
+
const answerBytes = await follow(ctx, anchor, guide);
|
|
606
|
+
if (
|
|
607
|
+
answerBytes !== null && isSpanShaped(ctx, contextBytes, answerBytes)
|
|
608
|
+
) {
|
|
609
|
+
return { contextBytes, answerBytes };
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
const answerBytes = read(ctx, anchor);
|
|
614
|
+
// Candidate contexts, capped at the hub bound (a common answer's reverse
|
|
615
|
+
// fan-in is corpus-sized).
|
|
616
|
+
const capped = ctx.store.prevFirst(anchor, hubBound(ctx));
|
|
617
|
+
const spanShaped: Array<{ id: number; bytes: Uint8Array }> = [];
|
|
618
|
+
for (const p of capped) {
|
|
619
|
+
const ctxB = read(ctx, p);
|
|
620
|
+
if (ctxB.length > 0 && isSpanShaped(ctx, ctxB, answerBytes)) {
|
|
621
|
+
spanShaped.push({ id: p, bytes: ctxB });
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (spanShaped.length === 0) return null;
|
|
625
|
+
// Among span-shaped contexts, the longest wins (the smallest spanning frame
|
|
626
|
+
// heuristic's dual: more frame to locate in the query); the query gist,
|
|
627
|
+
// when given, breaks LENGTH TIES via chooseAmong — the same reverse-regime
|
|
628
|
+
// disambiguator every context pick uses, whose gist cache spares the
|
|
629
|
+
// re-fold this block once paid per tied candidate. Same strict first-seen
|
|
630
|
+
// tie-break as the hand loop it replaces.
|
|
631
|
+
const maxLen = Math.max(...spanShaped.map((s) => s.bytes.length));
|
|
632
|
+
const longest = spanShaped.filter((s) => s.bytes.length === maxLen);
|
|
633
|
+
let contextBytes = longest[0].bytes;
|
|
634
|
+
if (guide && longest.length > 1) {
|
|
635
|
+
const pick = chooseAmong(ctx, longest.map((s) => s.id), guide).id;
|
|
636
|
+
contextBytes = longest.find((s) => s.id === pick)!.bytes;
|
|
637
|
+
}
|
|
638
|
+
return { contextBytes, answerBytes };
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** Whether the answer is a SPARSE subsequence of the context (bytes in
|
|
642
|
+
* order, arbitrary gaps) — the OPEN span-shape reading (see the section
|
|
643
|
+
* note above). This is what lets extraction validate a MULTI-PIECE
|
|
644
|
+
* exemplar whose answer is stitched from several context runs — but it is
|
|
645
|
+
* deliberately permissive, so it must never be used as evidence that one
|
|
646
|
+
* span was "drawn from" another (see {@link containsSpan} for that).
|
|
647
|
+
*
|
|
648
|
+
* There is deliberately NO containsSpan pre-check here: strict containment
|
|
649
|
+
* IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
|
|
650
|
+
* whose content-addressed identity means its bytes occur contiguously — is
|
|
651
|
+
* an in-order embedding with zero gaps), so the scan below decides alone,
|
|
652
|
+
* with the same truth value. The old pre-check re-perceived the context
|
|
653
|
+
* (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
|
|
654
|
+
* pure cost, no discrimination. */
|
|
655
|
+
export function isSpanShaped(
|
|
656
|
+
_ctx: MindContext,
|
|
657
|
+
context: Uint8Array,
|
|
658
|
+
answer: Uint8Array,
|
|
659
|
+
): boolean {
|
|
660
|
+
let ai = 0;
|
|
661
|
+
for (let ci = 0; ci < context.length && ai < answer.length; ci++) {
|
|
662
|
+
if (context[ci] === answer[ai]) ai++;
|
|
663
|
+
}
|
|
664
|
+
return ai === answer.length;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/** STRICT containment: the answer's resolved node appears in the context's
|
|
668
|
+
* folded tree, or the answer occurs as one CONTIGUOUS byte run of the
|
|
669
|
+
* context. This is real evidence the answer was drawn from the context.
|
|
670
|
+
* Fusion gates on this — the sparse-subsequence reading of
|
|
671
|
+
* {@link isSpanShaped} is trivially satisfied by short answers over long
|
|
672
|
+
* queries ("cold" is a gap-tolerant subsequence of most sentences holding
|
|
673
|
+
* c…o…l…d in order), and gating fusion on it silently starved multi-topic
|
|
674
|
+
* queries of their further points of attention. */
|
|
675
|
+
export function containsSpan(
|
|
676
|
+
ctx: MindContext,
|
|
677
|
+
context: Uint8Array,
|
|
678
|
+
answer: Uint8Array,
|
|
679
|
+
): boolean {
|
|
680
|
+
const ansId = resolve(ctx, answer);
|
|
681
|
+
if (ansId !== null) {
|
|
682
|
+
let found = false;
|
|
683
|
+
foldTree(ctx, perceive(ctx, context), 0, (_n, _s, _e, node) => {
|
|
684
|
+
if (node === ansId) found = true;
|
|
685
|
+
});
|
|
686
|
+
if (found) return true;
|
|
687
|
+
}
|
|
688
|
+
return indexOf(context, answer, 0) >= 0;
|
|
689
|
+
}
|