@hviana/sema 0.4.7 → 0.5.0
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 +290 -77
- package/HOW_IT_WORKS.md +2170 -735
- package/dist/example/train_base.d.ts +9 -3
- package/dist/example/train_base.js +21 -4
- package/dist/src/canon.d.ts +19 -0
- package/dist/src/canon.js +28 -0
- package/dist/src/geometry.d.ts +52 -0
- package/dist/src/geometry.js +87 -1
- package/dist/src/mind/bridge.js +27 -1
- package/dist/src/mind/frame-filler.d.ts +15 -0
- package/dist/src/mind/frame-filler.js +535 -0
- package/dist/src/mind/learning.js +6 -11
- package/dist/src/mind/mechanisms/cast.js +72 -2
- package/dist/src/mind/mechanisms/cover.js +6 -1
- package/dist/src/mind/mechanisms/extraction.js +27 -0
- package/dist/src/mind/mechanisms/recall.js +214 -34
- package/dist/src/mind/mind.d.ts +49 -1
- package/dist/src/mind/mind.js +137 -10
- package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
- package/dist/src/mind/pipeline.js +29 -1
- package/dist/src/mind/prefix-completion.d.ts +59 -0
- package/dist/src/mind/prefix-completion.js +270 -0
- package/dist/src/mind/primitives.d.ts +29 -10
- package/dist/src/mind/primitives.js +52 -61
- package/dist/src/mind/recognition.js +119 -9
- package/dist/src/mind/traverse.d.ts +32 -0
- package/dist/src/mind/traverse.js +52 -0
- package/dist/src/mind/types.d.ts +55 -16
- package/dist/src/mind/types.js +68 -19
- package/dist/src/store.d.ts +21 -0
- package/dist/src/store.js +21 -0
- package/example/train_base.ts +21 -4
- package/package.json +1 -1
- package/src/canon.ts +28 -0
- package/src/geometry.ts +100 -1
- package/src/mind/bridge.ts +34 -0
- package/src/mind/frame-filler.ts +604 -0
- package/src/mind/learning.ts +5 -9
- package/src/mind/mechanisms/cast.ts +70 -2
- package/src/mind/mechanisms/cover.ts +6 -1
- package/src/mind/mechanisms/extraction.ts +27 -0
- package/src/mind/mechanisms/recall.ts +236 -37
- package/src/mind/mind.ts +154 -14
- package/src/mind/pipeline-mechanism.ts +7 -0
- package/src/mind/pipeline.ts +33 -1
- package/src/mind/prefix-completion.ts +314 -0
- package/src/mind/primitives.ts +59 -70
- package/src/mind/recognition.ts +117 -6
- package/src/mind/traverse.ts +52 -0
- package/src/mind/types.ts +98 -42
- package/src/store.ts +25 -0
- package/test/13-conversation.test.mjs +13 -0
- package/test/57-fusion-order.test.mjs +65 -0
- package/test/66-query-edge-whitespace.test.mjs +99 -0
- package/test/67-climb-anchor-breadth.test.mjs +113 -0
- package/test/68-extraction-unanchored.test.mjs +79 -0
- package/test/69-frame-filler.test.mjs +115 -0
- package/test/70-prefix-completion.test.mjs +170 -0
- package/test/71-embedded-canon-equivalence.test.mjs +121 -0
- package/test/72-prefix-candidate-supply.test.mjs +114 -0
- package/test/73-scaffolding-only-bridge-abstains.test.mjs +178 -0
- package/test/74-prefix-trap-not-sprung-early.test.mjs +114 -0
- package/test/75-multiturn-context-optimisation.test.mjs +1082 -0
|
@@ -62,9 +62,15 @@ export declare function bestOasstPath(root: OasstNode): OasstTurn[];
|
|
|
62
62
|
* turn experiences and local adjacent-pair facts are NOT emitted (they are
|
|
63
63
|
* subsumed by it and would merely replicate the content).
|
|
64
64
|
*
|
|
65
|
-
* The walk is
|
|
66
|
-
* ("teachConversation"): each turn is the continuation of all prior turns
|
|
67
|
-
*
|
|
65
|
+
* The walk is the pattern proven in test/13-conversation.test.mjs
|
|
66
|
+
* ("teachConversation"): each turn is the continuation of all prior turns,
|
|
67
|
+
* with BARE turn text — NO "User:/Assistant:" labels. The SHAPE is identical
|
|
68
|
+
* (cumulative context → next turn); the join string is not, and does not need
|
|
69
|
+
* to be — that file joins with nothing and this corpus joins with "\n" (see
|
|
70
|
+
* `accumulate`). Saying "byte-for-byte", as this comment used to, invites the
|
|
71
|
+
* reading that the two must agree on a separator. They must not agree,
|
|
72
|
+
* because there is nothing to agree about: turn boundaries are offsets, and
|
|
73
|
+
* the join string is just corpus text. Roles already
|
|
68
74
|
* alternate by position in an oasst2 best-path (the root is a prompter), so a
|
|
69
75
|
* label adds nothing the position does not, while a clean continuation matches
|
|
70
76
|
* the test's recall (predictNext queries bare prior turns) and lets a turn share
|
|
@@ -594,7 +594,18 @@ const isEpisode = (it) => typeof it !== "string";
|
|
|
594
594
|
/** Build the accumulated-context episodes of a turn sequence: each successive
|
|
595
595
|
* turn is the continuation of ALL the turns before it joined together. This is
|
|
596
596
|
* the same cumulative-context shape a multi-turn conversation deposits, so the
|
|
597
|
-
* store learns to continue a growing context.
|
|
597
|
+
* store learns to continue a growing context.
|
|
598
|
+
*
|
|
599
|
+
* The "\n" below is a CORPUS choice, not a protocol. oasst2 turns are
|
|
600
|
+
* paragraphs, and reading them back with the newlines kept is how this corpus
|
|
601
|
+
* reads naturally; a different corpus may join with nothing, and
|
|
602
|
+
* test/13-conversation.test.mjs does exactly that. Neither has to match the
|
|
603
|
+
* other, because Sema never scans content for turn boundaries — those are
|
|
604
|
+
* offsets the Conversation API carries beside the bytes (see Mind.addTurn's
|
|
605
|
+
* "ON SEPARATORS" note). The newline here is simply part of the text this
|
|
606
|
+
* store learnt, so anything replaying this corpus feeds it back as part of
|
|
607
|
+
* the turn: `addTurn(conv, "\n" + turnText)`. It is not a convention the
|
|
608
|
+
* engine, the API, or the tests have to agree on. */
|
|
598
609
|
function accumulate(turns) {
|
|
599
610
|
const out = [];
|
|
600
611
|
for (let i = 1; i < turns.length; i++) {
|
|
@@ -709,9 +720,15 @@ export function bestOasstPath(root) {
|
|
|
709
720
|
* turn experiences and local adjacent-pair facts are NOT emitted (they are
|
|
710
721
|
* subsumed by it and would merely replicate the content).
|
|
711
722
|
*
|
|
712
|
-
* The walk is
|
|
713
|
-
* ("teachConversation"): each turn is the continuation of all prior turns
|
|
714
|
-
*
|
|
723
|
+
* The walk is the pattern proven in test/13-conversation.test.mjs
|
|
724
|
+
* ("teachConversation"): each turn is the continuation of all prior turns,
|
|
725
|
+
* with BARE turn text — NO "User:/Assistant:" labels. The SHAPE is identical
|
|
726
|
+
* (cumulative context → next turn); the join string is not, and does not need
|
|
727
|
+
* to be — that file joins with nothing and this corpus joins with "\n" (see
|
|
728
|
+
* `accumulate`). Saying "byte-for-byte", as this comment used to, invites the
|
|
729
|
+
* reading that the two must agree on a separator. They must not agree,
|
|
730
|
+
* because there is nothing to agree about: turn boundaries are offsets, and
|
|
731
|
+
* the join string is just corpus text. Roles already
|
|
715
732
|
* alternate by position in an oasst2 best-path (the root is a prompter), so a
|
|
716
733
|
* label adds nothing the position does not, while a clean continuation matches
|
|
717
734
|
* the test's recall (predictNext queries bare prior turns) and lets a turn share
|
package/dist/src/canon.d.ts
CHANGED
|
@@ -24,3 +24,22 @@ export declare function textCanon(bytes: Uint8Array): Uint8Array;
|
|
|
24
24
|
* is keyed on. Same construction as the node table's content hash; a
|
|
25
25
|
* collision is resolved by verifying canon(stored) === key, never trusted. */
|
|
26
26
|
export declare function canonHash(key: Uint8Array): number;
|
|
27
|
+
/** The span of `bytes` between its first and last non-whitespace byte — the
|
|
28
|
+
* QUESTION, with the caller's edge spacing dropped. Returns a subarray (no
|
|
29
|
+
* copy), and the original when there is nothing to trim.
|
|
30
|
+
*
|
|
31
|
+
* THIS LIVES HERE, not in the core byte utilities, for the reason stated at
|
|
32
|
+
* the top of this file: "nothing in the store or the mind's core knows what
|
|
33
|
+
* 'case' or 'whitespace' is". Edge spacing is a TEXT fact — for a binary or
|
|
34
|
+
* grid modality 0x20 is content, not presentation — so it belongs beside the
|
|
35
|
+
* text canonicalizer, is injected on the same modality test, and never leaks
|
|
36
|
+
* into a mechanism. A modality that supplies its own canon supplies its own
|
|
37
|
+
* reading of "edge" too, or none.
|
|
38
|
+
*
|
|
39
|
+
* Why trimming is sound HERE when {@link textCanon} deliberately refuses it:
|
|
40
|
+
* canon preserves edge whitespace because the hazard is a recognised SUB-span
|
|
41
|
+
* swallowing the boundary byte that separates it from its neighbour (observed:
|
|
42
|
+
* "ice " matching the stored "ice"). At the outer edges of a WHOLE input
|
|
43
|
+
* there is no neighbour — nothing precedes byte 0, nothing follows the last
|
|
44
|
+
* byte — so that hazard cannot arise, and only there. */
|
|
45
|
+
export declare function textEdgeTrim(bytes: Uint8Array): Uint8Array;
|
package/dist/src/canon.js
CHANGED
|
@@ -55,3 +55,31 @@ export function canonHash(key) {
|
|
|
55
55
|
}
|
|
56
56
|
return h >>> 0;
|
|
57
57
|
}
|
|
58
|
+
/** The span of `bytes` between its first and last non-whitespace byte — the
|
|
59
|
+
* QUESTION, with the caller's edge spacing dropped. Returns a subarray (no
|
|
60
|
+
* copy), and the original when there is nothing to trim.
|
|
61
|
+
*
|
|
62
|
+
* THIS LIVES HERE, not in the core byte utilities, for the reason stated at
|
|
63
|
+
* the top of this file: "nothing in the store or the mind's core knows what
|
|
64
|
+
* 'case' or 'whitespace' is". Edge spacing is a TEXT fact — for a binary or
|
|
65
|
+
* grid modality 0x20 is content, not presentation — so it belongs beside the
|
|
66
|
+
* text canonicalizer, is injected on the same modality test, and never leaks
|
|
67
|
+
* into a mechanism. A modality that supplies its own canon supplies its own
|
|
68
|
+
* reading of "edge" too, or none.
|
|
69
|
+
*
|
|
70
|
+
* Why trimming is sound HERE when {@link textCanon} deliberately refuses it:
|
|
71
|
+
* canon preserves edge whitespace because the hazard is a recognised SUB-span
|
|
72
|
+
* swallowing the boundary byte that separates it from its neighbour (observed:
|
|
73
|
+
* "ice " matching the stored "ice"). At the outer edges of a WHOLE input
|
|
74
|
+
* there is no neighbour — nothing precedes byte 0, nothing follows the last
|
|
75
|
+
* byte — so that hazard cannot arise, and only there. */
|
|
76
|
+
export function textEdgeTrim(bytes) {
|
|
77
|
+
const space = (b) => b === 0x20 || b === 0x09 || b === 0x0a || b === 0x0d;
|
|
78
|
+
let from = 0;
|
|
79
|
+
let to = bytes.length;
|
|
80
|
+
while (from < to && space(bytes[from]))
|
|
81
|
+
from++;
|
|
82
|
+
while (to > from && space(bytes[to - 1]))
|
|
83
|
+
to--;
|
|
84
|
+
return from === 0 && to === bytes.length ? bytes : bytes.subarray(from, to);
|
|
85
|
+
}
|
package/dist/src/geometry.d.ts
CHANGED
|
@@ -131,6 +131,58 @@ export declare function knownPrefixLength(bytes: Uint8Array, leafAt: (i: number)
|
|
|
131
131
|
* turn extend perception instead of refolding it: identical prefixes
|
|
132
132
|
* produce identical subtrees regardless of what follows them. */
|
|
133
133
|
export declare function bytesToTree(space: Space, alphabet: Alphabet, bytes: Uint8Array, leafAt?: (i: number) => number | null, lookup?: (leafIds: number[]) => number | null, boundaries?: readonly number[]): Sema;
|
|
134
|
+
/** A plain content fold's reusable state: the level-0 cut edges over the whole
|
|
135
|
+
* stream and each segment's independently-folded root. See
|
|
136
|
+
* {@link contentFoldIncremental}. */
|
|
137
|
+
export interface ContentFold {
|
|
138
|
+
edges: number[];
|
|
139
|
+
segs: Folded[];
|
|
140
|
+
}
|
|
141
|
+
/** {@link contentFoldSpan} over a WHOLE stream, reusing the segments a previous
|
|
142
|
+
* fold of a byte-identical prefix already produced.
|
|
143
|
+
*
|
|
144
|
+
* WHY THIS IS SOUND, AND WHY IT NEEDS NO BOUNDARIES. A level-0 segment is a
|
|
145
|
+
* pure function of its own bytes ({@link flatFold} reads nothing else), so
|
|
146
|
+
* reusing one whose [start,end) is unchanged is bit-identical to refolding it
|
|
147
|
+
* — the cache can never change the tree, only skip work. And the cuts
|
|
148
|
+
* themselves are stable under APPEND: {@link contentLevels} decides each cut
|
|
149
|
+
* from a rolling hash over a local window, so bytes added at the right edge
|
|
150
|
+
* cannot move a cut to their left (measured over a growing 12-turn context:
|
|
151
|
+
* 100% of prior cuts survive every append, zero tail churn). Together those
|
|
152
|
+
* two facts are the whole optimisation — a grown stream refolds only the
|
|
153
|
+
* segments at its right edge.
|
|
154
|
+
*
|
|
155
|
+
* This is the reuse the conversation path wants, and it costs NOTHING in
|
|
156
|
+
* structure: the tree is exactly the tree {@link bytesToTree} builds for the
|
|
157
|
+
* same bytes with no boundary set at all. Turn boundaries buy prefix-ROOT
|
|
158
|
+
* identity, which is a different property from incremental reuse; conflating
|
|
159
|
+
* the two is what put an imposed boundary set on the inference path and left
|
|
160
|
+
* it folding differently from the deposits it was querying.
|
|
161
|
+
*
|
|
162
|
+
* `groupByLevel` above the segments is re-run whole. It operates on segment
|
|
163
|
+
* ROOTS (a few dozen items for a several-hundred-byte context), not on bytes,
|
|
164
|
+
* and only its right edge actually changes shape — measured at ~40 rebuilt
|
|
165
|
+
* nodes per turn, flat as the context grows sevenfold.
|
|
166
|
+
*
|
|
167
|
+
* PRECONDITION — `prev` MUST have been folded over a BYTE-IDENTICAL PREFIX of
|
|
168
|
+
* `bytes`. Reuse is keyed on a segment's [start,end) OFFSETS, which is what
|
|
169
|
+
* makes it O(1) per segment; offsets alone cannot witness that the underlying
|
|
170
|
+
* bytes agree. Hand it a fold of DIFFERENT bytes whose cuts happen to land
|
|
171
|
+
* in the same places and it will splice those foreign segments in — measured,
|
|
172
|
+
* a deliberately mismatched `prev` produced a wrong tree on 336 of 400 random
|
|
173
|
+
* streams. Verifying the bytes here would cost O(prefix) and defeat the
|
|
174
|
+
* whole point, so the obligation sits with the caller, and every caller
|
|
175
|
+
* discharges it structurally rather than by care: `perceiveDeposit` looks the
|
|
176
|
+
* entry up under `latin1Key(bytes.subarray(0, L))` — the prefix's own bytes
|
|
177
|
+
* ARE the cache key — and a conversation's fold state advances only by
|
|
178
|
+
* append. A new caller that cannot make the same structural argument must
|
|
179
|
+
* pass no `prev` at all; the cold path is always correct.
|
|
180
|
+
* ({@link stablePrefixFoldIncremental} carries the identical precondition for
|
|
181
|
+
* the identical reason.) */
|
|
182
|
+
export declare function contentFoldIncremental(space: Space, alphabet: Alphabet, bytes: Uint8Array, prev?: ContentFold): {
|
|
183
|
+
tree: Sema;
|
|
184
|
+
fold: ContentFold;
|
|
185
|
+
};
|
|
134
186
|
/** A stable-prefix fold's reusable state: the segment edge offsets and each
|
|
135
187
|
* segment's independently-folded root ({@link riverFoldRaw} output). A
|
|
136
188
|
* grown stream whose boundary set EXTENDS a previous fold's reuses every
|
package/dist/src/geometry.js
CHANGED
|
@@ -620,6 +620,84 @@ function contentFoldSpan(space, alphabet, bytes, from, to) {
|
|
|
620
620
|
return groupByLevel(space, segs, levels, 1);
|
|
621
621
|
return segs[0];
|
|
622
622
|
}
|
|
623
|
+
/** {@link contentFoldSpan} over a WHOLE stream, reusing the segments a previous
|
|
624
|
+
* fold of a byte-identical prefix already produced.
|
|
625
|
+
*
|
|
626
|
+
* WHY THIS IS SOUND, AND WHY IT NEEDS NO BOUNDARIES. A level-0 segment is a
|
|
627
|
+
* pure function of its own bytes ({@link flatFold} reads nothing else), so
|
|
628
|
+
* reusing one whose [start,end) is unchanged is bit-identical to refolding it
|
|
629
|
+
* — the cache can never change the tree, only skip work. And the cuts
|
|
630
|
+
* themselves are stable under APPEND: {@link contentLevels} decides each cut
|
|
631
|
+
* from a rolling hash over a local window, so bytes added at the right edge
|
|
632
|
+
* cannot move a cut to their left (measured over a growing 12-turn context:
|
|
633
|
+
* 100% of prior cuts survive every append, zero tail churn). Together those
|
|
634
|
+
* two facts are the whole optimisation — a grown stream refolds only the
|
|
635
|
+
* segments at its right edge.
|
|
636
|
+
*
|
|
637
|
+
* This is the reuse the conversation path wants, and it costs NOTHING in
|
|
638
|
+
* structure: the tree is exactly the tree {@link bytesToTree} builds for the
|
|
639
|
+
* same bytes with no boundary set at all. Turn boundaries buy prefix-ROOT
|
|
640
|
+
* identity, which is a different property from incremental reuse; conflating
|
|
641
|
+
* the two is what put an imposed boundary set on the inference path and left
|
|
642
|
+
* it folding differently from the deposits it was querying.
|
|
643
|
+
*
|
|
644
|
+
* `groupByLevel` above the segments is re-run whole. It operates on segment
|
|
645
|
+
* ROOTS (a few dozen items for a several-hundred-byte context), not on bytes,
|
|
646
|
+
* and only its right edge actually changes shape — measured at ~40 rebuilt
|
|
647
|
+
* nodes per turn, flat as the context grows sevenfold.
|
|
648
|
+
*
|
|
649
|
+
* PRECONDITION — `prev` MUST have been folded over a BYTE-IDENTICAL PREFIX of
|
|
650
|
+
* `bytes`. Reuse is keyed on a segment's [start,end) OFFSETS, which is what
|
|
651
|
+
* makes it O(1) per segment; offsets alone cannot witness that the underlying
|
|
652
|
+
* bytes agree. Hand it a fold of DIFFERENT bytes whose cuts happen to land
|
|
653
|
+
* in the same places and it will splice those foreign segments in — measured,
|
|
654
|
+
* a deliberately mismatched `prev` produced a wrong tree on 336 of 400 random
|
|
655
|
+
* streams. Verifying the bytes here would cost O(prefix) and defeat the
|
|
656
|
+
* whole point, so the obligation sits with the caller, and every caller
|
|
657
|
+
* discharges it structurally rather than by care: `perceiveDeposit` looks the
|
|
658
|
+
* entry up under `latin1Key(bytes.subarray(0, L))` — the prefix's own bytes
|
|
659
|
+
* ARE the cache key — and a conversation's fold state advances only by
|
|
660
|
+
* append. A new caller that cannot make the same structural argument must
|
|
661
|
+
* pass no `prev` at all; the cold path is always correct.
|
|
662
|
+
* ({@link stablePrefixFoldIncremental} carries the identical precondition for
|
|
663
|
+
* the identical reason.) */
|
|
664
|
+
export function contentFoldIncremental(space, alphabet, bytes, prev) {
|
|
665
|
+
if (bytes.length === 0) {
|
|
666
|
+
return {
|
|
667
|
+
tree: sema(alphabet.vecs[0], new Uint8Array(0), null),
|
|
668
|
+
fold: { edges: [0], segs: [] },
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const { cuts, levels } = contentLevels(space, bytes);
|
|
672
|
+
const edges = [0, ...cuts, bytes.length];
|
|
673
|
+
const segs = [];
|
|
674
|
+
for (let i = 0; i + 1 < edges.length; i++) {
|
|
675
|
+
const hit = prev !== undefined && prev.edges[i] === edges[i] &&
|
|
676
|
+
prev.edges[i + 1] === edges[i + 1]
|
|
677
|
+
? prev.segs[i]
|
|
678
|
+
: undefined;
|
|
679
|
+
segs.push(hit ?? flatFold(space, alphabet, bytes, edges[i], edges[i + 1]));
|
|
680
|
+
}
|
|
681
|
+
const folded = segs.length > 1
|
|
682
|
+
? groupByLevel(space, segs, levels, 1)
|
|
683
|
+
: segs[0];
|
|
684
|
+
// THE ROOT IS NORMALIZED IN PLACE, A CACHED SEGMENT NEVER IS. With one
|
|
685
|
+
// segment — or with a grouping that passes a lone item through — `folded`
|
|
686
|
+
// IS a cached seg, and a later turn will reuse it as an interior node whose
|
|
687
|
+
// magnitude must stay byte-proportional. Copy before normalizing, exactly
|
|
688
|
+
// as the stable-prefix twin does. A single LEAF is copied too: its vector
|
|
689
|
+
// is the shared alphabet entry and must never be written.
|
|
690
|
+
const aliased = segs.some((s) => s.tree === folded.tree);
|
|
691
|
+
let tree = folded.tree;
|
|
692
|
+
if (aliased) {
|
|
693
|
+
tree = tree.kids === null
|
|
694
|
+
? sema(tree.v, tree.leaf, null)
|
|
695
|
+
: sema(Float32Array.from(tree.v), null, tree.kids);
|
|
696
|
+
}
|
|
697
|
+
if (tree.kids !== null)
|
|
698
|
+
normalize(tree.v);
|
|
699
|
+
return { tree, fold: { edges, segs } };
|
|
700
|
+
}
|
|
623
701
|
/** Group a row of items by the level of the cut BETWEEN them: items separated
|
|
624
702
|
* by a cut of level < L belong to the same parent, and a cut of level ≥ L ends
|
|
625
703
|
* it. Recurses upward until one root remains, so the shape at every level is
|
|
@@ -820,9 +898,17 @@ function stablePrefixFold(space, alphabet, bytes, boundaries) {
|
|
|
820
898
|
* folded over a byte-identical prefix (the caller keys the cache by
|
|
821
899
|
* content). Requires a non-empty effective boundary set. */
|
|
822
900
|
export function stablePrefixFoldIncremental(space, alphabet, bytes, boundaries, prev) {
|
|
901
|
+
// SORTED, like {@link bytesToTree} does before calling the non-incremental
|
|
902
|
+
// twin. The filter below is sequential (`b > prevB`), so an out-of-order
|
|
903
|
+
// entry is silently DROPPED rather than rejected — and these two functions
|
|
904
|
+
// are documented as producing the same cuts, so a caller that hands the
|
|
905
|
+
// same set to each and gets different trees has hit a trap, not a contract.
|
|
906
|
+
// Sorting here makes the twins genuinely interchangeable; the set is one
|
|
907
|
+
// entry per conversation turn, so the cost is nil.
|
|
908
|
+
const sorted = [...boundaries].sort((a, b) => a - b);
|
|
823
909
|
const cuts = [];
|
|
824
910
|
let prevB = 0;
|
|
825
|
-
for (const b of
|
|
911
|
+
for (const b of sorted) {
|
|
826
912
|
if (b > prevB && b < bytes.length) {
|
|
827
913
|
cuts.push(b);
|
|
828
914
|
prevB = b;
|
package/dist/src/mind/bridge.js
CHANGED
|
@@ -94,7 +94,7 @@ import { conceptThreshold, dominates, significanceBar } from "../geometry.js";
|
|
|
94
94
|
import { bytesEqual, indexOf } from "../bytes.js";
|
|
95
95
|
import { foldTree, perceive, read } from "./primitives.js";
|
|
96
96
|
import { chainReach, leafIdRun } from "./canonical.js";
|
|
97
|
-
import { corpusN, edgeAncestors, hubBound, sharedReachMemo, } from "./traverse.js";
|
|
97
|
+
import { allWindowsAreScaffolding, corpusN, edgeAncestors, hubBound, sharedReachMemo, } from "./traverse.js";
|
|
98
98
|
import { rItem, rNode } from "./trace.js";
|
|
99
99
|
import { junctionContainersFrom } from "./junction.js";
|
|
100
100
|
import { spanHalo } from "./match.js";
|
|
@@ -329,6 +329,32 @@ async function bridgeImpl(ctx, query, proposed) {
|
|
|
329
329
|
ctx.trace?.step("substitutionBridge", [rItem(query, "query")], [], "no stored query window can anchor a corroborated substitution", undefined, diagnostics);
|
|
330
330
|
return null;
|
|
331
331
|
}
|
|
332
|
+
// NO DISCRIMINATING LITERAL EVIDENCE — abstain (§2.13). A bridge grounds
|
|
333
|
+
// through the literal spans it did NOT substitute; those anchors are the
|
|
334
|
+
// whole of its evidence. When every one of them is SATURATED — containment
|
|
335
|
+
// clamped at the √N hub bound, i.e. the window is corpus-global scaffolding
|
|
336
|
+
// — the query's unsubstituted part discriminates nothing, and the single
|
|
337
|
+
// substituted span is carrying the entire semantic load. That is not a
|
|
338
|
+
// corroborated bridge; it is a template match, and it FABRICATES.
|
|
339
|
+
//
|
|
340
|
+
// Measured on the trained store (hubBound 571). "What is the capital of"
|
|
341
|
+
// has 19 anchors, ALL saturated ("What":572, "hat ":572, "at i":572 …), and
|
|
342
|
+
// bridged to an unrelated trained context about an integral, voiced
|
|
343
|
+
// confidently. Every query the bridge answers CORRECTLY has at least one
|
|
344
|
+
// unsaturated anchor, by a wide margin and with no near miss:
|
|
345
|
+
// "Who is the author of Hamlet?" → "let?":12, "How do you say 'thank you'
|
|
346
|
+
// in French?" → "y 't":3, "…largest planet…" → "tem?":31, "What is the
|
|
347
|
+
// capital of France?" → "f Fr":114. The honest-silence probes sit on the
|
|
348
|
+
// same side as the correct ones ("Zamu":3), so this gate is not what makes
|
|
349
|
+
// them silent and cannot be credited for them.
|
|
350
|
+
//
|
|
351
|
+
// This introduces NO new threshold: `bound` is the same √N reading of "hub"
|
|
352
|
+
// the anchor scan already clamps its own containment read to (§2.2, §2.7).
|
|
353
|
+
if (allWindowsAreScaffolding(ctx, query)) {
|
|
354
|
+
ctx.trace?.step("substitutionBridge", [rItem(query, "query")], [], "every query window that could anchor is corpus-global scaffolding — " +
|
|
355
|
+
"no literal evidence to corroborate a substitution", undefined, diagnostics);
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
332
358
|
// CORROBORATION (see the module-level doc) over the precomputed window
|
|
333
359
|
// facts: the query span [qs,qe) attests when every full W-window inside
|
|
334
360
|
// it is a stored flat form and at least one is reused across ≥ 2
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MindContext } from "./types.js";
|
|
2
|
+
/** A grounded frame-filler substitution: the stored form the constructed key
|
|
3
|
+
* resolved to, and the spans that explain how it was reached. */
|
|
4
|
+
export interface FrameFillerHit {
|
|
5
|
+
/** The trained form the key resolved to — grounded through its own edge. */
|
|
6
|
+
id: number;
|
|
7
|
+
/** `[start, end)` of the query span the filler stood in for. */
|
|
8
|
+
described: [number, number];
|
|
9
|
+
/** The filler's bytes, for the rationale trace. */
|
|
10
|
+
filler: Uint8Array;
|
|
11
|
+
}
|
|
12
|
+
/** Find the query's own most discriminative unit and the trained contexts that
|
|
13
|
+
* hold it, then try the store for the query with a candidate filler in the
|
|
14
|
+
* described span's place. Returns the sole surviving stored form, or null. */
|
|
15
|
+
export declare function frameFillerSubstitution(ctx: MindContext, query: Uint8Array, ranked: ReadonlyArray<number>): FrameFillerHit | null;
|