@hviana/sema 0.4.0 → 0.4.2
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/CONTRIBUTING.md +92 -10
- package/LICENSE.md +2 -2
- package/package.json +1 -1
- package/src/ingest-cache.ts +4 -0
- package/src/meter.ts +5 -0
- package/src/mind/attention.ts +18 -1
- package/src/mind/bridge.ts +292 -54
- package/src/mind/junction.ts +21 -7
- package/src/mind/match.ts +92 -1
- package/src/mind/mechanisms/cast.ts +12 -0
- package/src/mind/mechanisms/confluence.ts +30 -1
- package/src/mind/mechanisms/cover.ts +36 -4
- package/src/mind/mechanisms/recall.ts +21 -44
- package/src/mind/mind.ts +39 -2
- package/src/mind/pipeline-mechanism.ts +86 -4
- package/src/mind/pipeline.ts +1 -1
- package/src/mind/reasoning.ts +15 -8
- package/src/mind/recognition.ts +40 -0
- package/src/mind/resonance.ts +0 -0
- package/src/mind/traverse.ts +17 -15
- package/src/mind/types.ts +10 -0
- package/test/49-natural-units-synonym-bridge.test.mjs +56 -15
- package/dist/example/demo.d.ts +0 -1
- package/dist/example/demo.js +0 -39
- package/dist/example/train_base.d.ts +0 -87
- package/dist/example/train_base.js +0 -2252
- package/dist/src/alphabet.d.ts +0 -7
- package/dist/src/alphabet.js +0 -33
- package/dist/src/alu/src/alu.d.ts +0 -185
- package/dist/src/alu/src/alu.js +0 -440
- package/dist/src/alu/src/expr.d.ts +0 -61
- package/dist/src/alu/src/expr.js +0 -318
- package/dist/src/alu/src/index.d.ts +0 -11
- package/dist/src/alu/src/index.js +0 -19
- package/dist/src/alu/src/kernel-arith.d.ts +0 -16
- package/dist/src/alu/src/kernel-arith.js +0 -264
- package/dist/src/alu/src/kernel-bits.d.ts +0 -19
- package/dist/src/alu/src/kernel-bits.js +0 -152
- package/dist/src/alu/src/kernel-logic.d.ts +0 -4
- package/dist/src/alu/src/kernel-logic.js +0 -60
- package/dist/src/alu/src/kernel-nd.d.ts +0 -3
- package/dist/src/alu/src/kernel-nd.js +0 -208
- package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
- package/dist/src/alu/src/kernel-numeric.js +0 -366
- package/dist/src/alu/src/operation.d.ts +0 -168
- package/dist/src/alu/src/operation.js +0 -189
- package/dist/src/alu/src/parser.d.ts +0 -221
- package/dist/src/alu/src/parser.js +0 -577
- package/dist/src/alu/src/resonance.d.ts +0 -55
- package/dist/src/alu/src/resonance.js +0 -126
- package/dist/src/alu/src/text.d.ts +0 -31
- package/dist/src/alu/src/text.js +0 -73
- package/dist/src/alu/src/value.d.ts +0 -109
- package/dist/src/alu/src/value.js +0 -300
- package/dist/src/alu/test/alu.test.d.ts +0 -1
- package/dist/src/alu/test/alu.test.js +0 -764
- package/dist/src/bytes.d.ts +0 -14
- package/dist/src/bytes.js +0 -59
- package/dist/src/canon.d.ts +0 -26
- package/dist/src/canon.js +0 -57
- package/dist/src/config.d.ts +0 -111
- package/dist/src/config.js +0 -91
- package/dist/src/derive/src/deduction.d.ts +0 -136
- package/dist/src/derive/src/deduction.js +0 -159
- package/dist/src/derive/src/index.d.ts +0 -8
- package/dist/src/derive/src/index.js +0 -11
- package/dist/src/derive/src/priority-queue.d.ts +0 -20
- package/dist/src/derive/src/priority-queue.js +0 -73
- package/dist/src/derive/src/rewrite.d.ts +0 -56
- package/dist/src/derive/src/rewrite.js +0 -100
- package/dist/src/derive/src/trie.d.ts +0 -90
- package/dist/src/derive/src/trie.js +0 -217
- package/dist/src/derive/test/derive.test.d.ts +0 -1
- package/dist/src/derive/test/derive.test.js +0 -122
- package/dist/src/extension.d.ts +0 -37
- package/dist/src/extension.js +0 -7
- package/dist/src/geometry.d.ts +0 -175
- package/dist/src/geometry.js +0 -823
- package/dist/src/index.d.ts +0 -17
- package/dist/src/index.js +0 -19
- package/dist/src/ingest-cache.d.ts +0 -41
- package/dist/src/ingest-cache.js +0 -161
- package/dist/src/meter.d.ts +0 -171
- package/dist/src/meter.js +0 -269
- package/dist/src/mind/articulation.d.ts +0 -6
- package/dist/src/mind/articulation.js +0 -99
- package/dist/src/mind/attention.d.ts +0 -414
- package/dist/src/mind/attention.js +0 -2065
- package/dist/src/mind/bridge.d.ts +0 -39
- package/dist/src/mind/bridge.js +0 -738
- package/dist/src/mind/canonical.d.ts +0 -34
- package/dist/src/mind/canonical.js +0 -93
- package/dist/src/mind/graph-search.d.ts +0 -294
- package/dist/src/mind/graph-search.js +0 -996
- package/dist/src/mind/index.d.ts +0 -9
- package/dist/src/mind/index.js +0 -5
- package/dist/src/mind/junction.d.ts +0 -134
- package/dist/src/mind/junction.js +0 -331
- package/dist/src/mind/learning.d.ts +0 -75
- package/dist/src/mind/learning.js +0 -270
- package/dist/src/mind/match.d.ts +0 -166
- package/dist/src/mind/match.js +0 -565
- package/dist/src/mind/mechanisms/alu.d.ts +0 -4
- package/dist/src/mind/mechanisms/alu.js +0 -36
- package/dist/src/mind/mechanisms/cast.d.ts +0 -89
- package/dist/src/mind/mechanisms/cast.js +0 -773
- package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
- package/dist/src/mind/mechanisms/confluence.js +0 -225
- package/dist/src/mind/mechanisms/cover.d.ts +0 -6
- package/dist/src/mind/mechanisms/cover.js +0 -203
- package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
- package/dist/src/mind/mechanisms/extraction.js +0 -300
- package/dist/src/mind/mechanisms/recall.d.ts +0 -16
- package/dist/src/mind/mechanisms/recall.js +0 -388
- package/dist/src/mind/mind.d.ts +0 -330
- package/dist/src/mind/mind.js +0 -594
- package/dist/src/mind/pipeline-mechanism.d.ts +0 -172
- package/dist/src/mind/pipeline-mechanism.js +0 -382
- package/dist/src/mind/pipeline.d.ts +0 -49
- package/dist/src/mind/pipeline.js +0 -275
- package/dist/src/mind/primitives.d.ts +0 -66
- package/dist/src/mind/primitives.js +0 -306
- package/dist/src/mind/rationale.d.ts +0 -139
- package/dist/src/mind/rationale.js +0 -163
- package/dist/src/mind/reasoning.d.ts +0 -40
- package/dist/src/mind/reasoning.js +0 -272
- package/dist/src/mind/recognition.d.ts +0 -20
- package/dist/src/mind/recognition.js +0 -463
- package/dist/src/mind/resonance.d.ts +0 -23
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +0 -15
- package/dist/src/mind/trace.js +0 -73
- package/dist/src/mind/traverse.d.ts +0 -124
- package/dist/src/mind/traverse.js +0 -649
- package/dist/src/mind/types.d.ts +0 -323
- package/dist/src/mind/types.js +0 -130
- package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
- package/dist/src/rabitq-ivf/src/database.js +0 -201
- package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
- package/dist/src/rabitq-ivf/src/index.js +0 -4
- package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
- package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
- package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
- package/dist/src/rabitq-ivf/src/prng.js +0 -36
- package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
- package/dist/src/rabitq-ivf/src/rabitq.js +0 -283
- package/dist/src/sema.d.ts +0 -31
- package/dist/src/sema.js +0 -63
- package/dist/src/store-sqlite.d.ts +0 -184
- package/dist/src/store-sqlite.js +0 -942
- package/dist/src/store.d.ts +0 -678
- package/dist/src/store.js +0 -1703
- package/dist/src/vec.d.ts +0 -31
- package/dist/src/vec.js +0 -109
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
// pipeline.ts — the think pipeline (Section 5 of the mind).
|
|
2
|
-
//
|
|
3
|
-
// think() is the whole file's job: one lightest-derivation choice among
|
|
4
|
-
// UNIFORM mechanisms. The pipeline sees mechanisms through the
|
|
5
|
-
// PipelineMechanism interface only — it never imports a mechanism-specific
|
|
6
|
-
// type and never has a special-case branch for any mechanism. Adding a
|
|
7
|
-
// mechanism means registering one object; removing one means dropping it
|
|
8
|
-
// from the list. The mechanisms themselves live in mechanisms/ (one file
|
|
9
|
-
// each); the shared pre-computation they exchange lives in Precomputed
|
|
10
|
-
// (pipeline-mechanism.ts).
|
|
11
|
-
import { PASS, STEP } from "./graph-search.js";
|
|
12
|
-
import { gistOf, resolve } from "./primitives.js";
|
|
13
|
-
import { recognise } from "./recognition.js";
|
|
14
|
-
import { fuseAttention, reason } from "./reasoning.js";
|
|
15
|
-
import { unexplainedSpans } from "./rationale.js";
|
|
16
|
-
import { rItem } from "./trace.js";
|
|
17
|
-
import { Precomputed } from "./pipeline-mechanism.js";
|
|
18
|
-
import { coverMechanism } from "./mechanisms/cover.js";
|
|
19
|
-
import { castMechanism } from "./mechanisms/cast.js";
|
|
20
|
-
import { confluenceMechanism } from "./mechanisms/confluence.js";
|
|
21
|
-
import { extractionMechanism } from "./mechanisms/extraction.js";
|
|
22
|
-
import { recallMechanism } from "./mechanisms/recall.js";
|
|
23
|
-
// Re-exports: cover's pre-resolution helpers and the ALU adapter kept
|
|
24
|
-
// importable from the pipeline module (their historical home).
|
|
25
|
-
export { resolveConcepts, resolveConnectors } from "./mechanisms/cover.js";
|
|
26
|
-
export { aluToMechanism } from "./mechanisms/alu.js";
|
|
27
|
-
// ── Extension dispatch (pre-loop parse) ─────────────────────────────────────
|
|
28
|
-
async function collectComputed(ctx, mechanisms, query) {
|
|
29
|
-
const out = [];
|
|
30
|
-
const meter = ctx.meter;
|
|
31
|
-
for (const m of mechanisms) {
|
|
32
|
-
if (!m.parse)
|
|
33
|
-
continue;
|
|
34
|
-
const spans = meter
|
|
35
|
-
? await meter.time(`${m.name}.parse`, () => m.parse(query))
|
|
36
|
-
: await m.parse(query);
|
|
37
|
-
out.push(...spans);
|
|
38
|
-
}
|
|
39
|
-
return out;
|
|
40
|
-
}
|
|
41
|
-
// ── Built-in mechanisms ─────────────────────────────────────────────────────
|
|
42
|
-
// ORDER MATTERS, but only through the uniform floor/worthRunning pruning —
|
|
43
|
-
// no mechanism is special-cased. Cover runs FIRST: when a computed
|
|
44
|
-
// extension result (e.g. ALU) exists, cover masks it in at near-zero cost
|
|
45
|
-
// (see mechanisms/cover.ts), which becomes `best` before any other mechanism
|
|
46
|
-
// invests in its own precomputation. CAST's and confluence's floors (2*STEP,
|
|
47
|
-
// 3*STEP) then fail `worthRunning` and are skipped by the SAME admissible-
|
|
48
|
-
// floor pruning every mechanism is already subject to — not by asking
|
|
49
|
-
// "is this an extension?". Grade TIES keep the earlier candidate, so this
|
|
50
|
-
// order is also the tie-break priority: cover, cast, confluence, extraction,
|
|
51
|
-
// recall.
|
|
52
|
-
export const defaultMechanisms = [
|
|
53
|
-
coverMechanism,
|
|
54
|
-
castMechanism,
|
|
55
|
-
confluenceMechanism,
|
|
56
|
-
extractionMechanism,
|
|
57
|
-
recallMechanism,
|
|
58
|
-
];
|
|
59
|
-
/** Think: a single lightest-derivation exploration of the Sema graph.
|
|
60
|
-
*
|
|
61
|
-
* Every answer travels the same path:
|
|
62
|
-
* 1. Pre-computation — recognise, extension parse, guide; everything
|
|
63
|
-
* expensive stays lazy on Precomputed until a mechanism asks.
|
|
64
|
-
* 2. Grounding — every mechanism yields candidates weighed in the one
|
|
65
|
-
* cost ladder; the lightest grounding derivation wins.
|
|
66
|
-
* 3. Post-grounding — diagnostics (narrowDecision, thinGrounding),
|
|
67
|
-
* reasoning (multi-hop), fusion (multi-topic). */
|
|
68
|
-
export async function think(ctx, query, mechs) {
|
|
69
|
-
if (query.length === 0)
|
|
70
|
-
return null;
|
|
71
|
-
ctx._edgeGuide = gistOf(ctx, query);
|
|
72
|
-
ctx._edgeChoice.clear();
|
|
73
|
-
const t = ctx.trace?.enter("think", [rItem(query, "query")]);
|
|
74
|
-
const done = (answer, note) => {
|
|
75
|
-
t?.done(answer
|
|
76
|
-
? [rItem(answer, "answer", resolve(ctx, answer) ?? undefined)]
|
|
77
|
-
: [], note);
|
|
78
|
-
return answer;
|
|
79
|
-
};
|
|
80
|
-
// ── Pre-computation ──────────────────────────────────────────────────
|
|
81
|
-
const mechanisms = mechs ?? defaultMechanisms;
|
|
82
|
-
const rec = recognise(ctx, query);
|
|
83
|
-
// Phase 1: collect computed spans from mechanisms that implement parse()
|
|
84
|
-
const computed = await collectComputed(ctx, mechanisms, query);
|
|
85
|
-
if (computed.length > 0) {
|
|
86
|
-
ctx.trace?.step("computeExtensions", [rItem(query, "query")], computed.map((u) => rItem(query.subarray(u.i, u.j), "operand", undefined, [u.i, u.j])), `extensions recognised and evaluated ${computed.length} computation(s)`);
|
|
87
|
-
for (const u of computed) {
|
|
88
|
-
ctx.trace?.step("evalComputation", [rItem(query.subarray(u.i, u.j), "expression", undefined, [u.i, u.j])], [rItem(u.bytes, "result", resolve(ctx, u.bytes) ?? undefined)], "evaluate the recognised operation to its authoritative result");
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
// Phase 2: the shared pre-computation container. Eager fields only
|
|
92
|
-
// (recognition, computed spans, guide) — every expensive analysis
|
|
93
|
-
// (consensus climb, weave, span-shape classification) is a lazily-cached
|
|
94
|
-
// method on Precomputed, first-touched by whichever mechanism's floor
|
|
95
|
-
// survives its cheap gates and the worthRunning check. A query no
|
|
96
|
-
// mechanism climbs for (e.g. one an extension decided) never climbs.
|
|
97
|
-
const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
|
|
98
|
-
const grade = (w) => Math.floor(w / STEP);
|
|
99
|
-
const unaccounted = (spans) => unexplainedSpans(query.length, spans)
|
|
100
|
-
.reduce((sum, [s, e]) => sum + (e - s), 0);
|
|
101
|
-
const weigh = (accounted, moves) => moves + PASS * unaccounted(accounted);
|
|
102
|
-
const candidates = [];
|
|
103
|
-
let best = null;
|
|
104
|
-
const consider = (c) => {
|
|
105
|
-
if (c.bytes.length === 0)
|
|
106
|
-
return;
|
|
107
|
-
if (ctx.meter)
|
|
108
|
-
ctx.meter.candidates++;
|
|
109
|
-
candidates.push(c);
|
|
110
|
-
if (best === null || grade(c.weight) < grade(best.weight))
|
|
111
|
-
best = c;
|
|
112
|
-
};
|
|
113
|
-
const worthRunning = (floor) => best === null || grade(floor) < grade(best.weight);
|
|
114
|
-
// Phase 3: grounding loop
|
|
115
|
-
// Per-mechanism accounting (src/meter.ts). The market's whole premise is
|
|
116
|
-
// that mechanisms compete on one cost scale — so the profiling read-out is
|
|
117
|
-
// also per-mechanism, uniformly: the loop never asks which one it holds.
|
|
118
|
-
const meter = ctx.meter;
|
|
119
|
-
for (const mech of mechanisms) {
|
|
120
|
-
const floor = meter
|
|
121
|
-
? await meter.time(`${mech.name}.floor`, () => mech.floor(ctx, query, pre, worthRunning))
|
|
122
|
-
: await mech.floor(ctx, query, pre, worthRunning);
|
|
123
|
-
if (meter) {
|
|
124
|
-
if (floor === null)
|
|
125
|
-
meter.mechanismSkips++;
|
|
126
|
-
else
|
|
127
|
-
meter.mechanismFloors++;
|
|
128
|
-
}
|
|
129
|
-
if (floor === null) {
|
|
130
|
-
ctx.trace?.step("skipMechanism", [], [], `${mech.name} skipped — structural precondition failed`);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
if (!worthRunning(floor)) {
|
|
134
|
-
ctx.trace?.step("skipMechanism", [], [], `${mech.name} skipped — floor ${floor} cannot beat incumbent (grade ${grade(best.weight)})`);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (meter)
|
|
138
|
-
meter.mechanismRuns++;
|
|
139
|
-
const results = meter
|
|
140
|
-
? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
|
|
141
|
-
: await mech.run(ctx, query, pre);
|
|
142
|
-
for (const r of results) {
|
|
143
|
-
const weight = r.weight ?? weigh(r.accounted, r.moves);
|
|
144
|
-
consider({
|
|
145
|
-
bytes: r.bytes,
|
|
146
|
-
provenance: r.provenance ?? mech.provenance,
|
|
147
|
-
weight,
|
|
148
|
-
used: r.used,
|
|
149
|
-
accounted: r.accounted,
|
|
150
|
-
unexplained: r.unexplained,
|
|
151
|
-
complete: r.complete,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
// (TS cannot see the closure assignments into `best` and narrows it to its
|
|
156
|
-
// initial null, so the read-back needs the assertion.)
|
|
157
|
-
const decided = best;
|
|
158
|
-
if (candidates.length > 1) {
|
|
159
|
-
// The runner-up is computed BEFORE the decideGrounding step so its grade
|
|
160
|
-
// margin can ride along in the step's structured data payload; the
|
|
161
|
-
// computation itself is pure and was always unconditional — only its
|
|
162
|
-
// position moved.
|
|
163
|
-
let runnerUp = null;
|
|
164
|
-
if (decided !== null) {
|
|
165
|
-
for (const c of candidates) {
|
|
166
|
-
if (c === decided)
|
|
167
|
-
continue;
|
|
168
|
-
if (runnerUp === null || grade(c.weight) < grade(runnerUp.weight)) {
|
|
169
|
-
runnerUp = c;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
const margin = decided !== null && runnerUp !== null
|
|
174
|
-
? grade(runnerUp.weight) - grade(decided.weight)
|
|
175
|
-
: null;
|
|
176
|
-
ctx.trace?.step("decideGrounding", candidates.map((c) => rItem(c.bytes, `${c.provenance} (weight ${c.weight.toFixed(3)}${c.unexplained ? `, unexplained: "${c.unexplained}"` : ""})`)), decided ? [rItem(decided.bytes, decided.provenance)] : [], "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder", undefined, {
|
|
177
|
-
version: 1,
|
|
178
|
-
candidates: candidates.map((c) => ({
|
|
179
|
-
provenance: c.provenance,
|
|
180
|
-
weight: c.weight,
|
|
181
|
-
grade: grade(c.weight),
|
|
182
|
-
unexplainedBytes: unaccounted(c.accounted),
|
|
183
|
-
decided: c === decided,
|
|
184
|
-
})),
|
|
185
|
-
...(margin !== null ? { runnerUpMargin: margin } : {}),
|
|
186
|
-
});
|
|
187
|
-
if (decided !== null && runnerUp !== null && margin !== null) {
|
|
188
|
-
if (margin <= 1) {
|
|
189
|
-
ctx.trace?.step("narrowDecision", [
|
|
190
|
-
rItem(decided.bytes, `${decided.provenance} (weight ${decided.weight.toFixed(3)})`),
|
|
191
|
-
], [
|
|
192
|
-
rItem(runnerUp.bytes, `${runnerUp.provenance} (weight ${runnerUp.weight.toFixed(3)})`),
|
|
193
|
-
], `margin ${margin} grade-unit(s) — the decision could change with one more training fact`, undefined, { version: 1, margin });
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (decided === null) {
|
|
198
|
-
done(null, "no mechanism grounded an answer");
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
// Honesty density
|
|
202
|
-
{
|
|
203
|
-
const covered = query.length - unaccounted(decided.accounted);
|
|
204
|
-
const density = query.length > 0 ? covered / query.length : 1;
|
|
205
|
-
const thinBar = 1 / ctx.space.maxGroup;
|
|
206
|
-
if (density < thinBar) {
|
|
207
|
-
ctx.trace?.step("thinGrounding", [rItem(decided.bytes, decided.provenance)], [], `grounded but thin — density ${density.toFixed(3)} is below 1/W (${thinBar.toFixed(3)})`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
const answer = decided.bytes;
|
|
211
|
-
const provenance = decided.provenance;
|
|
212
|
-
const castUsed = decided.used ?? new Set();
|
|
213
|
-
// ── Post-grounding, gated by provenance ──────────────────────────────
|
|
214
|
-
const preConsumed = provenance === "cast" || provenance === "join"
|
|
215
|
-
? castUsed
|
|
216
|
-
: provenance === "recall" || provenance === "recall-echo"
|
|
217
|
-
? new Set()
|
|
218
|
-
: new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
219
|
-
// A grounding that DECLARED itself complete is not extended: the answer is
|
|
220
|
-
// already a trained form's own continuation, reached through an identity
|
|
221
|
-
// claim about the query, so a multi-hop pivot could only chain past the
|
|
222
|
-
// fact that produced it (see MechanismResult.complete).
|
|
223
|
-
const reasoned = decided.complete ? answer : meter
|
|
224
|
-
? await meter.time("reason", () => reason(ctx, query, answer, preConsumed, pre))
|
|
225
|
-
: await reason(ctx, query, answer, preConsumed, pre);
|
|
226
|
-
// Fuse only when the query has a genuine REMAINDER no mechanism's
|
|
227
|
-
// structural evidence touched at all. `decided.accounted` alone
|
|
228
|
-
// undercounts this: it is a COST-LADDER quantity (cover.ts prices its
|
|
229
|
-
// masked/computed spans at near-zero and deliberately leaves them out of
|
|
230
|
-
// `accounted` so PASS-bridged bytes are still charged), not a coverage
|
|
231
|
-
// one — a query fully explained by one computed span plus bridged
|
|
232
|
-
// connectors can report `accounted: []` while nothing is actually left
|
|
233
|
-
// unexplained. The genuine remainder is what NEITHER the winning
|
|
234
|
-
// candidate's accounted spans NOR any recognised extension's computed
|
|
235
|
-
// span (`pre.computed` — every mechanism's parse() output, ALU included)
|
|
236
|
-
// ever touched. A remainder under one river-fold quantum (W, the same
|
|
237
|
-
// floor cover.ts's restatedSpan and the honesty-density bar above both
|
|
238
|
-
// use) is bridging punctuation/whitespace, never a second topic —
|
|
239
|
-
// observed: a single space between two fully-computed arithmetic spans
|
|
240
|
-
// ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
|
|
241
|
-
// corpus fact, corrupting "4 6" into "4 63".
|
|
242
|
-
const explained = [
|
|
243
|
-
...decided.accounted,
|
|
244
|
-
...pre.computed.map((u) => [u.i, u.j]),
|
|
245
|
-
];
|
|
246
|
-
const remainder = unaccounted(explained);
|
|
247
|
-
// Whether the winning candidate's entire recognised substance is
|
|
248
|
-
// COMPUTED — every accounted span exactly a pre.computed span, nothing
|
|
249
|
-
// from a genuinely recognised/climbed site. fuseAttention's lone-root
|
|
250
|
-
// shortcut assumes a single point of attention already IS primary's own
|
|
251
|
-
// source; that assumption is exactly backwards for a pure computation
|
|
252
|
-
// (an ALU result has no anchor of its own) — see fuseAttention's
|
|
253
|
-
// `unclimbed` parameter, gated there by Attention.breadth so a
|
|
254
|
-
// coincidental echo (which this flag alone cannot distinguish) is still
|
|
255
|
-
// rejected.
|
|
256
|
-
const unclimbed = decided.accounted.length > 0 &&
|
|
257
|
-
decided.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
|
|
258
|
-
// Where the winning grounding stands in the query — fusion places primary
|
|
259
|
-
// by it (see fuseAttention's `primarySpans`). `accounted` is the
|
|
260
|
-
// cost-ladder read and is authoritative when non-empty; when it is empty
|
|
261
|
-
// the grounding is a pure COMPUTATION, whose evidence is its computed span.
|
|
262
|
-
// Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
|
|
263
|
-
// read here for POSITION instead of for coverage — and resolved here, where
|
|
264
|
-
// both readings are in hand, rather than inside fuseAttention.
|
|
265
|
-
const primarySpans = decided.accounted.length > 0
|
|
266
|
-
? decided.accounted
|
|
267
|
-
: pre.computed.map((u) => [u.i, u.j]);
|
|
268
|
-
const fused = remainder < ctx.space.maxGroup
|
|
269
|
-
? reasoned
|
|
270
|
-
: meter
|
|
271
|
-
? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
|
|
272
|
-
: await fuseAttention(ctx, query, reasoned, pre, unclimbed, decided.accounted);
|
|
273
|
-
done(fused, "grounded, reasoned forward, fused across points of attention");
|
|
274
|
-
return { bytes: fused, provenance };
|
|
275
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { Vec } from "../vec.js";
|
|
2
|
-
import { Sema } from "../sema.js";
|
|
3
|
-
import type { Input, MindContext } from "./types.js";
|
|
4
|
-
/** The content key of a byte span — one latin1 char per byte, an exact,
|
|
5
|
-
* collision-free encoding. Spans on the perception path are query-scale
|
|
6
|
-
* (windows, regions, candidate spans), so key construction is far cheaper
|
|
7
|
-
* than the river fold it deduplicates. */
|
|
8
|
-
export declare function latin1Key(bytes: Uint8Array): string;
|
|
9
|
-
/** Perceive input into a content-defined tree (the river fold).
|
|
10
|
-
* Deterministic — identical bytes always produce an identical tree.
|
|
11
|
-
*
|
|
12
|
-
* `boundaries` is an optional sorted list of proper byte offsets where the
|
|
13
|
-
* fold must split so that each prefix segment folds identically to how it
|
|
14
|
-
* folded when it was learned (§10.3 stable-prefix contract). Only the
|
|
15
|
-
* CALLER — who assembled the multi-turn context — knows where those
|
|
16
|
-
* boundaries are; the geometry never guesses them from the bytes. */
|
|
17
|
-
export declare function perceive(ctx: MindContext, input: Input, leafAt?: (i: number) => number | null, lookup?: (ids: number[]) => number | null, boundaries?: readonly number[]): Sema;
|
|
18
|
-
/** The DEPOSIT-shaped perceive. Folds over the stream's own content cuts —
|
|
19
|
-
* bit-identical to what inference computes for the same bytes, and that
|
|
20
|
-
* train/inference agreement is load-bearing for exact recall. An input that
|
|
21
|
-
* EXTENDS a previously deposited one is a conversation context grown by one
|
|
22
|
-
* turn; the cached prefix length IS the turn boundary (derived from the deposit
|
|
23
|
-
* sequence itself, never from a content convention) and joins the cut set, so
|
|
24
|
-
* the trained context node and the query's context subtree are the SAME node.
|
|
25
|
-
* Segment folds reuse across deposits ({@link stablePrefixFoldIncremental}) —
|
|
26
|
-
* O(turn) instead of O(context) per turn. All of it is purely a cache: an
|
|
27
|
-
* evicted chain loses only the turn boundaries, and since the content cuts do
|
|
28
|
-
* not depend on the cache, the segments themselves are unchanged. */
|
|
29
|
-
export declare function perceiveDeposit(ctx: MindContext, bytes: Uint8Array, conversational?: boolean): Sema;
|
|
30
|
-
/** The raw bytes of an input — modality-neutral conversion. */
|
|
31
|
-
export declare function inputBytes(ctx: MindContext, input: Input): Uint8Array;
|
|
32
|
-
/** Convenience: the gist vector of a byte span. */
|
|
33
|
-
export declare function gistOf(ctx: MindContext, bytes: Uint8Array): Vec;
|
|
34
|
-
/** Fold a perceived tree bottom-up against the store's content-addressed maps:
|
|
35
|
-
* every leaf is named by findLeaf, every branch by findBranch over its kids'
|
|
36
|
-
* ids (null the moment any child is unknown). `visit`, when given, sees each
|
|
37
|
-
* node with its byte span and resolved id. Returns the node's byte end and
|
|
38
|
-
* resolved id. */
|
|
39
|
-
export declare function foldTree(ctx: MindContext, n: Sema, start: number, visit?: (n: Sema, start: number, end: number, node: number | null) => void): {
|
|
40
|
-
end: number;
|
|
41
|
-
node: number | null;
|
|
42
|
-
};
|
|
43
|
-
/** The canonical node id of a byte span: perceive it in isolation — the way
|
|
44
|
-
* training did — and recover its root bottom-up. Returns null if any part is
|
|
45
|
-
* unknown. */
|
|
46
|
-
export declare function resolve(ctx: MindContext, bytes: Uint8Array): number | null;
|
|
47
|
-
/** Equivalence-class resolution: when the exact content-addressed lookup
|
|
48
|
-
* misses, find a stored node whose CANONICAL key equals the span's — the
|
|
49
|
-
* store's canon index proposes candidates by key hash, and each is verified
|
|
50
|
-
* by re-canonicalizing its bytes (hash-then-verify, like every content
|
|
51
|
-
* lookup). Among verified candidates, one that leads somewhere (has a
|
|
52
|
-
* continuation edge) is preferred; ties break to the lowest id — a corpus
|
|
53
|
-
* property, not a seed property. Null when the response carries no
|
|
54
|
-
* canonicalizer, the store has no canon index, or nothing verifies. */
|
|
55
|
-
export declare function canonResolve(ctx: MindContext, bytes: Uint8Array): number | null;
|
|
56
|
-
/** Walk a perceived tree in POST-ORDER with byte offsets — children before
|
|
57
|
-
* their parent, `visit(node, start, end)` for every node including leaves.
|
|
58
|
-
* Returns the byte end. The one shared traversal the offset-carrying tree
|
|
59
|
-
* readers (recognition via foldTree's richer variant, attention's region
|
|
60
|
-
* collection, resonance's branch counting) build on, so each does not
|
|
61
|
-
* re-derive the offset bookkeeping. (recognition.segment keeps its own
|
|
62
|
-
* walk: its flush semantics need PRE-order decisions at leaf-parents, which
|
|
63
|
-
* a post-order visitor cannot express.) */
|
|
64
|
-
export declare function walkTree(n: Sema, start: number, visit: (node: Sema, start: number, end: number) => void): number;
|
|
65
|
-
/** Reconstruct a node's byte content from the DAG, up to `maxLen` bytes. */
|
|
66
|
-
export declare function read(ctx: MindContext, id: number, maxLen?: number): Uint8Array;
|
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
// primitives.ts — Address + Read primitives (Section 1 of the mind).
|
|
2
|
-
//
|
|
3
|
-
// Address — bytes → node (perceive, foldTree, resolve)
|
|
4
|
-
// Read — node → bytes (read)
|
|
5
|
-
import { bytesToTree, gridToTree, hilbertBytes, stablePrefixFoldIncremental, stackGrids, } from "../geometry.js";
|
|
6
|
-
import { canonHash } from "../canon.js";
|
|
7
|
-
import { bytesEqual } from "../bytes.js";
|
|
8
|
-
import { ALL } from "./types.js";
|
|
9
|
-
// ── Address: bytes → node ──────────────────────────────────────────────
|
|
10
|
-
/** The content key of a byte span — one latin1 char per byte, an exact,
|
|
11
|
-
* collision-free encoding. Spans on the perception path are query-scale
|
|
12
|
-
* (windows, regions, candidate spans), so key construction is far cheaper
|
|
13
|
-
* than the river fold it deduplicates. */
|
|
14
|
-
export function latin1Key(bytes) {
|
|
15
|
-
// Batched String.fromCharCode — avoids the O(n²) cost of repeated += on
|
|
16
|
-
// potentially-large query spans, and stays well under the ~65536 arg limit.
|
|
17
|
-
const n = bytes.length;
|
|
18
|
-
let s = "";
|
|
19
|
-
for (let i = 0; i < n; i += 4096) {
|
|
20
|
-
s += String.fromCharCode(...bytes.subarray(i, Math.min(i + 4096, n)));
|
|
21
|
-
}
|
|
22
|
-
return s;
|
|
23
|
-
}
|
|
24
|
-
/** Perceive input into a content-defined tree (the river fold).
|
|
25
|
-
* Deterministic — identical bytes always produce an identical tree.
|
|
26
|
-
*
|
|
27
|
-
* `boundaries` is an optional sorted list of proper byte offsets where the
|
|
28
|
-
* fold must split so that each prefix segment folds identically to how it
|
|
29
|
-
* folded when it was learned (§10.3 stable-prefix contract). Only the
|
|
30
|
-
* CALLER — who assembled the multi-turn context — knows where those
|
|
31
|
-
* boundaries are; the geometry never guesses them from the bytes. */
|
|
32
|
-
export function perceive(ctx, input, leafAt, lookup, boundaries) {
|
|
33
|
-
if (typeof input === "string" || input instanceof Uint8Array) {
|
|
34
|
-
const bytes = typeof input === "string"
|
|
35
|
-
? new TextEncoder().encode(input)
|
|
36
|
-
: input;
|
|
37
|
-
if (leafAt === undefined && lookup === undefined) {
|
|
38
|
-
// Per-response memo (see MindContext.perceiveMemo): only the plain
|
|
39
|
-
// inference shape — raw bytes, no store capabilities — is memoised,
|
|
40
|
-
// keyed by CONTENT so byte-identical spans in fresh arrays still hit.
|
|
41
|
-
// The tree is shared by reference; Sema nodes are never mutated.
|
|
42
|
-
const memo = ctx.perceiveMemo;
|
|
43
|
-
if (memo) {
|
|
44
|
-
const key = latin1Key(bytes);
|
|
45
|
-
const hit = memo.get(key);
|
|
46
|
-
if (hit !== undefined) {
|
|
47
|
-
if (ctx.meter)
|
|
48
|
-
ctx.meter.perceiveHits++;
|
|
49
|
-
return hit;
|
|
50
|
-
}
|
|
51
|
-
if (ctx.meter) {
|
|
52
|
-
ctx.meter.perceptions++;
|
|
53
|
-
ctx.meter.perceivedBytes += bytes.length;
|
|
54
|
-
}
|
|
55
|
-
const tree = bytesToTree(ctx.space, ctx.alphabet, bytes, undefined, undefined, boundaries);
|
|
56
|
-
memo.set(key, tree);
|
|
57
|
-
return tree;
|
|
58
|
-
}
|
|
59
|
-
if (ctx.meter) {
|
|
60
|
-
ctx.meter.perceptions++;
|
|
61
|
-
ctx.meter.perceivedBytes += bytes.length;
|
|
62
|
-
}
|
|
63
|
-
return bytesToTree(ctx.space, ctx.alphabet, bytes, undefined, undefined, boundaries);
|
|
64
|
-
}
|
|
65
|
-
return bytesToTree(ctx.space, ctx.alphabet, bytes, leafAt, lookup);
|
|
66
|
-
}
|
|
67
|
-
if (Array.isArray(input)) {
|
|
68
|
-
return gridToTree(ctx.space, ctx.alphabet, stackGrids(input));
|
|
69
|
-
}
|
|
70
|
-
return gridToTree(ctx.space, ctx.alphabet, input);
|
|
71
|
-
}
|
|
72
|
-
/** The DEPOSIT-shaped perceive. Folds over the stream's own content cuts —
|
|
73
|
-
* bit-identical to what inference computes for the same bytes, and that
|
|
74
|
-
* train/inference agreement is load-bearing for exact recall. An input that
|
|
75
|
-
* EXTENDS a previously deposited one is a conversation context grown by one
|
|
76
|
-
* turn; the cached prefix length IS the turn boundary (derived from the deposit
|
|
77
|
-
* sequence itself, never from a content convention) and joins the cut set, so
|
|
78
|
-
* the trained context node and the query's context subtree are the SAME node.
|
|
79
|
-
* Segment folds reuse across deposits ({@link stablePrefixFoldIncremental}) —
|
|
80
|
-
* O(turn) instead of O(context) per turn. All of it is purely a cache: an
|
|
81
|
-
* evicted chain loses only the turn boundaries, and since the content cuts do
|
|
82
|
-
* not depend on the cache, the segments themselves are unchanged. */
|
|
83
|
-
export function perceiveDeposit(ctx, bytes, conversational = false) {
|
|
84
|
-
let prev;
|
|
85
|
-
let prefixLen = 0;
|
|
86
|
-
// Cache consult (both boundary lookup and stable-prefix reuse) is scoped
|
|
87
|
-
// to conversational deposits only — a bare, unrelated fact whose bytes
|
|
88
|
-
// happen to extend an earlier deposit is NOT a conversation turn, and
|
|
89
|
-
// must keep the plain fold so it shares structure with ITS OWN prior
|
|
90
|
-
// deposits, not fragment against a coincidental byte-prefix.
|
|
91
|
-
if (conversational) {
|
|
92
|
-
// Longest cached PROPER prefix first.
|
|
93
|
-
const lens = [...ctx._depositLens]
|
|
94
|
-
.filter((L) => L >= 2 && L < bytes.length)
|
|
95
|
-
.sort((a, b) => b - a);
|
|
96
|
-
for (const L of lens) {
|
|
97
|
-
const hit = ctx._depositTrees.get(latin1Key(bytes.subarray(0, L)));
|
|
98
|
-
// The suffix must bytes-equal the hit's OWN recorded continuation —
|
|
99
|
-
// proof this deposit is that turn's actual next turn, not a fact
|
|
100
|
-
// that coincidentally shares its byte prefix.
|
|
101
|
-
if (hit !== undefined && hit.nextBytes !== undefined &&
|
|
102
|
-
bytesEqual(hit.nextBytes, bytes.subarray(L))) {
|
|
103
|
-
prev = hit;
|
|
104
|
-
prefixLen = L;
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// ONLY turn boundaries belong here. The stream's own content cuts are NOT
|
|
110
|
-
// passed in: `bytesToTree` and `stablePrefixFoldIncremental` both derive them
|
|
111
|
-
// per span, at every level, and handing the level-0 cuts in as stable-prefix
|
|
112
|
-
// boundaries instead produces a LEFT-NESTED join of flat segments — a
|
|
113
|
-
// different tree from the one inference builds for the same bytes. When that
|
|
114
|
-
// happened, a deposit's context root and `resolve(question)` were different
|
|
115
|
-
// nodes, so the trained edge hung off a node inference never reached and
|
|
116
|
-
// recall went silent (test/44 caught it as a site that could not be emitted
|
|
117
|
-
// because the resolved node led nowhere). Train and infer must fold
|
|
118
|
-
// identically; the way to guarantee that is to give this function nothing
|
|
119
|
-
// extra to say.
|
|
120
|
-
const cuts = new Set();
|
|
121
|
-
if (prev !== undefined) {
|
|
122
|
-
for (const b of prev.boundaries)
|
|
123
|
-
cuts.add(b);
|
|
124
|
-
cuts.add(prefixLen);
|
|
125
|
-
}
|
|
126
|
-
const boundaries = [...cuts].sort((a, b) => a - b);
|
|
127
|
-
const folded = stablePrefixFoldIncremental(ctx.space, ctx.alphabet, bytes, boundaries, prev?.stable);
|
|
128
|
-
const tree = folded.tree;
|
|
129
|
-
const entry = { boundaries, stable: folded.fold };
|
|
130
|
-
// Only a conversational deposit writes the cache too — otherwise a bare
|
|
131
|
-
// fact's plain fold could later be misread as a conversation's turn-zero
|
|
132
|
-
// boundary by an unrelated conversational deposit that happens to extend
|
|
133
|
-
// its bytes.
|
|
134
|
-
if (conversational && bytes.length >= 2) {
|
|
135
|
-
// The lengths set drifts as the map evicts; past the probe budget the
|
|
136
|
-
// drift itself becomes the cost (each stale length is an O(len) key
|
|
137
|
-
// build), so both reset together — losing only warm-up on live chains.
|
|
138
|
-
if (ctx._depositLens.size > 64) {
|
|
139
|
-
ctx._depositLens.clear();
|
|
140
|
-
ctx._depositTrees.clear();
|
|
141
|
-
}
|
|
142
|
-
ctx._depositTrees.set(latin1Key(bytes), entry);
|
|
143
|
-
ctx._depositLens.add(bytes.length);
|
|
144
|
-
}
|
|
145
|
-
return tree;
|
|
146
|
-
}
|
|
147
|
-
/** The raw bytes of an input — modality-neutral conversion. */
|
|
148
|
-
export function inputBytes(ctx, input) {
|
|
149
|
-
if (typeof input === "string")
|
|
150
|
-
return new TextEncoder().encode(input);
|
|
151
|
-
if (input instanceof Uint8Array)
|
|
152
|
-
return input;
|
|
153
|
-
if (Array.isArray(input))
|
|
154
|
-
return hilbertBytes(stackGrids(input));
|
|
155
|
-
return hilbertBytes(input);
|
|
156
|
-
}
|
|
157
|
-
/** Convenience: the gist vector of a byte span. */
|
|
158
|
-
export function gistOf(ctx, bytes) {
|
|
159
|
-
return perceive(ctx, bytes).v;
|
|
160
|
-
}
|
|
161
|
-
/** Fold a perceived tree bottom-up against the store's content-addressed maps:
|
|
162
|
-
* every leaf is named by findLeaf, every branch by findBranch over its kids'
|
|
163
|
-
* ids (null the moment any child is unknown). `visit`, when given, sees each
|
|
164
|
-
* node with its byte span and resolved id. Returns the node's byte end and
|
|
165
|
-
* resolved id. */
|
|
166
|
-
export function foldTree(ctx, n, start, visit) {
|
|
167
|
-
// Fast path: subtree already resolved (from a previous conversation turn
|
|
168
|
-
// or an earlier recognition pass). The pyramid reuses prefix subtrees as
|
|
169
|
-
// identical Sema objects, so this cache turns foldTree into O(suffix)
|
|
170
|
-
// instead of O(context) for multi-turn recognition.
|
|
171
|
-
const cached = ctx._resolvedSubtrees?.get(n);
|
|
172
|
-
if (cached !== undefined) {
|
|
173
|
-
const end = start + cached.len;
|
|
174
|
-
visit?.(n, start, end, cached.id);
|
|
175
|
-
return { end, node: cached.id };
|
|
176
|
-
}
|
|
177
|
-
if (n.kids === null) {
|
|
178
|
-
const b = n.leaf ?? new Uint8Array(0);
|
|
179
|
-
const end = start + b.length;
|
|
180
|
-
const node = ctx.store.findLeaf(b);
|
|
181
|
-
visit?.(n, start, end, node);
|
|
182
|
-
if (node !== null && ctx._resolvedSubtrees) {
|
|
183
|
-
ctx._resolvedSubtrees.set(n, { id: node, len: b.length });
|
|
184
|
-
}
|
|
185
|
-
return { end, node };
|
|
186
|
-
}
|
|
187
|
-
let pos = start;
|
|
188
|
-
let known = true;
|
|
189
|
-
const kids = [];
|
|
190
|
-
for (const k of n.kids) {
|
|
191
|
-
const r = foldTree(ctx, k, pos, visit);
|
|
192
|
-
if (r.node === null)
|
|
193
|
-
known = false;
|
|
194
|
-
else if (known)
|
|
195
|
-
kids.push(r.node);
|
|
196
|
-
pos = r.end;
|
|
197
|
-
}
|
|
198
|
-
const node = known ? ctx.store.findBranch(kids) : null;
|
|
199
|
-
visit?.(n, start, pos, node);
|
|
200
|
-
if (node !== null && ctx._resolvedSubtrees) {
|
|
201
|
-
ctx._resolvedSubtrees.set(n, { id: node, len: pos - start });
|
|
202
|
-
}
|
|
203
|
-
return { end: pos, node };
|
|
204
|
-
}
|
|
205
|
-
/** The canonical node id of a byte span: perceive it in isolation — the way
|
|
206
|
-
* training did — and recover its root bottom-up. Returns null if any part is
|
|
207
|
-
* unknown. */
|
|
208
|
-
export function resolve(ctx, bytes) {
|
|
209
|
-
if (bytes.length === 0)
|
|
210
|
-
return null;
|
|
211
|
-
if (ctx.meter)
|
|
212
|
-
ctx.meter.resolves++;
|
|
213
|
-
const exact = foldTree(ctx, perceive(ctx, bytes), 0).node;
|
|
214
|
-
if (exact !== null)
|
|
215
|
-
return exact;
|
|
216
|
-
return canonResolve(ctx, bytes);
|
|
217
|
-
}
|
|
218
|
-
/** Equivalence-class resolution: when the exact content-addressed lookup
|
|
219
|
-
* misses, find a stored node whose CANONICAL key equals the span's — the
|
|
220
|
-
* store's canon index proposes candidates by key hash, and each is verified
|
|
221
|
-
* by re-canonicalizing its bytes (hash-then-verify, like every content
|
|
222
|
-
* lookup). Among verified candidates, one that leads somewhere (has a
|
|
223
|
-
* continuation edge) is preferred; ties break to the lowest id — a corpus
|
|
224
|
-
* property, not a seed property. Null when the response carries no
|
|
225
|
-
* canonicalizer, the store has no canon index, or nothing verifies. */
|
|
226
|
-
export function canonResolve(ctx, bytes) {
|
|
227
|
-
const canon = ctx.canon;
|
|
228
|
-
const store = ctx.store;
|
|
229
|
-
if (canon === null || !store.canonFind)
|
|
230
|
-
return null;
|
|
231
|
-
if (bytes.length < 2)
|
|
232
|
-
return null;
|
|
233
|
-
const memo = ctx.canonMemo;
|
|
234
|
-
const memoKey = memo ? latin1Key(bytes) : "";
|
|
235
|
-
if (memo) {
|
|
236
|
-
const hit = memo.get(memoKey);
|
|
237
|
-
if (hit !== undefined)
|
|
238
|
-
return hit;
|
|
239
|
-
}
|
|
240
|
-
const set = (v) => {
|
|
241
|
-
memo?.set(memoKey, v);
|
|
242
|
-
return v;
|
|
243
|
-
};
|
|
244
|
-
const key = canon(bytes);
|
|
245
|
-
if (key.length === 0)
|
|
246
|
-
return set(null);
|
|
247
|
-
// A stored form that IS canonical is not in the index (buildCanonIndex
|
|
248
|
-
// skips identity rows) — the exact content-addressed lookup of the
|
|
249
|
-
// canonical bytes finds it directly.
|
|
250
|
-
if (key.length !== bytes.length || !bytesEqual(key, bytes)) {
|
|
251
|
-
const direct = foldTree(ctx, perceive(ctx, key), 0).node;
|
|
252
|
-
if (direct !== null)
|
|
253
|
-
return set(direct);
|
|
254
|
-
}
|
|
255
|
-
if (ctx.meter)
|
|
256
|
-
ctx.meter.canonLookups++;
|
|
257
|
-
const candidates = store.canonFind(canonHash(key));
|
|
258
|
-
if (candidates.length === 0)
|
|
259
|
-
return set(null);
|
|
260
|
-
let best = null;
|
|
261
|
-
let bestLeads = false;
|
|
262
|
-
for (const id of candidates) {
|
|
263
|
-
const bytesOf = read(ctx, id);
|
|
264
|
-
const stored = canon(bytesOf);
|
|
265
|
-
if (stored.length !== key.length || !bytesEqual(stored, key))
|
|
266
|
-
continue;
|
|
267
|
-
// The index stores FLAT content twins; the id the exact path would have
|
|
268
|
-
// resolved for these bytes is their FOLD — the deposit-shaped node that
|
|
269
|
-
// carries the edges and halos. Re-folding the candidate's bytes lands
|
|
270
|
-
// on exactly the node the canonical-case query would have found.
|
|
271
|
-
const folded = foldTree(ctx, perceive(ctx, bytesOf), 0).node;
|
|
272
|
-
const use = folded ?? id;
|
|
273
|
-
const leads = store.hasNext(use) || store.haloMass(use) > 0;
|
|
274
|
-
if (best === null || (leads && !bestLeads) ||
|
|
275
|
-
(leads === bestLeads && use < best)) {
|
|
276
|
-
best = use;
|
|
277
|
-
bestLeads = leads;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return set(best);
|
|
281
|
-
}
|
|
282
|
-
/** Walk a perceived tree in POST-ORDER with byte offsets — children before
|
|
283
|
-
* their parent, `visit(node, start, end)` for every node including leaves.
|
|
284
|
-
* Returns the byte end. The one shared traversal the offset-carrying tree
|
|
285
|
-
* readers (recognition via foldTree's richer variant, attention's region
|
|
286
|
-
* collection, resonance's branch counting) build on, so each does not
|
|
287
|
-
* re-derive the offset bookkeeping. (recognition.segment keeps its own
|
|
288
|
-
* walk: its flush semantics need PRE-order decisions at leaf-parents, which
|
|
289
|
-
* a post-order visitor cannot express.) */
|
|
290
|
-
export function walkTree(n, start, visit) {
|
|
291
|
-
if (n.kids === null) {
|
|
292
|
-
const end = start + (n.leaf?.length ?? 0);
|
|
293
|
-
visit(n, start, end);
|
|
294
|
-
return end;
|
|
295
|
-
}
|
|
296
|
-
let pos = start;
|
|
297
|
-
for (const k of n.kids)
|
|
298
|
-
pos = walkTree(k, pos, visit);
|
|
299
|
-
visit(n, start, pos);
|
|
300
|
-
return pos;
|
|
301
|
-
}
|
|
302
|
-
// ── Read: node → bytes ──────────────────────────────────────────────────
|
|
303
|
-
/** Reconstruct a node's byte content from the DAG, up to `maxLen` bytes. */
|
|
304
|
-
export function read(ctx, id, maxLen = ALL) {
|
|
305
|
-
return ctx.store.bytesPrefix(id, maxLen);
|
|
306
|
-
}
|