@hviana/sema 0.5.2 → 0.5.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/AGENTS.md +114 -52
- package/HOW_IT_WORKS.md +275 -184
- package/dist/src/mind/bridge.d.ts +5 -7
- package/dist/src/mind/bridge.js +6 -97
- package/dist/src/mind/match.d.ts +159 -0
- package/dist/src/mind/match.js +300 -7
- package/dist/src/mind/mechanisms/prefix-completion.d.ts +22 -0
- package/dist/src/mind/{prefix-completion.js → mechanisms/prefix-completion.js} +64 -91
- package/dist/src/mind/mechanisms/recall.js +10 -108
- package/dist/src/mind/mechanisms/reference.d.ts +6 -0
- package/dist/src/mind/mechanisms/reference.js +296 -0
- package/dist/src/mind/mind.d.ts +1 -1
- package/dist/src/mind/pipeline-mechanism.d.ts +56 -1
- package/dist/src/mind/pipeline-mechanism.js +104 -3
- package/dist/src/mind/pipeline.d.ts +1 -1
- package/dist/src/mind/pipeline.js +13 -1
- package/dist/src/mind/traverse.d.ts +38 -0
- package/dist/src/mind/traverse.js +91 -1
- package/dist/src/store.d.ts +4 -4
- package/jsr.json +6 -0
- package/package.json +1 -1
- package/src/mind/bridge.ts +10 -104
- package/src/mind/match.ts +416 -7
- package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
- package/src/mind/mechanisms/recall.ts +9 -126
- package/src/mind/mechanisms/reference.ts +343 -0
- package/src/mind/mind.ts +12 -8
- package/src/mind/pipeline-mechanism.ts +120 -3
- package/src/mind/pipeline.ts +16 -2
- package/src/mind/traverse.ts +92 -1
- package/src/store.ts +13 -4
- package/test/33-multi-candidate.test.mjs +21 -11
- package/test/70-prefix-completion.test.mjs +1 -1
- package/test/72-prefix-candidate-supply.test.mjs +7 -9
- package/test/74-prefix-trap-not-sprung-early.test.mjs +1 -1
- package/test/76-reference-binding.test.mjs +471 -0
- package/dist/src/mind/frame-filler.d.ts +0 -15
- package/dist/src/mind/frame-filler.js +0 -535
- package/dist/src/mind/prefix-completion.d.ts +0 -59
- package/src/mind/frame-filler.ts +0 -604
- package/test/69-frame-filler.test.mjs +0 -115
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { AncestorReach, MindContext, Recognition } from "./types.js";
|
|
2
2
|
import type { AttentionRead } from "./types.js";
|
|
3
3
|
import type { ComputedSpan } from "../extension.js";
|
|
4
|
+
import type { Hit } from "../store.js";
|
|
4
5
|
import type { Vec } from "../vec.js";
|
|
5
|
-
import { type GradedRun } from "./match.js";
|
|
6
|
+
import { type FrameInstance, type GradedRun } from "./match.js";
|
|
6
7
|
export declare class Precomputed {
|
|
7
8
|
readonly ctx: MindContext;
|
|
8
9
|
readonly query: Uint8Array;
|
|
@@ -52,6 +53,60 @@ export declare class Precomputed {
|
|
|
52
53
|
* which every later consumer then got free. Attribution must follow the
|
|
53
54
|
* work, not the caller. */
|
|
54
55
|
private shared;
|
|
56
|
+
private _resonance?;
|
|
57
|
+
/** The response's ONE top-k content-index read: the k learnt forms nearest
|
|
58
|
+
* the whole-query gist, ranked. Recall's every gist tier is built on it,
|
|
59
|
+
* and {@link frames} assembles the frame inventory from it.
|
|
60
|
+
*
|
|
61
|
+
* An ANN query is the single most expensive read in the engine, and two
|
|
62
|
+
* mechanisms asking the same question of the same gist is the one
|
|
63
|
+
* duplication a profile shows as doubled `annVectorReads` with nothing to
|
|
64
|
+
* account for it. Cached BY PROMISE, so a second caller awaits the first. */
|
|
65
|
+
resonance(): Promise<ReadonlyArray<Hit>>;
|
|
66
|
+
private _wide?;
|
|
67
|
+
/** The response's WIDE candidate list — the top-k when the query's gist has
|
|
68
|
+
* no concept-level match anywhere, and an exhaustive √N read when it does.
|
|
69
|
+
*
|
|
70
|
+
* Every mechanism that has to look PAST the top-k reads this one list: the
|
|
71
|
+
* substitution bridge, prefix completion and the frame filler all did, and
|
|
72
|
+
* it was memoised inside recall for exactly that reason (measured: 490 ms
|
|
73
|
+
* median re-issued against 13 ms non-exhaustive, 36x). A memo inside one
|
|
74
|
+
* mechanism only serves that mechanism's own tiers, so it lives here now —
|
|
75
|
+
* the same move `resonance` made for the top-k.
|
|
76
|
+
*
|
|
77
|
+
* THE CONDITION IS THE TOP HIT'S SCORE, NOT THE CORPUS SIZE. When nothing
|
|
78
|
+
* ranks at concept level, an exhaustive ANN only scores more vectors below
|
|
79
|
+
* the bar (profiled at 38K–40K annVectorReads per refusing query on a 325K-
|
|
80
|
+
* context store); the structural channels — junction walks, anchor climbs,
|
|
81
|
+
* the write side's window index — are the correct proposal source there,
|
|
82
|
+
* because the ANN cannot propose what the gist cannot rank. This was once
|
|
83
|
+
* spelled `corpusN(ctx) <= (k · W)³`, which asks a different question and
|
|
84
|
+
* answers it wrongly at exactly the scale it was written from: at N =
|
|
85
|
+
* 325,608 with k = 24 and W = 4 the cube is 884,736, so that store took the
|
|
86
|
+
* exhaustive branch — the very branch measured above. Measured cost of the
|
|
87
|
+
* mismatch: substitutionBridge 8,544 ms of a 19,548 ms think (44%), against
|
|
88
|
+
* 1,248 ms and 14,218 ms without it, every answer byte-identical. */
|
|
89
|
+
wideResonance(): Promise<ReadonlyArray<number>>;
|
|
90
|
+
private _frames?;
|
|
91
|
+
/** THE FRAME INVENTORY — every ranked candidate that reads as an instance of
|
|
92
|
+
* the same frame as the query, each with the query spans it leaves VARIABLE
|
|
93
|
+
* ({@link FrameInstance}). The one place the engine represents "a position
|
|
94
|
+
* whose occupant comes from the context rather than the corpus".
|
|
95
|
+
*
|
|
96
|
+
* AN INVENTORY, NOT AN ELECTION. It reports every pairing and elects no
|
|
97
|
+
* frame, deliberately: a slot is a property of a PAIRING, not of the query,
|
|
98
|
+
* and different candidates put slots in different places. Committing to one
|
|
99
|
+
* reading here would push whichever consumer asked first onto everyone else
|
|
100
|
+
* — the market's decoupling (§2.6) broken from inside the shared container,
|
|
101
|
+
* and the population error §2.7 names. Each consumer groups and commits
|
|
102
|
+
* for its own question; reference elects the modal slot signature, and a
|
|
103
|
+
* consumer wanting a different reading is not fighting this one.
|
|
104
|
+
*
|
|
105
|
+
* NO LICENCE EITHER. Knowing a span is variable is safe for every consumer
|
|
106
|
+
* — it can only improve an alignment. Knowing one may be VOICED through is
|
|
107
|
+
* a different and much stronger claim, gated separately by
|
|
108
|
+
* {@link carriesFillers}, which needs projections this must not perform. */
|
|
109
|
+
frames(): Promise<ReadonlyArray<FrameInstance>>;
|
|
55
110
|
private _attention?;
|
|
56
111
|
/** The full consensus climb (roots + ranked anchors) — the query-level
|
|
57
112
|
* evidence CAST, confluence, extraction, recall's scaffolding tier, and
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
// 4. TRAVELING EVIDENCE — run() returns MechanismResult with accounted, moves,
|
|
14
14
|
// and unexplained. The pipeline computes the weight.
|
|
15
15
|
import { indexOf } from "../bytes.js";
|
|
16
|
-
import { dominates } from "../geometry.js";
|
|
16
|
+
import { conceptThreshold, dominates } from "../geometry.js";
|
|
17
17
|
import { windowIds } from "./canonical.js";
|
|
18
18
|
import { read, resolve } from "./primitives.js";
|
|
19
|
-
import { alignGraded, skillExemplar } from "./match.js";
|
|
19
|
+
import { alignGraded, frameSlots, skillExemplar, } from "./match.js";
|
|
20
20
|
import { climbAttentionAll } from "./attention.js";
|
|
21
|
-
import { sharedReachMemo } from "./traverse.js";
|
|
21
|
+
import { hubBound, sharedReachMemo } from "./traverse.js";
|
|
22
22
|
// ── Precomputed ──────────────────────────────────────────────────────────────
|
|
23
23
|
//
|
|
24
24
|
// Precomputed is a LAZY container for structural analyses of the query — the
|
|
@@ -116,6 +116,107 @@ export class Precomputed {
|
|
|
116
116
|
const meter = this.ctx.meter;
|
|
117
117
|
return meter ? meter.time(phase, fn) : fn();
|
|
118
118
|
}
|
|
119
|
+
_resonance;
|
|
120
|
+
/** The response's ONE top-k content-index read: the k learnt forms nearest
|
|
121
|
+
* the whole-query gist, ranked. Recall's every gist tier is built on it,
|
|
122
|
+
* and {@link frames} assembles the frame inventory from it.
|
|
123
|
+
*
|
|
124
|
+
* An ANN query is the single most expensive read in the engine, and two
|
|
125
|
+
* mechanisms asking the same question of the same gist is the one
|
|
126
|
+
* duplication a profile shows as doubled `annVectorReads` with nothing to
|
|
127
|
+
* account for it. Cached BY PROMISE, so a second caller awaits the first. */
|
|
128
|
+
resonance() {
|
|
129
|
+
return this._resonance ??= this.shared("resonance", () => this.ctx.store.resonate(this.guide, this.k));
|
|
130
|
+
}
|
|
131
|
+
_wide;
|
|
132
|
+
/** The response's WIDE candidate list — the top-k when the query's gist has
|
|
133
|
+
* no concept-level match anywhere, and an exhaustive √N read when it does.
|
|
134
|
+
*
|
|
135
|
+
* Every mechanism that has to look PAST the top-k reads this one list: the
|
|
136
|
+
* substitution bridge, prefix completion and the frame filler all did, and
|
|
137
|
+
* it was memoised inside recall for exactly that reason (measured: 490 ms
|
|
138
|
+
* median re-issued against 13 ms non-exhaustive, 36x). A memo inside one
|
|
139
|
+
* mechanism only serves that mechanism's own tiers, so it lives here now —
|
|
140
|
+
* the same move `resonance` made for the top-k.
|
|
141
|
+
*
|
|
142
|
+
* THE CONDITION IS THE TOP HIT'S SCORE, NOT THE CORPUS SIZE. When nothing
|
|
143
|
+
* ranks at concept level, an exhaustive ANN only scores more vectors below
|
|
144
|
+
* the bar (profiled at 38K–40K annVectorReads per refusing query on a 325K-
|
|
145
|
+
* context store); the structural channels — junction walks, anchor climbs,
|
|
146
|
+
* the write side's window index — are the correct proposal source there,
|
|
147
|
+
* because the ANN cannot propose what the gist cannot rank. This was once
|
|
148
|
+
* spelled `corpusN(ctx) <= (k · W)³`, which asks a different question and
|
|
149
|
+
* answers it wrongly at exactly the scale it was written from: at N =
|
|
150
|
+
* 325,608 with k = 24 and W = 4 the cube is 884,736, so that store took the
|
|
151
|
+
* exhaustive branch — the very branch measured above. Measured cost of the
|
|
152
|
+
* mismatch: substitutionBridge 8,544 ms of a 19,548 ms think (44%), against
|
|
153
|
+
* 1,248 ms and 14,218 ms without it, every answer byte-identical. */
|
|
154
|
+
wideResonance() {
|
|
155
|
+
return this._wide ??= this.shared("wideResonance", async () => {
|
|
156
|
+
const hits = await this.resonance();
|
|
157
|
+
if (hits.length > 0 &&
|
|
158
|
+
hits[0].score >= conceptThreshold(this.ctx.store.D)) {
|
|
159
|
+
const exhaustive = await this.ctx.store.resonate(this.guide, hubBound(this.ctx), true);
|
|
160
|
+
return exhaustive.map((h) => h.id);
|
|
161
|
+
}
|
|
162
|
+
return hits.map((h) => h.id);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
_frames;
|
|
166
|
+
/** THE FRAME INVENTORY — every ranked candidate that reads as an instance of
|
|
167
|
+
* the same frame as the query, each with the query spans it leaves VARIABLE
|
|
168
|
+
* ({@link FrameInstance}). The one place the engine represents "a position
|
|
169
|
+
* whose occupant comes from the context rather than the corpus".
|
|
170
|
+
*
|
|
171
|
+
* AN INVENTORY, NOT AN ELECTION. It reports every pairing and elects no
|
|
172
|
+
* frame, deliberately: a slot is a property of a PAIRING, not of the query,
|
|
173
|
+
* and different candidates put slots in different places. Committing to one
|
|
174
|
+
* reading here would push whichever consumer asked first onto everyone else
|
|
175
|
+
* — the market's decoupling (§2.6) broken from inside the shared container,
|
|
176
|
+
* and the population error §2.7 names. Each consumer groups and commits
|
|
177
|
+
* for its own question; reference elects the modal slot signature, and a
|
|
178
|
+
* consumer wanting a different reading is not fighting this one.
|
|
179
|
+
*
|
|
180
|
+
* NO LICENCE EITHER. Knowing a span is variable is safe for every consumer
|
|
181
|
+
* — it can only improve an alignment. Knowing one may be VOICED through is
|
|
182
|
+
* a different and much stronger claim, gated separately by
|
|
183
|
+
* {@link carriesFillers}, which needs projections this must not perform. */
|
|
184
|
+
frames() {
|
|
185
|
+
return this._frames ??= this.shared("frames", async () => {
|
|
186
|
+
const ctx = this.ctx;
|
|
187
|
+
const W = ctx.space.maxGroup;
|
|
188
|
+
// PHRASE SCALE, the same bound the bridge and the frame filler put on a
|
|
189
|
+
// candidate's bytes: a form an order of magnitude longer than the query
|
|
190
|
+
// is not a candidate for BEING it with a span replaced.
|
|
191
|
+
const capBytes = this.query.length * W;
|
|
192
|
+
const out = [];
|
|
193
|
+
for (const h of await this.resonance()) {
|
|
194
|
+
// REJECT BY LENGTH BEFORE RECONSTRUCTING (§2.8): `contentLen` is an
|
|
195
|
+
// indexed read, `bytesPrefix` rebuilds a subtree. ONLY the phrase-scale
|
|
196
|
+
// cap is applied — it is a bounded-read discipline, not a judgement.
|
|
197
|
+
//
|
|
198
|
+
// A LOWER bound was here too (`dominates(len, query.length)`, on the
|
|
199
|
+
// reasoning that a candidate shorter than half the query cannot supply
|
|
200
|
+
// a frame that dominates it). That is reference's gate wearing a cost
|
|
201
|
+
// argument's clothes, and it hid the very pairings another consumer
|
|
202
|
+
// needs: `What is the capital of France?` (30 B) against `What is the
|
|
203
|
+
// capital of the country where the Eiffel Tower is?` (61 B) was
|
|
204
|
+
// rejected before it was ever read — a definite description standing
|
|
205
|
+
// where a noun stands, which is exactly the shape the frame filler
|
|
206
|
+
// exists for.
|
|
207
|
+
const len = ctx.store.contentLen(h.id, capBytes + 1);
|
|
208
|
+
if (len === 0 || len > capBytes)
|
|
209
|
+
continue;
|
|
210
|
+
const cand = ctx.store.bytesPrefix(h.id, capBytes + 1);
|
|
211
|
+
if (cand.length === 0 || cand.length > capBytes)
|
|
212
|
+
continue;
|
|
213
|
+
const inst = frameSlots(ctx, this.query, cand, h.id);
|
|
214
|
+
if (inst !== null)
|
|
215
|
+
out.push(inst);
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
119
220
|
_attention;
|
|
120
221
|
/** The full consensus climb (roots + ranked anchors) — the query-level
|
|
121
222
|
* evidence CAST, confluence, extraction, recall's scaffolding tier, and
|
|
@@ -3,7 +3,7 @@ import { type PipelineMechanism } from "./pipeline-mechanism.js";
|
|
|
3
3
|
export { resolveConcepts, resolveConnectors } from "./mechanisms/cover.js";
|
|
4
4
|
export { aluToMechanism } from "./mechanisms/alu.js";
|
|
5
5
|
export declare const defaultMechanisms: PipelineMechanism[];
|
|
6
|
-
export type Provenance = "cast" | "join" | "cover" | "extract" | "recall" | "recall-echo";
|
|
6
|
+
export type Provenance = "cast" | "join" | "cover" | "extract" | "reference" | "recall" | "recall-echo" | "prefix";
|
|
7
7
|
export interface Thought {
|
|
8
8
|
bytes: Uint8Array;
|
|
9
9
|
provenance: Provenance;
|
|
@@ -20,6 +20,8 @@ import { coverMechanism } from "./mechanisms/cover.js";
|
|
|
20
20
|
import { castMechanism } from "./mechanisms/cast.js";
|
|
21
21
|
import { confluenceMechanism } from "./mechanisms/confluence.js";
|
|
22
22
|
import { extractionMechanism } from "./mechanisms/extraction.js";
|
|
23
|
+
import { referenceMechanism } from "./mechanisms/reference.js";
|
|
24
|
+
import { prefixMechanism } from "./mechanisms/prefix-completion.js";
|
|
23
25
|
import { recallMechanism } from "./mechanisms/recall.js";
|
|
24
26
|
// Re-exports: cover's pre-resolution helpers and the ALU adapter kept
|
|
25
27
|
// importable from the pipeline module (their historical home).
|
|
@@ -49,13 +51,23 @@ async function collectComputed(ctx, mechanisms, query) {
|
|
|
49
51
|
// floor pruning every mechanism is already subject to — not by asking
|
|
50
52
|
// "is this an extension?". Grade TIES keep the earlier candidate, so this
|
|
51
53
|
// order is also the tie-break priority: cover, cast, confluence, extraction,
|
|
52
|
-
// recall.
|
|
54
|
+
// reference, recall.
|
|
55
|
+
//
|
|
56
|
+
// REFERENCE sits after extraction and before recall because that is what its
|
|
57
|
+
// claim is worth: extraction READS a span out of the query (no synthesis),
|
|
58
|
+
// reference voices one through a learnt slot, and recall's tiers degrade
|
|
59
|
+
// toward echo and silence. It does not PRUNE recall — its floor is two
|
|
60
|
+
// projections, so recall's one-STEP floor still clears `worthRunning` — and it
|
|
61
|
+
// is not meant to: both run, share one resonance read
|
|
62
|
+
// (Precomputed.resonance), and the ladder decides.
|
|
53
63
|
export const defaultMechanisms = [
|
|
54
64
|
coverMechanism,
|
|
55
65
|
castMechanism,
|
|
56
66
|
confluenceMechanism,
|
|
57
67
|
extractionMechanism,
|
|
68
|
+
referenceMechanism,
|
|
58
69
|
recallMechanism,
|
|
70
|
+
prefixMechanism,
|
|
59
71
|
];
|
|
60
72
|
/** Think: a single lightest-derivation exploration of the Sema graph.
|
|
61
73
|
*
|
|
@@ -156,3 +156,41 @@ export declare function chooseAmong(ctx: MindContext, candidates: readonly numbe
|
|
|
156
156
|
* scaffolding-only — it has no evidence either way, and its callers already
|
|
157
157
|
* refuse it on their own terms. */
|
|
158
158
|
export declare function allWindowsAreScaffolding(ctx: MindContext, query: Uint8Array): boolean;
|
|
159
|
+
/** Trained forms the query may OPEN, proposed from the write side's own
|
|
160
|
+
* leaf-id window index — the supply of last resort for prefix completion.
|
|
161
|
+
*
|
|
162
|
+
* WHY A SECOND SUPPLY EXISTS. The ranked list prefix completion normally reads
|
|
163
|
+
* is a resonance list, and resonance cannot rank a proper prefix: measured on
|
|
164
|
+
* the trained store, cos(prefix, form) falls from 0.9629 at a one-byte
|
|
165
|
+
* truncation to 0.6206 at three bytes, against a reachThreshold of 0.8750.
|
|
166
|
+
* Three bytes of truncation put the answer out of reach on GEOMETRY, not on a
|
|
167
|
+
* bug, so no k and no re-ranking recovers it.
|
|
168
|
+
*
|
|
169
|
+
* WHY THIS ROUTE WORKS WHERE THE FOLD DOES NOT. A query's own fold is
|
|
170
|
+
* useless here: content addressing is not phrase-position-invariant, so a
|
|
171
|
+
* standalone prefix folds to a DIFFERENT node than the same bytes sitting
|
|
172
|
+
* inside a longer deposit, and neither the prefix's own node nor its
|
|
173
|
+
* ancestors lead to the deposit (measured: the 22-byte prefix of the
|
|
174
|
+
* photosynthesis form resolves, is shared by 6 contexts, and does not have
|
|
175
|
+
* the form among its ancestors). Leaf ids ARE position-invariant — they are
|
|
176
|
+
* content-addressed on single bytes — and `indexSubSpans` already interns a
|
|
177
|
+
* flat branch over every canonical WINDOW of a deposit's leaf-id stream, with
|
|
178
|
+
* containment edges to the chunks that window spans. A query that is a
|
|
179
|
+
* prefix therefore shares those window nodes exactly, and reaches the deposit
|
|
180
|
+
* by climbing containment then parents. Nothing is added to the write side;
|
|
181
|
+
* this reads an index training already built.
|
|
182
|
+
*
|
|
183
|
+
* BOUNDED (§2.8), AND WITH NO NEW THRESHOLD. The window whose containment is
|
|
184
|
+
* SMALLEST carries the most evidence, and one saturated at `hubBound` carries
|
|
185
|
+
* none — that is the same √N reading of "hub" the rest of the mind uses, not
|
|
186
|
+
* a tuned knob. The upward walk spends a budget of `hubBound` nodes and
|
|
187
|
+
* fans out by W, so a hub query enumerates nothing and the caller stays
|
|
188
|
+
* silent rather than guessing (§2.13). Measured on the trained store: the
|
|
189
|
+
* photosynthesis form at a one-byte truncation picks a window with 52
|
|
190
|
+
* containers, visits 446 nodes, and yields exactly ONE candidate that
|
|
191
|
+
* survives the caller's byte compare — the form itself.
|
|
192
|
+
*
|
|
193
|
+
* These are PROPOSALS only. Every candidate still faces the byte-exact
|
|
194
|
+
* prefix compare and all three guards below, so a wrong proposal costs one
|
|
195
|
+
* bounded read and can never be voiced (§2.3). */
|
|
196
|
+
export declare function formsOpenedBy(ctx: MindContext, query: Uint8Array): number[];
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// project) live in match.ts — the elementary match-and-project operation.
|
|
8
8
|
import { cosine } from "../vec.js";
|
|
9
9
|
import { gistOf, read } from "./primitives.js";
|
|
10
|
-
import { leafIdRun } from "./canonical.js";
|
|
10
|
+
import { canonicalWindows, leafIdPrefix, leafIdRun } from "./canonical.js";
|
|
11
11
|
//
|
|
12
12
|
// Budgeted on the same terms as the reach memo below (AGENTS §2.12): these
|
|
13
13
|
// three maps are cleared on every write, but a long read-only session over a
|
|
@@ -714,3 +714,93 @@ export function allWindowsAreScaffolding(ctx, query) {
|
|
|
714
714
|
}
|
|
715
715
|
return sawOne;
|
|
716
716
|
}
|
|
717
|
+
// ── THE PREFIX SUPPLY ───────────────────────────────────────────────────────
|
|
718
|
+
//
|
|
719
|
+
// A RETRIEVAL capability, not a grounding one: "which trained forms does this
|
|
720
|
+
// byte run OPEN?" It lived inside a recall tier, which is the wrong altitude
|
|
721
|
+
// — it reads the write side's own leaf-id window index and answers a question
|
|
722
|
+
// about the STORE, so any mechanism may ask it.
|
|
723
|
+
/** Trained forms the query may OPEN, proposed from the write side's own
|
|
724
|
+
* leaf-id window index — the supply of last resort for prefix completion.
|
|
725
|
+
*
|
|
726
|
+
* WHY A SECOND SUPPLY EXISTS. The ranked list prefix completion normally reads
|
|
727
|
+
* is a resonance list, and resonance cannot rank a proper prefix: measured on
|
|
728
|
+
* the trained store, cos(prefix, form) falls from 0.9629 at a one-byte
|
|
729
|
+
* truncation to 0.6206 at three bytes, against a reachThreshold of 0.8750.
|
|
730
|
+
* Three bytes of truncation put the answer out of reach on GEOMETRY, not on a
|
|
731
|
+
* bug, so no k and no re-ranking recovers it.
|
|
732
|
+
*
|
|
733
|
+
* WHY THIS ROUTE WORKS WHERE THE FOLD DOES NOT. A query's own fold is
|
|
734
|
+
* useless here: content addressing is not phrase-position-invariant, so a
|
|
735
|
+
* standalone prefix folds to a DIFFERENT node than the same bytes sitting
|
|
736
|
+
* inside a longer deposit, and neither the prefix's own node nor its
|
|
737
|
+
* ancestors lead to the deposit (measured: the 22-byte prefix of the
|
|
738
|
+
* photosynthesis form resolves, is shared by 6 contexts, and does not have
|
|
739
|
+
* the form among its ancestors). Leaf ids ARE position-invariant — they are
|
|
740
|
+
* content-addressed on single bytes — and `indexSubSpans` already interns a
|
|
741
|
+
* flat branch over every canonical WINDOW of a deposit's leaf-id stream, with
|
|
742
|
+
* containment edges to the chunks that window spans. A query that is a
|
|
743
|
+
* prefix therefore shares those window nodes exactly, and reaches the deposit
|
|
744
|
+
* by climbing containment then parents. Nothing is added to the write side;
|
|
745
|
+
* this reads an index training already built.
|
|
746
|
+
*
|
|
747
|
+
* BOUNDED (§2.8), AND WITH NO NEW THRESHOLD. The window whose containment is
|
|
748
|
+
* SMALLEST carries the most evidence, and one saturated at `hubBound` carries
|
|
749
|
+
* none — that is the same √N reading of "hub" the rest of the mind uses, not
|
|
750
|
+
* a tuned knob. The upward walk spends a budget of `hubBound` nodes and
|
|
751
|
+
* fans out by W, so a hub query enumerates nothing and the caller stays
|
|
752
|
+
* silent rather than guessing (§2.13). Measured on the trained store: the
|
|
753
|
+
* photosynthesis form at a one-byte truncation picks a window with 52
|
|
754
|
+
* containers, visits 446 nodes, and yields exactly ONE candidate that
|
|
755
|
+
* survives the caller's byte compare — the form itself.
|
|
756
|
+
*
|
|
757
|
+
* These are PROPOSALS only. Every candidate still faces the byte-exact
|
|
758
|
+
* prefix compare and all three guards below, so a wrong proposal costs one
|
|
759
|
+
* bounded read and can never be voiced (§2.3). */
|
|
760
|
+
export function formsOpenedBy(ctx, query) {
|
|
761
|
+
const store = ctx.store;
|
|
762
|
+
const W = ctx.space.maxGroup;
|
|
763
|
+
const run = leafIdPrefix(ctx, query);
|
|
764
|
+
// The widest canonical window is the most discriminative one the write side
|
|
765
|
+
// ever interned; a query too short to spell one carries no window evidence.
|
|
766
|
+
const len = canonicalWindows(W)[1];
|
|
767
|
+
if (run.length < len)
|
|
768
|
+
return [];
|
|
769
|
+
const bound = hubBound(ctx);
|
|
770
|
+
let best = null;
|
|
771
|
+
let bestN = 0;
|
|
772
|
+
for (let off = 0; off + len <= run.length; off++) {
|
|
773
|
+
const wid = store.findBranch(run.slice(off, off + len));
|
|
774
|
+
if (wid === null)
|
|
775
|
+
continue;
|
|
776
|
+
const n = store.containersSlice(wid, 0, bound).length;
|
|
777
|
+
// Empty says the window spans no chunk; saturated says it is a hub, whose
|
|
778
|
+
// containment discriminates nothing. Neither is evidence.
|
|
779
|
+
if (n === 0 || n >= bound)
|
|
780
|
+
continue;
|
|
781
|
+
if (best === null || n < bestN) {
|
|
782
|
+
best = wid;
|
|
783
|
+
bestN = n;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (best === null)
|
|
787
|
+
return [];
|
|
788
|
+
let frontier = store.containersSlice(best, 0, bound);
|
|
789
|
+
const seen = new Set(frontier);
|
|
790
|
+
let budget = bound;
|
|
791
|
+
while (frontier.length > 0 && budget > 0) {
|
|
792
|
+
const next = [];
|
|
793
|
+
for (const f of frontier) {
|
|
794
|
+
if (budget-- <= 0)
|
|
795
|
+
break;
|
|
796
|
+
for (const p of store.parentsFirst(f, W)) {
|
|
797
|
+
if (seen.has(p))
|
|
798
|
+
continue;
|
|
799
|
+
seen.add(p);
|
|
800
|
+
next.push(p);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
frontier = next;
|
|
804
|
+
}
|
|
805
|
+
return [...seen];
|
|
806
|
+
}
|
package/dist/src/store.d.ts
CHANGED
|
@@ -533,15 +533,15 @@ export declare abstract class AbstractStore implements Store {
|
|
|
533
533
|
vector: Float32Array;
|
|
534
534
|
}>;
|
|
535
535
|
/** Halo index write buffer — keyed by id so repeats within a batch coalesce. */
|
|
536
|
-
protected _haloBuffer: Map<
|
|
536
|
+
protected _haloBuffer: Map<NodeId, Float32Array>;
|
|
537
537
|
/** Containment write buffer: child → new parents, merged on flush cadence. */
|
|
538
|
-
protected _containBuf: Map<
|
|
538
|
+
protected _containBuf: Map<NodeId, Set<NodeId>>;
|
|
539
539
|
/** Dedup-target candidates still in the write buffer (keyed by id). Only
|
|
540
540
|
* roots that have gained an edge/halo are targets; a fresh intermediate
|
|
541
541
|
* branch is never folded onto. */
|
|
542
|
-
protected _nearDedupBuf: Map<
|
|
542
|
+
protected _nearDedupBuf: Map<NodeId, Float32Array>;
|
|
543
543
|
/** Ids currently in `_contentBuffer` (not yet flushed) — O(1) membership. */
|
|
544
|
-
protected _bufferedIds: Set<
|
|
544
|
+
protected _bufferedIds: Set<NodeId>;
|
|
545
545
|
/** {@link Store.chainRun} results, valid for the store's lifetime BETWEEN
|
|
546
546
|
* writes: a chain is a pure function of the kid and edge tables, so any
|
|
547
547
|
* write that could break a node's transparency (a fresh mint inserting kid
|
package/jsr.json
ADDED
package/package.json
CHANGED
package/src/mind/bridge.ts
CHANGED
|
@@ -105,16 +105,13 @@ import {
|
|
|
105
105
|
} from "./traverse.js";
|
|
106
106
|
import { rItem, rNode } from "./trace.js";
|
|
107
107
|
import { junctionContainersFrom } from "./junction.js";
|
|
108
|
-
import { spanHalo } from "./match.js";
|
|
108
|
+
import { alignAround, type AlignGap, spanHalo } from "./match.js";
|
|
109
109
|
|
|
110
110
|
/** One accepted substitution: query span [qs,qe) stands in for the
|
|
111
|
-
* candidate context's span — recorded for the rationale trace.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
cs: number;
|
|
116
|
-
ce: number;
|
|
117
|
-
}
|
|
111
|
+
* candidate context's span — recorded for the rationale trace. The same
|
|
112
|
+
* shape the shared aligner reports a disagreement as ({@link AlignGap}); an
|
|
113
|
+
* accepted substitution is a gap that cleared this file's gates. */
|
|
114
|
+
type Substitution = AlignGap;
|
|
118
115
|
|
|
119
116
|
/** A bridged grounding proposal: the trained context to ground, the query
|
|
120
117
|
* spans its alignment accounts for, and the substitutions that closed it. */
|
|
@@ -151,102 +148,11 @@ export function dismissedKnownContent(
|
|
|
151
148
|
return false;
|
|
152
149
|
}
|
|
153
150
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
function align(
|
|
160
|
-
ctx: MindContext,
|
|
161
|
-
q: Uint8Array,
|
|
162
|
-
c: Uint8Array,
|
|
163
|
-
qo: number,
|
|
164
|
-
co: number,
|
|
165
|
-
): { matched: Array<[number, number]>; gaps: Substitution[] } {
|
|
166
|
-
const W = ctx.space.maxGroup;
|
|
167
|
-
const reachCap = chainReach(W);
|
|
168
|
-
// Maximal run around the seed.
|
|
169
|
-
let qs = qo, ss = co;
|
|
170
|
-
while (qs > 0 && ss > 0 && q[qs - 1] === c[ss - 1]) {
|
|
171
|
-
qs--;
|
|
172
|
-
ss--;
|
|
173
|
-
}
|
|
174
|
-
let qe = qo, se = co;
|
|
175
|
-
while (qe < q.length && se < c.length && q[qe] === c[se]) {
|
|
176
|
-
qe++;
|
|
177
|
-
se++;
|
|
178
|
-
}
|
|
179
|
-
const matched: Array<[number, number]> = [[qs, qe]];
|
|
180
|
-
const gaps: Substitution[] = [];
|
|
181
|
-
// The next common run of ≥ W bytes past (qi, si), with each side's gap
|
|
182
|
-
// bounded by chainReach; smallest total gap wins (nearest continuation).
|
|
183
|
-
const runLenAt = (qi: number, si: number): number => {
|
|
184
|
-
let n = 0;
|
|
185
|
-
while (qi + n < q.length && si + n < c.length && q[qi + n] === c[si + n]) {
|
|
186
|
-
n++;
|
|
187
|
-
}
|
|
188
|
-
return n;
|
|
189
|
-
};
|
|
190
|
-
// RIGHT sweep.
|
|
191
|
-
let qi = qe, si = se;
|
|
192
|
-
for (;;) {
|
|
193
|
-
let found = false;
|
|
194
|
-
for (let total = 1; total <= 2 * reachCap && !found; total++) {
|
|
195
|
-
for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
|
|
196
|
-
const gs = total - gq;
|
|
197
|
-
if (gs > reachCap) continue;
|
|
198
|
-
if (qi + gq >= q.length || si + gs >= c.length) continue;
|
|
199
|
-
const n = runLenAt(qi + gq, si + gs);
|
|
200
|
-
if (n >= W || qi + gq + n === q.length) {
|
|
201
|
-
if (n === 0) continue;
|
|
202
|
-
if (gq > 0 || gs > 0) {
|
|
203
|
-
gaps.push({ qs: qi, qe: qi + gq, cs: si, ce: si + gs });
|
|
204
|
-
}
|
|
205
|
-
matched.push([qi + gq, qi + gq + n]);
|
|
206
|
-
qi = qi + gq + n;
|
|
207
|
-
si = si + gs + n;
|
|
208
|
-
found = true;
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (!found) break;
|
|
214
|
-
}
|
|
215
|
-
// LEFT sweep (mirror).
|
|
216
|
-
qi = qs;
|
|
217
|
-
si = ss;
|
|
218
|
-
for (;;) {
|
|
219
|
-
let found = false;
|
|
220
|
-
for (let total = 1; total <= 2 * reachCap && !found; total++) {
|
|
221
|
-
for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
|
|
222
|
-
const gs = total - gq;
|
|
223
|
-
if (gs > reachCap) continue;
|
|
224
|
-
if (qi - gq <= 0 || si - gs <= 0) continue;
|
|
225
|
-
// Run ENDING at (qi - gq, si - gs).
|
|
226
|
-
let n = 0;
|
|
227
|
-
while (
|
|
228
|
-
n < qi - gq && n < si - gs &&
|
|
229
|
-
q[qi - gq - 1 - n] === c[si - gs - 1 - n]
|
|
230
|
-
) {
|
|
231
|
-
n++;
|
|
232
|
-
}
|
|
233
|
-
if (n >= W || n === qi - gq) {
|
|
234
|
-
if (n === 0) continue;
|
|
235
|
-
if (gq > 0 || gs > 0) {
|
|
236
|
-
gaps.push({ qs: qi - gq, qe: qi, cs: si - gs, ce: si });
|
|
237
|
-
}
|
|
238
|
-
matched.push([qi - gq - n, qi - gq]);
|
|
239
|
-
qi = qi - gq - n;
|
|
240
|
-
si = si - gs - n;
|
|
241
|
-
found = true;
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
if (!found) break;
|
|
247
|
-
}
|
|
248
|
-
return { matched, gaps };
|
|
249
|
-
}
|
|
151
|
+
// The seeded aligner this file used to own now lives in the shared match
|
|
152
|
+
// family as {@link alignAround} — the frame reading (match.ts) reads the same
|
|
153
|
+
// gaps and asks the OPPOSITE question of them (see AlignGap's own doc). Two
|
|
154
|
+
// consumers, one definition (AGENTS §2.5); the bridge's reading is unchanged.
|
|
155
|
+
const align = alignAround;
|
|
250
156
|
|
|
251
157
|
/** Recall's corroborated-substitution bridge — see the module comment.
|
|
252
158
|
* Returns the best bridged grounding proposal, or null. */
|