@hviana/sema 0.2.1 → 0.2.3
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/dist/src/geometry.d.ts +28 -0
- package/dist/src/geometry.js +35 -0
- package/dist/src/mind/articulation.js +1 -1
- package/dist/src/mind/attention.d.ts +4 -0
- package/dist/src/mind/attention.js +65 -12
- package/dist/src/mind/graph-search.d.ts +16 -1
- package/dist/src/mind/graph-search.js +34 -5
- package/dist/src/mind/learning.d.ts +1 -1
- package/dist/src/mind/learning.js +20 -5
- package/dist/src/mind/mechanisms/alu.js +8 -1
- package/dist/src/mind/mechanisms/cast.js +35 -11
- package/dist/src/mind/mechanisms/cover.js +40 -1
- package/dist/src/mind/mechanisms/extraction.js +75 -30
- package/dist/src/mind/mechanisms/recall.js +26 -2
- package/dist/src/mind/mind.d.ts +3 -2
- package/dist/src/mind/mind.js +28 -54
- package/dist/src/mind/pipeline.js +34 -2
- package/dist/src/mind/primitives.d.ts +16 -9
- package/dist/src/mind/primitives.js +58 -22
- package/dist/src/mind/reasoning.d.ts +9 -1
- package/dist/src/mind/reasoning.js +26 -4
- package/dist/src/mind/recognition.js +30 -6
- package/dist/src/mind/traverse.js +15 -0
- package/dist/src/mind/types.d.ts +58 -10
- package/dist/src/mind/types.js +15 -0
- package/package.json +1 -1
- package/src/geometry.ts +61 -1
- package/src/mind/articulation.ts +1 -0
- package/src/mind/attention.ts +80 -12
- package/src/mind/graph-search.ts +59 -2
- package/src/mind/learning.ts +20 -3
- package/src/mind/mechanisms/alu.ts +8 -1
- package/src/mind/mechanisms/cast.ts +46 -8
- package/src/mind/mechanisms/cover.ts +46 -0
- package/src/mind/mechanisms/extraction.ts +101 -40
- package/src/mind/mechanisms/recall.ts +30 -2
- package/src/mind/mind.ts +38 -61
- package/src/mind/pipeline.ts +37 -2
- package/src/mind/primitives.ts +71 -29
- package/src/mind/reasoning.ts +26 -3
- package/src/mind/recognition.ts +28 -5
- package/src/mind/traverse.ts +18 -0
- package/src/mind/types.ts +73 -10
- package/test/35-attention-confidence.test.mjs +139 -0
package/dist/src/geometry.d.ts
CHANGED
|
@@ -90,6 +90,13 @@ export declare function consensusFloor(N: number): number;
|
|
|
90
90
|
* replaces runtime coverage gating with a batch pass that removes
|
|
91
91
|
* structurally-isolated entries. Derived, never tuned. */
|
|
92
92
|
export declare function coverageBar(_maxGroup: number, D: number): number;
|
|
93
|
+
export interface Folded {
|
|
94
|
+
tree: Sema;
|
|
95
|
+
/** Byte length of the subtree — carried incrementally so the stable-prefix
|
|
96
|
+
* boundary scan never re-walks subtrees (the old per-level walk was
|
|
97
|
+
* O(n log n) over the whole input). */
|
|
98
|
+
len: number;
|
|
99
|
+
}
|
|
93
100
|
export interface Grid {
|
|
94
101
|
width: number;
|
|
95
102
|
height: number;
|
|
@@ -116,6 +123,27 @@ export declare function knownPrefixLength(bytes: Uint8Array, leafAt: (i: number)
|
|
|
116
123
|
* turn extend perception instead of refolding it: identical prefixes
|
|
117
124
|
* produce identical subtrees regardless of what follows them. */
|
|
118
125
|
export declare function bytesToTree(space: Space, alphabet: Alphabet, bytes: Uint8Array, leafAt?: (i: number) => number | null, lookup?: (leafIds: number[]) => number | null, boundaries?: readonly number[]): Sema;
|
|
126
|
+
/** A stable-prefix fold's reusable state: the segment edge offsets and each
|
|
127
|
+
* segment's independently-folded root ({@link riverFoldRaw} output). A
|
|
128
|
+
* grown stream whose boundary set EXTENDS a previous fold's reuses every
|
|
129
|
+
* matching segment's Folded unchanged (segments fold independently by
|
|
130
|
+
* construction, so reuse is bit-identical to refolding) and folds only the
|
|
131
|
+
* new right-edge segment — O(turn) per extension, the stable-prefix
|
|
132
|
+
* counterpart of {@link FoldPyramid}. Purely a cache: the produced tree
|
|
133
|
+
* never depends on cache state. */
|
|
134
|
+
export interface StableFold {
|
|
135
|
+
edges: number[];
|
|
136
|
+
segs: Folded[];
|
|
137
|
+
}
|
|
138
|
+
/** {@link stablePrefixFold} with incremental segment reuse — same cuts, same
|
|
139
|
+
* segment folds, same left-nested join, same single root normalize; `prev`
|
|
140
|
+
* only elides recomputing segments whose [start,end) offsets it already
|
|
141
|
+
* folded over a byte-identical prefix (the caller keys the cache by
|
|
142
|
+
* content). Requires a non-empty effective boundary set. */
|
|
143
|
+
export declare function stablePrefixFoldIncremental(space: Space, alphabet: Alphabet, bytes: Uint8Array, boundaries: readonly number[], prev?: StableFold): {
|
|
144
|
+
tree: Sema;
|
|
145
|
+
fold: StableFold;
|
|
146
|
+
};
|
|
119
147
|
/** The PLAIN fold's full level pyramid — every level's item list, bottom
|
|
120
148
|
* (leaves) to top (root). Left-grouped folding is RADIX-ALIGNED: the item
|
|
121
149
|
* at level L, index i, covers exactly bytes [i·mg^L, (i+1)·mg^L) whenever
|
package/dist/src/geometry.js
CHANGED
|
@@ -321,6 +321,41 @@ function stablePrefixFold(space, alphabet, bytes, boundaries) {
|
|
|
321
321
|
normalize(cur.tree.v);
|
|
322
322
|
return cur.tree;
|
|
323
323
|
}
|
|
324
|
+
/** {@link stablePrefixFold} with incremental segment reuse — same cuts, same
|
|
325
|
+
* segment folds, same left-nested join, same single root normalize; `prev`
|
|
326
|
+
* only elides recomputing segments whose [start,end) offsets it already
|
|
327
|
+
* folded over a byte-identical prefix (the caller keys the cache by
|
|
328
|
+
* content). Requires a non-empty effective boundary set. */
|
|
329
|
+
export function stablePrefixFoldIncremental(space, alphabet, bytes, boundaries, prev) {
|
|
330
|
+
const cuts = [];
|
|
331
|
+
let prevB = 0;
|
|
332
|
+
for (const b of boundaries) {
|
|
333
|
+
if (b > prevB && b < bytes.length) {
|
|
334
|
+
cuts.push(b);
|
|
335
|
+
prevB = b;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const edges = [0, ...cuts, bytes.length];
|
|
339
|
+
const segs = [];
|
|
340
|
+
for (let i = 0; i + 1 < edges.length; i++) {
|
|
341
|
+
const hit = prev !== undefined && prev.edges[i] === edges[i] &&
|
|
342
|
+
prev.edges[i + 1] === edges[i + 1]
|
|
343
|
+
? prev.segs[i]
|
|
344
|
+
: undefined;
|
|
345
|
+
segs.push(hit ??
|
|
346
|
+
riverFoldRaw(space, bytesToLeaves(alphabet, bytes.subarray(edges[i], edges[i + 1]))));
|
|
347
|
+
}
|
|
348
|
+
if (segs.length === 1) {
|
|
349
|
+
// Degenerate boundary set — the plain fold, as stablePrefixFold does.
|
|
350
|
+
const tree = riverFold(space, bytesToLeaves(alphabet, bytes), bytes.length).tree;
|
|
351
|
+
return { tree, fold: { edges, segs } };
|
|
352
|
+
}
|
|
353
|
+
let cur = segs[0];
|
|
354
|
+
for (let i = 1; i < segs.length; i++)
|
|
355
|
+
cur = fold2(space, cur, segs[i]);
|
|
356
|
+
normalize(cur.tree.v);
|
|
357
|
+
return { tree: cur.tree, fold: { edges, segs } };
|
|
358
|
+
}
|
|
324
359
|
/** Join two folded items as one 2-kid branch — the top-level join of the
|
|
325
360
|
* stable-prefix fold, identical FP ops to foldSlice's seat-bound
|
|
326
361
|
* accumulation over a group of two. Unnormalized (interior). */
|
|
@@ -83,7 +83,7 @@ export async function articulate(ctx, answer, query) {
|
|
|
83
83
|
s.end,
|
|
84
84
|
])),
|
|
85
85
|
]);
|
|
86
|
-
const solved = ctx.search.cover(answer.length, voicedSites, new Map(), ans.leaves, ans.splits, substitutions, undefined, undefined, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
|
|
86
|
+
const solved = ctx.search.cover(answer.length, voicedSites, new Map(), ans.leaves, ans.splits, ans.starts, substitutions, undefined, undefined, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
|
|
87
87
|
const segs = solved && solved.segs;
|
|
88
88
|
tArtCover?.done(segs === null
|
|
89
89
|
? []
|
|
@@ -39,6 +39,9 @@ export declare function poolVotes(ctx: MindContext, regionVotes: readonly Region
|
|
|
39
39
|
end: number;
|
|
40
40
|
w: number;
|
|
41
41
|
}>;
|
|
42
|
+
/** Per-anchor SCALE-INVARIANT support: Σ RegionVote.absorbed over the
|
|
43
|
+
* distinct contributing regions — see Attention.breadth. */
|
|
44
|
+
regionSupport: Map<number, number>;
|
|
42
45
|
steps: DerivationStep[];
|
|
43
46
|
};
|
|
44
47
|
export declare function commitVotes(ctx: MindContext, pooled: {
|
|
@@ -49,6 +52,7 @@ export declare function commitVotes(ctx: MindContext, pooled: {
|
|
|
49
52
|
end: number;
|
|
50
53
|
w: number;
|
|
51
54
|
}>;
|
|
55
|
+
regionSupport: Map<number, number>;
|
|
52
56
|
steps: DerivationStep[];
|
|
53
57
|
}, sat: SaturationInfo, regions: readonly Region[], regionVoter: ReadonlyArray<{
|
|
54
58
|
id: number;
|
|
@@ -400,6 +400,7 @@ export function poolVotes(ctx, regionVotes, sat, N) {
|
|
|
400
400
|
const votes = new Map();
|
|
401
401
|
const votesIdf = new Map();
|
|
402
402
|
const support = new Map();
|
|
403
|
+
const regionSupport = new Map();
|
|
403
404
|
const steps = [];
|
|
404
405
|
let order = 0;
|
|
405
406
|
for (const pc of pool.values()) {
|
|
@@ -407,14 +408,17 @@ export function poolVotes(ctx, regionVotes, sat, N) {
|
|
|
407
408
|
votes.set(pc.item.id, pc.cost);
|
|
408
409
|
const premises = [];
|
|
409
410
|
const seenRi = new Set();
|
|
411
|
+
let breadthSum = 0;
|
|
410
412
|
for (const c of pc.contributions) {
|
|
411
413
|
const p0 = c.premises[0].item;
|
|
412
414
|
if (p0.kind !== "region" || seenRi.has(p0.ri))
|
|
413
415
|
continue;
|
|
414
416
|
seenRi.add(p0.ri);
|
|
415
417
|
const rv = regionVotes[p0.ri];
|
|
418
|
+
breadthSum += rv.absorbed ?? 1;
|
|
416
419
|
premises.push({ kind: "form", span: [rv.start, rv.end] });
|
|
417
420
|
}
|
|
421
|
+
regionSupport.set(pc.item.id, breadthSum);
|
|
418
422
|
steps.push({
|
|
419
423
|
order: order++,
|
|
420
424
|
move: "pool-vote",
|
|
@@ -444,18 +448,29 @@ export function poolVotes(ctx, regionVotes, sat, N) {
|
|
|
444
448
|
}
|
|
445
449
|
}
|
|
446
450
|
}
|
|
447
|
-
return { votes, votesIdf, support, steps };
|
|
451
|
+
return { votes, votesIdf, support, regionSupport, steps };
|
|
448
452
|
}
|
|
449
453
|
export function commitVotes(ctx, pooled, sat, regions, regionVoter, N) {
|
|
450
|
-
const { votes, votesIdf, support, steps } = pooled;
|
|
454
|
+
const { votes, votesIdf, support, regionSupport, steps } = pooled;
|
|
451
455
|
if (votes.size === 0) {
|
|
452
456
|
traceAttention(ctx, regions, regionVoter, [], steps);
|
|
453
457
|
return { roots: [], ranked: [] };
|
|
454
458
|
}
|
|
459
|
+
// SCALE-INVARIANT confidence — see Attention.breadth's doc. regions.length
|
|
460
|
+
// is the query's OWN full candidate count (most never vote at all), the
|
|
461
|
+
// same denominator the "N of M sub-regions voted" rationale text already
|
|
462
|
+
// reports; regionSupport is that same accounting read PER ANCHOR.
|
|
463
|
+
const totalRegions = Math.max(1, regions.length);
|
|
455
464
|
const ranked = [...votes.entries()]
|
|
456
465
|
.map(([anchor, vote]) => {
|
|
457
466
|
const s = support.get(anchor);
|
|
458
|
-
return {
|
|
467
|
+
return {
|
|
468
|
+
anchor,
|
|
469
|
+
vote,
|
|
470
|
+
start: s.start,
|
|
471
|
+
end: s.end,
|
|
472
|
+
breadth: (regionSupport.get(anchor) ?? 0) / totalRegions,
|
|
473
|
+
};
|
|
459
474
|
})
|
|
460
475
|
.sort((a, b) => b.vote - a.vote);
|
|
461
476
|
const overlaps = (a, b) => a.start < b.end && b.start < a.end;
|
|
@@ -736,6 +751,27 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo) {
|
|
|
736
751
|
continue;
|
|
737
752
|
if (ra.end >= rb.start)
|
|
738
753
|
continue; // overlap or adjacent — nothing between
|
|
754
|
+
// Candidates strictly BETWEEN ra and rb (cand is sorted by start, so
|
|
755
|
+
// that is exactly cand[a+1 .. b-1]) that already cast their OWN vote —
|
|
756
|
+
// genuine, individually-corroborated evidence about what fills the gap
|
|
757
|
+
// — gate the container search below: a joint container is binding
|
|
758
|
+
// evidence only when it is CONSISTENT with that evidence, i.e. its own
|
|
759
|
+
// bytes actually contain what the between-region says. This is the
|
|
760
|
+
// n-ary composition's normal shape (a between-attribute's bytes DO
|
|
761
|
+
// recur inside the joint container, credited as an "extra" below) as
|
|
762
|
+
// opposed to a container that silently substitutes something else for
|
|
763
|
+
// it (e.g. bridging past "Italy" to a container whose interior is
|
|
764
|
+
// "Japan" — a different, contradicting learnt whole).
|
|
765
|
+
// Only a KNOWN (content-addressed, exact) between-region qualifies —
|
|
766
|
+
// an approximate region's resonance climbing "somewhere" is ordinary
|
|
767
|
+
// noise (any ANN query returns SOME nearest neighbour), not evidence
|
|
768
|
+
// this specific gap already means something specific.
|
|
769
|
+
const between = [];
|
|
770
|
+
for (let m = a + 1; m < b; m++) {
|
|
771
|
+
if (strong.has(cand[m]) && !consumed.has(cand[m]) &&
|
|
772
|
+
regions[cand[m]].known)
|
|
773
|
+
between.push(cand[m]);
|
|
774
|
+
}
|
|
739
775
|
// A single KNOWN region covering both: the whole form is already a
|
|
740
776
|
// stored identity that votes directly; its pieces add nothing.
|
|
741
777
|
if (regions.some((r) => r.known && r.start <= ra.start && rb.end <= r.end))
|
|
@@ -781,6 +817,14 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo) {
|
|
|
781
817
|
if (indexOf(query, joined, 0) >= 0)
|
|
782
818
|
continue; // query says it itself
|
|
783
819
|
}
|
|
820
|
+
// CONTRADICTION GUARD: a between-region already carrying its own
|
|
821
|
+
// vote must actually recur in this container's bytes — otherwise
|
|
822
|
+
// the container is a different learnt whole that happens to share
|
|
823
|
+
// ra/rb, and letting it stand in for the gap would silently
|
|
824
|
+
// override evidence the query itself already resolved there.
|
|
825
|
+
if (between.some((bi) => indexOf(bytes, query.subarray(regions[bi].start, regions[bi].end), 0) < 0)) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
784
828
|
let cov = left.length + right.length;
|
|
785
829
|
const extras = [];
|
|
786
830
|
for (const ei of cand) {
|
|
@@ -826,14 +870,6 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo) {
|
|
|
826
870
|
spanStart = Math.min(spanStart, regions[ei].start);
|
|
827
871
|
spanEnd = Math.max(spanEnd, regions[ei].end);
|
|
828
872
|
}
|
|
829
|
-
out.push({
|
|
830
|
-
start: spanStart,
|
|
831
|
-
end: spanEnd,
|
|
832
|
-
canonicalFailed: false, // content-addressed: never saturation-masked
|
|
833
|
-
roots: reach.roots,
|
|
834
|
-
w,
|
|
835
|
-
wFocus: w,
|
|
836
|
-
});
|
|
837
873
|
consumed.add(cand[a]);
|
|
838
874
|
consumed.add(cand[b]);
|
|
839
875
|
for (const ei of bestExtras)
|
|
@@ -843,15 +879,32 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo) {
|
|
|
843
879
|
// vote's bytes are literally part of the learnt whole), and FULL root
|
|
844
880
|
// disjointness is the disagreement test: a vote sharing even one root
|
|
845
881
|
// with the junction corroborates it and keeps its say elsewhere.
|
|
882
|
+
// Counted BEFORE pushing the junction's own vote below: each ORIGINAL
|
|
883
|
+
// region this ascent explains away is evidence the junction speaks
|
|
884
|
+
// for, not evidence lost — `absorbed` (RegionVote's breadth-accounting
|
|
885
|
+
// field) must credit the junction with all of it, not just the ONE
|
|
886
|
+
// pooled axiom it collapses to.
|
|
846
887
|
const containerBytes = cachedRead(ctx, cache, best.id, cap);
|
|
847
888
|
const jointRoots = new Set(reach.roots);
|
|
889
|
+
let explainedAway = 0;
|
|
848
890
|
for (const rv of rvs.votes) {
|
|
849
891
|
if (rv.roots.some((r) => jointRoots.has(r)))
|
|
850
892
|
continue;
|
|
851
893
|
const bytes = query.subarray(rv.start, rv.end);
|
|
852
|
-
if (indexOf(containerBytes, bytes, 0) >= 0)
|
|
894
|
+
if (indexOf(containerBytes, bytes, 0) >= 0 && !superseded.has(rv)) {
|
|
853
895
|
superseded.add(rv);
|
|
896
|
+
explainedAway++;
|
|
897
|
+
}
|
|
854
898
|
}
|
|
899
|
+
out.push({
|
|
900
|
+
start: spanStart,
|
|
901
|
+
end: spanEnd,
|
|
902
|
+
canonicalFailed: false, // content-addressed: never saturation-masked
|
|
903
|
+
roots: reach.roots,
|
|
904
|
+
w,
|
|
905
|
+
wFocus: w,
|
|
906
|
+
absorbed: 1 + explainedAway,
|
|
907
|
+
});
|
|
855
908
|
const label = [cand[a], cand[b], ...bestExtras]
|
|
856
909
|
.sort((x, y) => regions[x].start - regions[y].start)
|
|
857
910
|
.map((ri) => dec(query.subarray(regions[ri].start, regions[ri].end)))
|
|
@@ -54,6 +54,18 @@ export type GItem = {
|
|
|
54
54
|
cover: boolean;
|
|
55
55
|
rec: boolean;
|
|
56
56
|
node?: number;
|
|
57
|
+
/** Set only for a {@link ComputedResult} axiom (an extension's derived
|
|
58
|
+
* value, e.g. ALU arithmetic) — as opposed to a genuinely RECOGNISED
|
|
59
|
+
* learned form. Both set `rec: true` (a computation bridges the cover
|
|
60
|
+
* exactly like a learned terminal answer), but they mean different
|
|
61
|
+
* things for `i..j`'s WIDTH: a recognised form's query-span width is
|
|
62
|
+
* real evidence of how much of the query's meaning it accounts for
|
|
63
|
+
* (liftAnswer's half-dominance framing decision); a computed span's
|
|
64
|
+
* width is operand digit-count, uncorrelated with meaning ("1000 - 421"
|
|
65
|
+
* is wider than "15 * 7" only because the numbers are bigger, not
|
|
66
|
+
* because subtraction is more "the point" of its query). See
|
|
67
|
+
* {@link liftAnswer}. */
|
|
68
|
+
computed?: boolean;
|
|
57
69
|
};
|
|
58
70
|
export declare const STEP = 1;
|
|
59
71
|
export declare const CONCEPT = 10;
|
|
@@ -72,6 +84,9 @@ export interface Seg {
|
|
|
72
84
|
bytes: Uint8Array;
|
|
73
85
|
rec: boolean;
|
|
74
86
|
node?: number;
|
|
87
|
+
/** See the `computed` field of the "out" {@link GItem} — set only for an
|
|
88
|
+
* extension's derived value, never a genuinely recognised learned form. */
|
|
89
|
+
computed?: boolean;
|
|
75
90
|
}
|
|
76
91
|
/** One rule application inside the cover's lightest derivation — the FINEST
|
|
77
92
|
* grain of Sema's core reasoning, one node of the adapted A*LD proof tree. `move`
|
|
@@ -144,7 +159,7 @@ export declare class GraphSearch {
|
|
|
144
159
|
* Any learnt connector between two rewrites is spliced IN by the in-search
|
|
145
160
|
* connector rule (see {@link outRules}), so the returned spans already carry
|
|
146
161
|
* it — there is no post-pass. */
|
|
147
|
-
cover(queryLen: number, sites: ReadonlyArray<Site>, conceptTarget: ReadonlyMap<number, number>, leaves: ReadonlyArray<Leaf>, splits: ReadonlySet<number>, substitutions?: ReadonlyMap<number, Uint8Array>, connectors?: ReadonlyMap<string, Uint8Array>, computedResults?: ReadonlyArray<ComputedResult>,
|
|
162
|
+
cover(queryLen: number, sites: ReadonlyArray<Site>, conceptTarget: ReadonlyMap<number, number>, leaves: ReadonlyArray<Leaf>, splits: ReadonlySet<number>, starts: ReadonlySet<number>, substitutions?: ReadonlyMap<number, Uint8Array>, connectors?: ReadonlyMap<string, Uint8Array>, computedResults?: ReadonlyArray<ComputedResult>,
|
|
148
163
|
/** When given, receives the lightest derivation's rule applications — the
|
|
149
164
|
* full adapted A*LD proof tree as classified {@link DerivationStep}s — for the TOP
|
|
150
165
|
* cover only (a recursive recompletion solves its own sub-cover and is not
|
|
@@ -63,6 +63,7 @@ function readCover(derivation) {
|
|
|
63
63
|
bytes: out.bytes,
|
|
64
64
|
rec: out.rec,
|
|
65
65
|
node: out.node,
|
|
66
|
+
computed: out.computed,
|
|
66
67
|
});
|
|
67
68
|
}
|
|
68
69
|
node = node.premises[0];
|
|
@@ -205,7 +206,7 @@ export class GraphSearch {
|
|
|
205
206
|
* Any learnt connector between two rewrites is spliced IN by the in-search
|
|
206
207
|
* connector rule (see {@link outRules}), so the returned spans already carry
|
|
207
208
|
* it — there is no post-pass. */
|
|
208
|
-
cover(queryLen, sites, conceptTarget, leaves, splits, substitutions, connectors, computedResults,
|
|
209
|
+
cover(queryLen, sites, conceptTarget, leaves, splits, starts, substitutions, connectors, computedResults,
|
|
209
210
|
/** When given, receives the lightest derivation's rule applications — the
|
|
210
211
|
* full adapted A*LD proof tree as classified {@link DerivationStep}s — for the TOP
|
|
211
212
|
* cover only (a recursive recompletion solves its own sub-cover and is not
|
|
@@ -221,6 +222,7 @@ export class GraphSearch {
|
|
|
221
222
|
sites,
|
|
222
223
|
leaves,
|
|
223
224
|
splits,
|
|
225
|
+
starts,
|
|
224
226
|
}, conceptTarget, substitutions, connectors, computedResults, onDerivation);
|
|
225
227
|
}
|
|
226
228
|
/** Build the deduction system for one span and return its lightest cover's
|
|
@@ -237,7 +239,7 @@ export class GraphSearch {
|
|
|
237
239
|
* decomposes, two recomposes, any mix — and stops only when it reaches a node
|
|
238
240
|
* that leads nowhere new, never at an arbitrary count. */
|
|
239
241
|
solve(spanLen, recognition, conceptTarget, substitutions, connectors, computedResults, onDerivation) {
|
|
240
|
-
const system = this.buildSearch(spanLen, recognition.sites, conceptTarget, recognition.leaves, recognition.splits, substitutions, connectors, computedResults);
|
|
242
|
+
const system = this.buildSearch(spanLen, recognition.sites, conceptTarget, recognition.leaves, recognition.splits, recognition.starts, substitutions, connectors, computedResults);
|
|
241
243
|
const derivation = lightestDerivation(system);
|
|
242
244
|
// When covering under a substitution map (articulation), a form→out rule is
|
|
243
245
|
// the form EMITTING the asker's voice, not grounding to its own answer — so
|
|
@@ -257,8 +259,15 @@ export class GraphSearch {
|
|
|
257
259
|
* adjacent fragments toward a known leaf (findLeaf) or branch (findBranch);
|
|
258
260
|
* a completion fused with its neighbour may spell a deeper learned form the
|
|
259
261
|
* flat probes can't name, recovered canonically by {@link resolve}. */
|
|
260
|
-
buildSearch(queryLen, sites, conceptTarget, leaves, splits, substitutions, connectors, computedResults) {
|
|
262
|
+
buildSearch(queryLen, sites, conceptTarget, leaves, splits, starts, substitutions, connectors, computedResults) {
|
|
261
263
|
const W = this.maxGroup; // fusible span ceiling (shortest composite bound)
|
|
264
|
+
// Same corpus-scale hub floor {@link atomIsHub}/{@link atomReach} (traverse.ts)
|
|
265
|
+
// derive for byte atoms — duplicated here rather than imported because this
|
|
266
|
+
// module is deliberately host-based (no MindContext), and both inputs
|
|
267
|
+
// (maxGroup, edgeSourceCount) are already in scope with no ctx needed. If
|
|
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())));
|
|
262
271
|
const nodeBytes = (n) => this.store.bytesPrefix(n, ALL);
|
|
263
272
|
// Content-addressed probes over the store's hash-cons maps — the same keys
|
|
264
273
|
// training filled. No byte-by-byte trie walk.
|
|
@@ -350,6 +359,7 @@ export class GraphSearch {
|
|
|
350
359
|
cover: true,
|
|
351
360
|
rec: true,
|
|
352
361
|
node: u.node,
|
|
362
|
+
computed: true,
|
|
353
363
|
},
|
|
354
364
|
cost: STEP,
|
|
355
365
|
};
|
|
@@ -388,6 +398,8 @@ export class GraphSearch {
|
|
|
388
398
|
return this.outRules(it, {
|
|
389
399
|
W,
|
|
390
400
|
splits,
|
|
401
|
+
starts,
|
|
402
|
+
atomsAreHubs,
|
|
391
403
|
coversDone,
|
|
392
404
|
outsByStart,
|
|
393
405
|
outsByEnd,
|
|
@@ -845,7 +857,23 @@ export class GraphSearch {
|
|
|
845
857
|
* enters the graph as a form the moment it names a node. */
|
|
846
858
|
*fuse(l, r, ctx) {
|
|
847
859
|
const bytes = concat2(l.bytes, r.bytes);
|
|
848
|
-
|
|
860
|
+
// A PURE leaf-leaf fuse (neither side already a recognised completion)
|
|
861
|
+
// is opportunistic cross-leaf recovery exactly like recognition.ts's own
|
|
862
|
+
// canonical chain — findLeaf/findBranch here have no idea WHY these two
|
|
863
|
+
// leaves are adjacent, only that their concatenation happens to spell a
|
|
864
|
+
// trained form ("hi" recovered from "W[hi]ch"). The same corpus-scale
|
|
865
|
+
// caution recognition.ts's `boundary` gate applies: trust it fully when
|
|
866
|
+
// `l.i` is a position the query's OWN fold chose as a boundary (real
|
|
867
|
+
// structural evidence); past the scale where atoms themselves stop
|
|
868
|
+
// discriminating, an interior offset's opportunistic match is noise, not
|
|
869
|
+
// a genuine cross-leaf recovery. A completion-involved fuse (l.rec ||
|
|
870
|
+
// r.rec) is a different, legitimate case — a rewrite growing into its
|
|
871
|
+
// neighbour — and is exempt, same as recognition.ts's rec-derived sites.
|
|
872
|
+
const trusted = l.rec || r.rec || ctx.starts.has(l.i) ||
|
|
873
|
+
!ctx.atomsAreHubs;
|
|
874
|
+
let node = (trusted && bytes.length <= ctx.W)
|
|
875
|
+
? ctx.findLeafU(bytes)
|
|
876
|
+
: undefined;
|
|
849
877
|
// Whether this pair ACTUALLY forms a 2-child branch — the hard evidence
|
|
850
878
|
// that the fused bytes are a learned form worth keeping alive. Derived
|
|
851
879
|
// from the same findBranchU probe that sets `node`; when false, the pair
|
|
@@ -853,7 +881,8 @@ export class GraphSearch {
|
|
|
853
881
|
// node cannot contribute to any further fusion (findBranch needs two
|
|
854
882
|
// nodes, resolve needs a completion, and findLeaf already had its chance).
|
|
855
883
|
let pairFormsBranch = false;
|
|
856
|
-
if (node === undefined && l.node !== undefined &&
|
|
884
|
+
if (trusted && node === undefined && l.node !== undefined &&
|
|
885
|
+
r.node !== undefined) {
|
|
857
886
|
node = ctx.findBranchU([l.node, r.node]);
|
|
858
887
|
pairFormsBranch = node !== undefined;
|
|
859
888
|
}
|
|
@@ -18,7 +18,7 @@ export declare function internTreeIds(ctx: MindContext, node: Sema, ids: Map<Sem
|
|
|
18
18
|
export declare function indexSubSpans(ctx: MindContext, tree: Sema, ids: Map<Sema, number>): Promise<boolean>;
|
|
19
19
|
/** Perceive, intern, and index a single input. Returns the perceived tree,
|
|
20
20
|
* root id, id map, and the changed (new) subtrees for halo reinforcement. */
|
|
21
|
-
export declare function deposit(ctx: MindContext, input: Input, track: boolean): Promise<{
|
|
21
|
+
export declare function deposit(ctx: MindContext, input: Input, track: boolean, conversational?: boolean): Promise<{
|
|
22
22
|
tree: Sema;
|
|
23
23
|
rootId: number;
|
|
24
24
|
ids: Map<Sema, number>;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// node. A fact is an EDGE between node ids; recall traverses edges.
|
|
5
5
|
import { bindSeat, companySignature, isChunk } from "../sema.js";
|
|
6
6
|
import { changedNodes } from "./types.js";
|
|
7
|
-
import { inputBytes, perceiveDeposit, resolve, } from "./primitives.js";
|
|
7
|
+
import { inputBytes, latin1Key, perceiveDeposit, resolve, } from "./primitives.js";
|
|
8
8
|
import { canonicalWindows, leafIdPrefix } from "./canonical.js";
|
|
9
9
|
import { fold as foldVecs } from "../sema.js";
|
|
10
10
|
/** Intern a perceived tree into node ids, bottom-up, sharing equal subtrees.
|
|
@@ -103,7 +103,7 @@ export async function indexSubSpans(ctx, tree, ids) {
|
|
|
103
103
|
}
|
|
104
104
|
/** Perceive, intern, and index a single input. Returns the perceived tree,
|
|
105
105
|
* root id, id map, and the changed (new) subtrees for halo reinforcement. */
|
|
106
|
-
export async function deposit(ctx, input, track) {
|
|
106
|
+
export async function deposit(ctx, input, track, conversational = false) {
|
|
107
107
|
const bytes = inputBytes(ctx, input);
|
|
108
108
|
// Deposit-shaped perception: stable-prefix tree SEEDING (see
|
|
109
109
|
// perceiveDeposit) — an accumulated context re-folds only its new suffix,
|
|
@@ -111,8 +111,13 @@ export async function deposit(ctx, input, track) {
|
|
|
111
111
|
// (no store-probe fallback): a knownPrefixLength scan on every novel fact
|
|
112
112
|
// would cost O(n²) hashing, while conversation replays are always warm —
|
|
113
113
|
// re-deposition replays from the first turn, rebuilding the cache as it
|
|
114
|
-
// goes.
|
|
115
|
-
|
|
114
|
+
// goes. `conversational` scopes the STABLE-PREFIX variant (turn-boundary
|
|
115
|
+
// folding, matching query-time perception) to ingestPair's own growing
|
|
116
|
+
// context argument — a bare ingestOne deposit whose bytes merely happen
|
|
117
|
+
// to extend an earlier UNRELATED deposit (no conversational relationship)
|
|
118
|
+
// must keep the plain fold, or two coincidentally-prefix-sharing facts
|
|
119
|
+
// would stop sharing structure with each other.
|
|
120
|
+
const tree = perceiveDeposit(ctx, bytes, conversational);
|
|
116
121
|
const ids = new Map();
|
|
117
122
|
const rootId = await internTreeIds(ctx, tree, ids);
|
|
118
123
|
const indexed = await indexSubSpans(ctx, tree, ids);
|
|
@@ -185,9 +190,19 @@ async function propagateSuffixes(ctx, src, dst) {
|
|
|
185
190
|
}
|
|
186
191
|
/** Ingest a pair (context, continuation) — learn an edge and pour halos. */
|
|
187
192
|
export async function ingestPair(ctx, ctxInput, cont) {
|
|
188
|
-
const c = await deposit(ctx, ctxInput, true);
|
|
193
|
+
const c = await deposit(ctx, ctxInput, true, true);
|
|
189
194
|
const cont_ = await deposit(ctx, cont, false);
|
|
190
195
|
const ctxId = c.rootId, contId = cont_.rootId;
|
|
196
|
+
// Stamp this turn's continuation onto its own cache entry — the proof a
|
|
197
|
+
// FUTURE, longer ctxInput needs (see perceiveDeposit) to recognise itself
|
|
198
|
+
// as this conversation's genuine next turn rather than an unrelated fact
|
|
199
|
+
// that happens to share this ctxInput's byte prefix.
|
|
200
|
+
{
|
|
201
|
+
const ctxBytes = inputBytes(ctx, ctxInput);
|
|
202
|
+
const entry = ctx._depositTrees.get(latin1Key(ctxBytes));
|
|
203
|
+
if (entry !== undefined)
|
|
204
|
+
entry.nextBytes = inputBytes(ctx, cont);
|
|
205
|
+
}
|
|
191
206
|
await ctx.store.link(ctxId, contId);
|
|
192
207
|
await propagateSuffixes(ctx, ctxId, contId);
|
|
193
208
|
// Halos pour company SIGNATURES (identity), not gists (content) — see
|
|
@@ -12,7 +12,14 @@ import { unexplainedLabel } from "../rationale.js";
|
|
|
12
12
|
export function aluToMechanism(alu) {
|
|
13
13
|
return {
|
|
14
14
|
name: "alu",
|
|
15
|
-
|
|
15
|
+
// Not a cover derivation: cover.ts composes an answer by walking
|
|
16
|
+
// recognised query STRUCTURE; the ALU evaluates a recognised expression
|
|
17
|
+
// to its authoritative result and hands the bytes back untouched. It
|
|
18
|
+
// shares cover's near-zero floor (computation always wins, masked into
|
|
19
|
+
// cover's own search — see mechanisms/cover.ts), but the candidate this
|
|
20
|
+
// produces is not one of cover's derivations, so it carries its own
|
|
21
|
+
// honest label, the same way extract/cast/recall each carry theirs.
|
|
22
|
+
provenance: "alu",
|
|
16
23
|
parse: (query) => alu.parse(query),
|
|
17
24
|
async floor(_ctx, _query, pre, _worthRunning) {
|
|
18
25
|
return pre.computed.length > 0 ? 0 : null;
|
|
@@ -70,19 +70,37 @@ const MIN_WEAVE = 2;
|
|
|
70
70
|
*
|
|
71
71
|
* Returns the array of {@link CastResult}s that fired (possibly empty). */
|
|
72
72
|
export async function counterfactualTransfer(ctx, query, pre) {
|
|
73
|
+
// Opened unconditionally, at entry — the same convention recall.ts's
|
|
74
|
+
// recallByResonance and extraction.ts's extractBySkill use, so every exit
|
|
75
|
+
// path (five gates below, then the schemas themselves) closes through
|
|
76
|
+
// ONE scope and inspectRationale never hits a silent dead end. Only the
|
|
77
|
+
// first two gates duplicate floor()'s own admissible bound (query length,
|
|
78
|
+
// ranked anchor count) — required to stay in sync per this function's own
|
|
79
|
+
// doc comment above, and effectively dead through the ordinary pipeline
|
|
80
|
+
// (floor() returning null already stops run() from being called at all),
|
|
81
|
+
// but this function is also exported and callable directly, so they stay
|
|
82
|
+
// and get the same honest trace as everything past them.
|
|
83
|
+
const t = ctx.trace?.enter("counterfactual", [rItem(query, "query")]);
|
|
84
|
+
const fail = (note) => {
|
|
85
|
+
t?.done([], note);
|
|
86
|
+
return [];
|
|
87
|
+
};
|
|
73
88
|
const quantum = ctx.space.maxGroup;
|
|
74
89
|
if (query.length < 2 * quantum || ctx.store.edgeSourceCount() === 0) {
|
|
75
|
-
return
|
|
90
|
+
return fail("query below the two-quantum floor, or no edges learnt yet");
|
|
76
91
|
}
|
|
77
92
|
const { roots, ranked } = await pre.attention();
|
|
78
|
-
if (ranked.length < 2)
|
|
79
|
-
return
|
|
93
|
+
if (ranked.length < 2) {
|
|
94
|
+
return fail(`only ${ranked.length} ranked anchor(s) — CAST needs at least two`);
|
|
95
|
+
}
|
|
80
96
|
const weave = await pre.weave();
|
|
81
97
|
const points = weave.points;
|
|
82
98
|
const depth = weave.depth;
|
|
83
99
|
const aligned = points.length;
|
|
84
|
-
if (aligned < 2)
|
|
85
|
-
return
|
|
100
|
+
if (aligned < 2) {
|
|
101
|
+
return fail(`only ${aligned} structure(s) aligned across the query — CAST needs ` +
|
|
102
|
+
`at least two to transfer between`);
|
|
103
|
+
}
|
|
86
104
|
// ── Frame gate (half-dominance, weave-local) ─────────────────────────
|
|
87
105
|
// A byte is FRAME when more than MIN_WEAVE aligned structures cover it
|
|
88
106
|
// AND those structures are a majority of all aligned structures.
|
|
@@ -120,14 +138,20 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
120
138
|
const isRoot = (id) => roots.some((r) => r.anchor === id);
|
|
121
139
|
// The weave must touch a COMMITTED point of attention: the dominant
|
|
122
140
|
// structure itself, or another aligned point the climb committed to.
|
|
123
|
-
if (!points.some((p) => isRoot(p.anchor)))
|
|
141
|
+
if (!points.some((p) => isRoot(p.anchor))) {
|
|
142
|
+
t?.done([
|
|
143
|
+
...points.map((p) => rNode(ctx, p.anchor, "aligned")),
|
|
144
|
+
...roots.map((r) => rNode(ctx, r.anchor, "committed-root")),
|
|
145
|
+
], `${points.length} aligned structure(s), but none is one of the climb's ` +
|
|
146
|
+
`${roots.length} committed root(s) — CAST refuses to transfer through ` +
|
|
147
|
+
`content the climb itself never settled on`);
|
|
124
148
|
return [];
|
|
149
|
+
}
|
|
125
150
|
const woven = points.some((p) => p.runs.some((r) => !pre.rec.sites.some((s) => r.qs >= s.start && r.qe <= s.end)));
|
|
126
|
-
if (!woven)
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
]);
|
|
151
|
+
if (!woven) {
|
|
152
|
+
return fail(`every aligned run restates a recognised query site — nothing was ` +
|
|
153
|
+
`actually WOVEN across structures, so there is nothing to transfer`);
|
|
154
|
+
}
|
|
131
155
|
// Each schema tried below RECORDS its candidate (when it fires) rather than
|
|
132
156
|
// returning immediately — every schema that succeeds contributes its own
|
|
133
157
|
// candidate, and the grounding decider's own weight comparison (not CAST's
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// cover runs FIRST in defaultMechanisms: a computed-backed cover becomes a
|
|
9
9
|
// near-zero-cost incumbent that prunes the other mechanisms through the
|
|
10
10
|
// ordinary admissible-floor check, with no extension special-case anywhere.
|
|
11
|
+
import { bytesEqual, indexOf } from "../../bytes.js";
|
|
11
12
|
import { read, resolve } from "../primitives.js";
|
|
12
13
|
import { guidedFirst } from "../traverse.js";
|
|
13
14
|
import { conceptHop } from "../match.js";
|
|
@@ -121,11 +122,18 @@ export const coverMechanism = {
|
|
|
121
122
|
return [];
|
|
122
123
|
const connectors = await resolveConnectors(ctx, sites);
|
|
123
124
|
let splits = rec.splits;
|
|
125
|
+
let starts = rec.starts;
|
|
124
126
|
if (computed.length > 0) {
|
|
125
127
|
splits = new Set(rec.splits);
|
|
128
|
+
starts = new Set(rec.starts);
|
|
126
129
|
for (const u of computed) {
|
|
127
130
|
splits.add(u.i);
|
|
128
131
|
splits.add(u.j);
|
|
132
|
+
// A computation's own boundaries carry the same fold-level evidence
|
|
133
|
+
// a chunk boundary does — "computation always wins" (see the header
|
|
134
|
+
// comment) extends to being trusted ground for cross-leaf recovery.
|
|
135
|
+
starts.add(u.i);
|
|
136
|
+
starts.add(u.j);
|
|
129
137
|
}
|
|
130
138
|
}
|
|
131
139
|
const concepts = await resolveConcepts(ctx, sites);
|
|
@@ -149,7 +157,7 @@ export const coverMechanism = {
|
|
|
149
157
|
])),
|
|
150
158
|
...computedResults.map((u) => rItem(u.bytes, "computed")),
|
|
151
159
|
], coverDeps.length ? coverDeps : undefined);
|
|
152
|
-
const solved = ctx.search.cover(query.length, sites, concepts, rec.leaves, splits, undefined, connectors, computedResults, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
|
|
160
|
+
const solved = ctx.search.cover(query.length, sites, concepts, rec.leaves, splits, starts, undefined, connectors, computedResults, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
|
|
153
161
|
const segs = solved && solved.segs;
|
|
154
162
|
tCover?.done(segs === null
|
|
155
163
|
? []
|
|
@@ -158,6 +166,37 @@ export const coverMechanism = {
|
|
|
158
166
|
: "lightest derivation: the chosen spans, left to right");
|
|
159
167
|
if (segs === null)
|
|
160
168
|
return [];
|
|
169
|
+
// A chosen span's SUBSTITUTED bytes (an edge followed from a recognised
|
|
170
|
+
// site, not the site's own literal text read back) that equal a byte
|
|
171
|
+
// span the query ALREADY CONTAINS elsewhere restates part of the
|
|
172
|
+
// question — never an answer (the same principle recall.ts's tiers
|
|
173
|
+
// apply to a whole-query projection: "a projection that is a proper
|
|
174
|
+
// byte-subspan of the query restates part of the question"). A
|
|
175
|
+
// recognised site that is itself an entire PRIOR TURN of a multi-turn
|
|
176
|
+
// query is exactly this shape: it carries a genuine learnt
|
|
177
|
+
// continuation, but that continuation is something the asker already
|
|
178
|
+
// said moments later in the SAME query, not a new answer — the cover
|
|
179
|
+
// search has no notion of "turn" to gate this itself, so the check
|
|
180
|
+
// belongs here, over the derivation it already chose.
|
|
181
|
+
const W = ctx.space.maxGroup;
|
|
182
|
+
for (const s of segs) {
|
|
183
|
+
if (!s.rec)
|
|
184
|
+
continue;
|
|
185
|
+
const literal = s.j - s.i === s.bytes.length &&
|
|
186
|
+
bytesEqual(s.bytes, query.subarray(s.i, s.j));
|
|
187
|
+
if (literal)
|
|
188
|
+
continue;
|
|
189
|
+
// A span shorter than one river window is exactly the "ice"/"c"
|
|
190
|
+
// case: a chain hop's short terminal coincidentally recurring as a
|
|
191
|
+
// substring of an unrelated longer word. Below the quantum, byte
|
|
192
|
+
// overlap is chance, not evidence — the same floor identityBar and
|
|
193
|
+
// reachThreshold hold every other structural-overlap claim to.
|
|
194
|
+
if (s.bytes.length >= W && s.bytes.length < query.length &&
|
|
195
|
+
indexOf(query, s.bytes, 0) >= 0) {
|
|
196
|
+
ctx.trace?.step("restatedSpan", [rItem(s.bytes, "substituted", s.node, [s.i, s.j])], [], "the chosen span's substitution already occurs elsewhere in the query — restates it, not an answer");
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
161
200
|
const composed = liftAnswer(segs, query.length);
|
|
162
201
|
if (composed === null)
|
|
163
202
|
return [];
|