@hviana/sema 0.5.2 → 0.5.4
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 +115 -53
- package/HOW_IT_WORKS.md +291 -184
- package/jsr.json +6 -0
- package/package.json +1 -1
- package/src/mind/bridge.ts +10 -104
- package/src/mind/match.ts +497 -7
- package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
- package/src/mind/mechanisms/recall.ts +38 -129
- 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 +543 -0
- package/dist/example/demo.d.ts +0 -1
- package/dist/example/demo.js +0 -39
- package/dist/example/train_base.d.ts +0 -93
- package/dist/example/train_base.js +0 -2269
- 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 -45
- package/dist/src/canon.js +0 -85
- 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 -233
- package/dist/src/geometry.js +0 -1089
- 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 -165
- package/dist/src/meter.d.ts +0 -176
- package/dist/src/meter.js +0 -274
- 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 -430
- package/dist/src/mind/attention.js +0 -2418
- package/dist/src/mind/bridge.d.ts +0 -39
- package/dist/src/mind/bridge.js +0 -998
- package/dist/src/mind/canonical.d.ts +0 -34
- package/dist/src/mind/canonical.js +0 -93
- package/dist/src/mind/frame-filler.d.ts +0 -15
- package/dist/src/mind/frame-filler.js +0 -535
- 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 -137
- package/dist/src/mind/junction.js +0 -358
- package/dist/src/mind/learning.d.ts +0 -75
- package/dist/src/mind/learning.js +0 -265
- package/dist/src/mind/match.d.ts +0 -192
- package/dist/src/mind/match.js +0 -666
- 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 -1186
- package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
- package/dist/src/mind/mechanisms/confluence.js +0 -271
- package/dist/src/mind/mechanisms/cover.d.ts +0 -6
- package/dist/src/mind/mechanisms/cover.js +0 -232
- package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
- package/dist/src/mind/mechanisms/extraction.js +0 -327
- package/dist/src/mind/mechanisms/recall.d.ts +0 -16
- package/dist/src/mind/mechanisms/recall.js +0 -558
- package/dist/src/mind/mind.d.ts +0 -386
- package/dist/src/mind/mind.js +0 -754
- package/dist/src/mind/pipeline-mechanism.d.ts +0 -183
- package/dist/src/mind/pipeline-mechanism.js +0 -521
- package/dist/src/mind/pipeline.d.ts +0 -49
- package/dist/src/mind/pipeline.js +0 -331
- package/dist/src/mind/prefix-completion.d.ts +0 -59
- package/dist/src/mind/prefix-completion.js +0 -270
- package/dist/src/mind/primitives.d.ts +0 -85
- package/dist/src/mind/primitives.js +0 -333
- 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 -42
- package/dist/src/mind/reasoning.js +0 -305
- package/dist/src/mind/recognition.d.ts +0 -20
- package/dist/src/mind/recognition.js +0 -696
- package/dist/src/mind/resonance.d.ts +0 -35
- package/dist/src/mind/resonance.js +0 -365
- 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 -158
- package/dist/src/mind/traverse.js +0 -716
- package/dist/src/mind/types.d.ts +0 -418
- package/dist/src/mind/types.js +0 -179
- 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 -313
- package/dist/src/sema.d.ts +0 -41
- package/dist/src/sema.js +0 -77
- package/dist/src/store-sqlite.d.ts +0 -184
- package/dist/src/store-sqlite.js +0 -942
- package/dist/src/store.d.ts +0 -762
- package/dist/src/store.js +0 -1823
- package/dist/src/vec.d.ts +0 -31
- package/dist/src/vec.js +0 -109
- package/src/mind/frame-filler.ts +0 -604
- package/test/69-frame-filler.test.mjs +0 -115
|
@@ -1,331 +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, read, 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 { hubBound } from "./traverse.js";
|
|
18
|
-
import { Precomputed } from "./pipeline-mechanism.js";
|
|
19
|
-
import { coverMechanism } from "./mechanisms/cover.js";
|
|
20
|
-
import { castMechanism } from "./mechanisms/cast.js";
|
|
21
|
-
import { confluenceMechanism } from "./mechanisms/confluence.js";
|
|
22
|
-
import { extractionMechanism } from "./mechanisms/extraction.js";
|
|
23
|
-
import { recallMechanism } from "./mechanisms/recall.js";
|
|
24
|
-
// Re-exports: cover's pre-resolution helpers and the ALU adapter kept
|
|
25
|
-
// importable from the pipeline module (their historical home).
|
|
26
|
-
export { resolveConcepts, resolveConnectors } from "./mechanisms/cover.js";
|
|
27
|
-
export { aluToMechanism } from "./mechanisms/alu.js";
|
|
28
|
-
// ── Extension dispatch (pre-loop parse) ─────────────────────────────────────
|
|
29
|
-
async function collectComputed(ctx, mechanisms, query) {
|
|
30
|
-
const out = [];
|
|
31
|
-
const meter = ctx.meter;
|
|
32
|
-
for (const m of mechanisms) {
|
|
33
|
-
if (!m.parse)
|
|
34
|
-
continue;
|
|
35
|
-
const spans = meter
|
|
36
|
-
? await meter.time(`${m.name}.parse`, () => m.parse(query))
|
|
37
|
-
: await m.parse(query);
|
|
38
|
-
out.push(...spans);
|
|
39
|
-
}
|
|
40
|
-
return out;
|
|
41
|
-
}
|
|
42
|
-
// ── Built-in mechanisms ─────────────────────────────────────────────────────
|
|
43
|
-
// ORDER MATTERS, but only through the uniform floor/worthRunning pruning —
|
|
44
|
-
// no mechanism is special-cased. Cover runs FIRST: when a computed
|
|
45
|
-
// extension result (e.g. ALU) exists, cover masks it in at near-zero cost
|
|
46
|
-
// (see mechanisms/cover.ts), which becomes `best` before any other mechanism
|
|
47
|
-
// invests in its own precomputation. CAST's and confluence's floors (2*STEP,
|
|
48
|
-
// 3*STEP) then fail `worthRunning` and are skipped by the SAME admissible-
|
|
49
|
-
// floor pruning every mechanism is already subject to — not by asking
|
|
50
|
-
// "is this an extension?". Grade TIES keep the earlier candidate, so this
|
|
51
|
-
// order is also the tie-break priority: cover, cast, confluence, extraction,
|
|
52
|
-
// recall.
|
|
53
|
-
export const defaultMechanisms = [
|
|
54
|
-
coverMechanism,
|
|
55
|
-
castMechanism,
|
|
56
|
-
confluenceMechanism,
|
|
57
|
-
extractionMechanism,
|
|
58
|
-
recallMechanism,
|
|
59
|
-
];
|
|
60
|
-
/** Think: a single lightest-derivation exploration of the Sema graph.
|
|
61
|
-
*
|
|
62
|
-
* Every answer travels the same path:
|
|
63
|
-
* 1. Pre-computation — recognise, extension parse, guide; everything
|
|
64
|
-
* expensive stays lazy on Precomputed until a mechanism asks.
|
|
65
|
-
* 2. Grounding — every mechanism yields candidates weighed in the one
|
|
66
|
-
* cost ladder; the lightest grounding derivation wins.
|
|
67
|
-
* 3. Post-grounding — diagnostics (narrowDecision, thinGrounding),
|
|
68
|
-
* reasoning (multi-hop), fusion (multi-topic). */
|
|
69
|
-
export async function think(ctx, query, mechs) {
|
|
70
|
-
if (query.length === 0)
|
|
71
|
-
return null;
|
|
72
|
-
ctx._edgeGuide = gistOf(ctx, query);
|
|
73
|
-
ctx._edgeChoice.clear();
|
|
74
|
-
const t = ctx.trace?.enter("think", [rItem(query, "query")]);
|
|
75
|
-
const done = (answer, note) => {
|
|
76
|
-
t?.done(answer
|
|
77
|
-
? [rItem(answer, "answer", resolve(ctx, answer) ?? undefined)]
|
|
78
|
-
: [], note);
|
|
79
|
-
return answer;
|
|
80
|
-
};
|
|
81
|
-
// ── Pre-computation ──────────────────────────────────────────────────
|
|
82
|
-
const mechanisms = mechs ?? defaultMechanisms;
|
|
83
|
-
const rec = recognise(ctx, query);
|
|
84
|
-
// Phase 1: collect computed spans from mechanisms that implement parse()
|
|
85
|
-
const computed = await collectComputed(ctx, mechanisms, query);
|
|
86
|
-
if (computed.length > 0) {
|
|
87
|
-
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)`);
|
|
88
|
-
for (const u of computed) {
|
|
89
|
-
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");
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
// Phase 2: the shared pre-computation container. Eager fields only
|
|
93
|
-
// (recognition, computed spans, guide) — every expensive analysis
|
|
94
|
-
// (consensus climb, weave, span-shape classification) is a lazily-cached
|
|
95
|
-
// method on Precomputed, first-touched by whichever mechanism's floor
|
|
96
|
-
// survives its cheap gates and the worthRunning check. A query no
|
|
97
|
-
// mechanism climbs for (e.g. one an extension decided) never climbs.
|
|
98
|
-
const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
|
|
99
|
-
const grade = (w) => Math.floor(w / STEP);
|
|
100
|
-
const unaccounted = (spans) => unexplainedSpans(query.length, spans)
|
|
101
|
-
.reduce((sum, [s, e]) => sum + (e - s), 0);
|
|
102
|
-
const weigh = (accounted, moves) => moves + PASS * unaccounted(accounted);
|
|
103
|
-
const candidates = [];
|
|
104
|
-
let best = null;
|
|
105
|
-
const consider = (c) => {
|
|
106
|
-
if (c.bytes.length === 0)
|
|
107
|
-
return;
|
|
108
|
-
if (ctx.meter)
|
|
109
|
-
ctx.meter.candidates++;
|
|
110
|
-
candidates.push(c);
|
|
111
|
-
if (best === null) {
|
|
112
|
-
best = c;
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const g = grade(c.weight), gb = grade(best.weight);
|
|
116
|
-
if (g < gb) {
|
|
117
|
-
best = c;
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
// TIE-BREAK: AT EQUAL GRADE, PREFER THE ANSWER THAT INVENTS LESS.
|
|
121
|
-
//
|
|
122
|
-
// The ladder prices what a candidate leaves UNACCOUNTED, which is the
|
|
123
|
-
// right primary question but cannot separate two candidates that leave
|
|
124
|
-
// the same bytes unaccounted — and then the winner is whichever mechanism
|
|
125
|
-
// happened to be considered first, which is not a reason.
|
|
126
|
-
//
|
|
127
|
-
// What still separates them is what they DID with those bytes. A
|
|
128
|
-
// candidate that carries an unexplained span into its answer is passing
|
|
129
|
-
// the asker's own words back as if they were derived; one that leaves
|
|
130
|
-
// them out has made a smaller, honest claim. Measured on test/22's
|
|
131
|
-
// two-fact chain: cover and recall both graded 11001 over 11 unexplained
|
|
132
|
-
// bytes, cover answering "The capital of France is Paris famous for" (11
|
|
133
|
-
// bytes of scaffolding) against recall's crossing of the hop (0). Order
|
|
134
|
-
// alone decided it, and the shallower reading won.
|
|
135
|
-
//
|
|
136
|
-
// This never overrides the ladder — it only orders within one grade, so
|
|
137
|
-
// coverage and moves still dominate exactly as before.
|
|
138
|
-
if (g === gb && (c.scaffolding ?? 0) < (best.scaffolding ?? 0))
|
|
139
|
-
best = c;
|
|
140
|
-
};
|
|
141
|
-
const worthRunning = (floor) => best === null || grade(floor) < grade(best.weight);
|
|
142
|
-
// Phase 3: grounding loop
|
|
143
|
-
// Per-mechanism accounting (src/meter.ts). The market's whole premise is
|
|
144
|
-
// that mechanisms compete on one cost scale — so the profiling read-out is
|
|
145
|
-
// also per-mechanism, uniformly: the loop never asks which one it holds.
|
|
146
|
-
const meter = ctx.meter;
|
|
147
|
-
for (const mech of mechanisms) {
|
|
148
|
-
const floor = meter
|
|
149
|
-
? await meter.time(`${mech.name}.floor`, () => mech.floor(ctx, query, pre, worthRunning))
|
|
150
|
-
: await mech.floor(ctx, query, pre, worthRunning);
|
|
151
|
-
if (meter) {
|
|
152
|
-
if (floor === null)
|
|
153
|
-
meter.mechanismSkips++;
|
|
154
|
-
else
|
|
155
|
-
meter.mechanismFloors++;
|
|
156
|
-
}
|
|
157
|
-
if (floor === null) {
|
|
158
|
-
ctx.trace?.step("skipMechanism", [], [], `${mech.name} skipped — structural precondition failed`);
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
if (!worthRunning(floor)) {
|
|
162
|
-
ctx.trace?.step("skipMechanism", [], [], `${mech.name} skipped — floor ${floor} cannot beat incumbent (grade ${grade(best.weight)})`);
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (meter)
|
|
166
|
-
meter.mechanismRuns++;
|
|
167
|
-
const results = meter
|
|
168
|
-
? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
|
|
169
|
-
: await mech.run(ctx, query, pre);
|
|
170
|
-
for (const r of results) {
|
|
171
|
-
const weight = r.weight ?? weigh(r.accounted, r.moves);
|
|
172
|
-
consider({
|
|
173
|
-
bytes: r.bytes,
|
|
174
|
-
provenance: r.provenance ?? mech.provenance,
|
|
175
|
-
weight,
|
|
176
|
-
used: r.used,
|
|
177
|
-
accounted: r.accounted,
|
|
178
|
-
unexplained: r.unexplained,
|
|
179
|
-
complete: r.complete,
|
|
180
|
-
scaffolding: r.scaffolding,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
// (TS cannot see the closure assignments into `best` and narrows it to its
|
|
185
|
-
// initial null, so the read-back needs the assertion.)
|
|
186
|
-
const decided = best;
|
|
187
|
-
if (candidates.length > 1) {
|
|
188
|
-
// The runner-up is computed BEFORE the decideGrounding step so its grade
|
|
189
|
-
// margin can ride along in the step's structured data payload; the
|
|
190
|
-
// computation itself is pure and was always unconditional — only its
|
|
191
|
-
// position moved.
|
|
192
|
-
let runnerUp = null;
|
|
193
|
-
if (decided !== null) {
|
|
194
|
-
for (const c of candidates) {
|
|
195
|
-
if (c === decided)
|
|
196
|
-
continue;
|
|
197
|
-
if (runnerUp === null || grade(c.weight) < grade(runnerUp.weight)) {
|
|
198
|
-
runnerUp = c;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
const margin = decided !== null && runnerUp !== null
|
|
203
|
-
? grade(runnerUp.weight) - grade(decided.weight)
|
|
204
|
-
: null;
|
|
205
|
-
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, {
|
|
206
|
-
version: 1,
|
|
207
|
-
candidates: candidates.map((c) => ({
|
|
208
|
-
provenance: c.provenance,
|
|
209
|
-
weight: c.weight,
|
|
210
|
-
grade: grade(c.weight),
|
|
211
|
-
unexplainedBytes: unaccounted(c.accounted),
|
|
212
|
-
decided: c === decided,
|
|
213
|
-
})),
|
|
214
|
-
...(margin !== null ? { runnerUpMargin: margin } : {}),
|
|
215
|
-
});
|
|
216
|
-
if (decided !== null && runnerUp !== null && margin !== null) {
|
|
217
|
-
if (margin <= 1) {
|
|
218
|
-
ctx.trace?.step("narrowDecision", [
|
|
219
|
-
rItem(decided.bytes, `${decided.provenance} (weight ${decided.weight.toFixed(3)})`),
|
|
220
|
-
], [
|
|
221
|
-
rItem(runnerUp.bytes, `${runnerUp.provenance} (weight ${runnerUp.weight.toFixed(3)})`),
|
|
222
|
-
], `margin ${margin} grade-unit(s) — the decision could change with one more training fact`, undefined, { version: 1, margin });
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
if (decided === null) {
|
|
227
|
-
done(null, "no mechanism grounded an answer");
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
// Honesty density
|
|
231
|
-
{
|
|
232
|
-
const covered = query.length - unaccounted(decided.accounted);
|
|
233
|
-
const density = query.length > 0 ? covered / query.length : 1;
|
|
234
|
-
const thinBar = 1 / ctx.space.maxGroup;
|
|
235
|
-
if (density < thinBar) {
|
|
236
|
-
ctx.trace?.step("thinGrounding", [rItem(decided.bytes, decided.provenance)], [], `grounded but thin — density ${density.toFixed(3)} is below 1/W (${thinBar.toFixed(3)})`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
const answer = decided.bytes;
|
|
240
|
-
const provenance = decided.provenance;
|
|
241
|
-
const castUsed = decided.used ?? new Set();
|
|
242
|
-
// ── Post-grounding, gated by provenance ──────────────────────────────
|
|
243
|
-
const preConsumed = provenance === "cast" || provenance === "join"
|
|
244
|
-
? castUsed
|
|
245
|
-
: provenance === "recall" || provenance === "recall-echo"
|
|
246
|
-
? new Set()
|
|
247
|
-
: new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
248
|
-
// A grounding that DECLARED itself complete is not extended: the answer is
|
|
249
|
-
// already a trained form's own continuation, reached through an identity
|
|
250
|
-
// claim about the query, so a multi-hop pivot could only chain past the
|
|
251
|
-
// fact that produced it (see MechanismResult.complete).
|
|
252
|
-
// WHAT THE MECHANISM WITHHELD, NOT WHAT IT VOICED. A pivot must not
|
|
253
|
-
// re-open content a grounding deliberately kept out: comparison cites two
|
|
254
|
-
// analogs and refuses their own downstream facts, so pivoting into one is
|
|
255
|
-
// the mechanism's own refusal undone one step later (test/29 C2 pivoted
|
|
256
|
-
// through `speare` — a stored fragment of the analog `William Shakespeare`
|
|
257
|
-
// — into the biography CAST had declined).
|
|
258
|
-
//
|
|
259
|
-
// Reading the used anchors' OWN bytes here says something stronger and
|
|
260
|
-
// wrong: that nothing INSIDE what was voiced may be pivoted through. A
|
|
261
|
-
// comparison's seat sentence legitimately contains further terms with
|
|
262
|
-
// their own unrelated facts, and C3 pins exactly that — `Mona Lisa`, inside
|
|
263
|
-
// the voiced seat `The Mona Lisa was painted by Leonardo da Vinci.`, leads
|
|
264
|
-
// on to `Mona Lisa hangs in the Louvre`, which is about neither analog.
|
|
265
|
-
// The withheld content is the used anchors' CONTINUATIONS, so that is what
|
|
266
|
-
// the containment rule reads: `speare` is contained in `Shakespeare wrote
|
|
267
|
-
// 39 plays` and stays refused, while `Mona Lisa` appears in no withheld
|
|
268
|
-
// continuation and the genuine further hop fires.
|
|
269
|
-
//
|
|
270
|
-
// Only a mechanism carrying its own `used` set (cast/join) gets this: there
|
|
271
|
-
// `preConsumed` is a deliberate, short list of the anchors the answer
|
|
272
|
-
// speaks for, so the fan-out is bounded. For every other provenance
|
|
273
|
-
// `preConsumed` is derived by re-recognising the answer — "everything in
|
|
274
|
-
// it", not "what it voiced" — and a containment rule over that would
|
|
275
|
-
// suppress every pivot the answer legitimately contains.
|
|
276
|
-
const voiced = (provenance === "cast" || provenance === "join")
|
|
277
|
-
? [...castUsed].flatMap((id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)))
|
|
278
|
-
: [];
|
|
279
|
-
const reasoned = decided.complete ? answer : meter
|
|
280
|
-
? await meter.time("reason", () => reason(ctx, query, answer, preConsumed, pre, voiced))
|
|
281
|
-
: await reason(ctx, query, answer, preConsumed, pre, voiced);
|
|
282
|
-
// Fuse only when the query has a genuine REMAINDER no mechanism's
|
|
283
|
-
// structural evidence touched at all. `decided.accounted` alone
|
|
284
|
-
// undercounts this: it is a COST-LADDER quantity (cover.ts prices its
|
|
285
|
-
// masked/computed spans at near-zero and deliberately leaves them out of
|
|
286
|
-
// `accounted` so PASS-bridged bytes are still charged), not a coverage
|
|
287
|
-
// one — a query fully explained by one computed span plus bridged
|
|
288
|
-
// connectors can report `accounted: []` while nothing is actually left
|
|
289
|
-
// unexplained. The genuine remainder is what NEITHER the winning
|
|
290
|
-
// candidate's accounted spans NOR any recognised extension's computed
|
|
291
|
-
// span (`pre.computed` — every mechanism's parse() output, ALU included)
|
|
292
|
-
// ever touched. A remainder under one river-fold quantum (W, the same
|
|
293
|
-
// floor cover.ts's restatedSpan and the honesty-density bar above both
|
|
294
|
-
// use) is bridging punctuation/whitespace, never a second topic —
|
|
295
|
-
// observed: a single space between two fully-computed arithmetic spans
|
|
296
|
-
// ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
|
|
297
|
-
// corpus fact, corrupting "4 6" into "4 63".
|
|
298
|
-
const explained = [
|
|
299
|
-
...decided.accounted,
|
|
300
|
-
...pre.computed.map((u) => [u.i, u.j]),
|
|
301
|
-
];
|
|
302
|
-
const remainder = unaccounted(explained);
|
|
303
|
-
// Whether the winning candidate's entire recognised substance is
|
|
304
|
-
// COMPUTED — every accounted span exactly a pre.computed span, nothing
|
|
305
|
-
// from a genuinely recognised/climbed site. fuseAttention's lone-root
|
|
306
|
-
// shortcut assumes a single point of attention already IS primary's own
|
|
307
|
-
// source; that assumption is exactly backwards for a pure computation
|
|
308
|
-
// (an ALU result has no anchor of its own) — see fuseAttention's
|
|
309
|
-
// `unclimbed` parameter, gated there by Attention.breadth so a
|
|
310
|
-
// coincidental echo (which this flag alone cannot distinguish) is still
|
|
311
|
-
// rejected.
|
|
312
|
-
const unclimbed = decided.accounted.length > 0 &&
|
|
313
|
-
decided.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
|
|
314
|
-
// Where the winning grounding stands in the query — fusion places primary
|
|
315
|
-
// by it (see fuseAttention's `primarySpans`). `accounted` is the
|
|
316
|
-
// cost-ladder read and is authoritative when non-empty; when it is empty
|
|
317
|
-
// the grounding is a pure COMPUTATION, whose evidence is its computed span.
|
|
318
|
-
// Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
|
|
319
|
-
// read here for POSITION instead of for coverage — and resolved here, where
|
|
320
|
-
// both readings are in hand, rather than inside fuseAttention.
|
|
321
|
-
const primarySpans = decided.accounted.length > 0
|
|
322
|
-
? decided.accounted
|
|
323
|
-
: pre.computed.map((u) => [u.i, u.j]);
|
|
324
|
-
const fused = remainder < ctx.space.maxGroup
|
|
325
|
-
? reasoned
|
|
326
|
-
: meter
|
|
327
|
-
? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
|
|
328
|
-
: await fuseAttention(ctx, query, reasoned, pre, unclimbed, decided.accounted);
|
|
329
|
-
done(fused, "grounded, reasoned forward, fused across points of attention");
|
|
330
|
-
return { bytes: fused, provenance };
|
|
331
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { MindContext } from "./types.js";
|
|
2
|
-
/** Trained forms the query may OPEN, proposed from the write side's own
|
|
3
|
-
* leaf-id window index — the supply of last resort for {@link
|
|
4
|
-
* prefixCompletion}.
|
|
5
|
-
*
|
|
6
|
-
* WHY A SECOND SUPPLY EXISTS. The ranked list this mechanism normally reads
|
|
7
|
-
* is a resonance list, and resonance cannot rank a proper prefix: measured on
|
|
8
|
-
* the trained store, cos(prefix, form) falls from 0.9629 at a one-byte
|
|
9
|
-
* truncation to 0.6206 at three bytes, against a reachThreshold of 0.8750.
|
|
10
|
-
* Three bytes of truncation put the answer out of reach on GEOMETRY, not on a
|
|
11
|
-
* bug, so no k and no re-ranking recovers it.
|
|
12
|
-
*
|
|
13
|
-
* WHY THIS ROUTE WORKS WHERE THE FOLD DOES NOT. A query's own fold is
|
|
14
|
-
* useless here: content addressing is not phrase-position-invariant, so a
|
|
15
|
-
* standalone prefix folds to a DIFFERENT node than the same bytes sitting
|
|
16
|
-
* inside a longer deposit, and neither the prefix's own node nor its
|
|
17
|
-
* ancestors lead to the deposit (measured: the 22-byte prefix of the
|
|
18
|
-
* photosynthesis form resolves, is shared by 6 contexts, and does not have
|
|
19
|
-
* the form among its ancestors). Leaf ids ARE position-invariant — they are
|
|
20
|
-
* content-addressed on single bytes — and `indexSubSpans` already interns a
|
|
21
|
-
* flat branch over every canonical WINDOW of a deposit's leaf-id stream, with
|
|
22
|
-
* containment edges to the chunks that window spans. A query that is a
|
|
23
|
-
* prefix therefore shares those window nodes exactly, and reaches the deposit
|
|
24
|
-
* by climbing containment then parents. Nothing is added to the write side;
|
|
25
|
-
* this reads an index training already built.
|
|
26
|
-
*
|
|
27
|
-
* BOUNDED (§2.8), AND WITH NO NEW THRESHOLD. The window whose containment is
|
|
28
|
-
* SMALLEST carries the most evidence, and one saturated at `hubBound` carries
|
|
29
|
-
* none — that is the same √N reading of "hub" the rest of the mind uses, not
|
|
30
|
-
* a tuned knob. The upward walk spends a budget of `hubBound` nodes and
|
|
31
|
-
* fans out by W, so a hub query enumerates nothing and the caller stays
|
|
32
|
-
* silent rather than guessing (§2.13). Measured on the trained store: the
|
|
33
|
-
* photosynthesis form at a one-byte truncation picks a window with 52
|
|
34
|
-
* containers, visits 446 nodes, and yields exactly ONE candidate that
|
|
35
|
-
* survives the caller's byte compare — the form itself.
|
|
36
|
-
*
|
|
37
|
-
* These are PROPOSALS only. Every candidate still faces the byte-exact
|
|
38
|
-
* prefix compare and all three guards below, so a wrong proposal costs one
|
|
39
|
-
* bounded read and can never be voiced (§2.3). */
|
|
40
|
-
export declare function prefixCandidates(ctx: MindContext, query: Uint8Array): number[];
|
|
41
|
-
/** A trained form the query opens, and the bytes by which it continues. */
|
|
42
|
-
export interface PrefixCompletion {
|
|
43
|
-
/** The trained form whose opening the query is — the answer, voiced whole. */
|
|
44
|
-
id: number;
|
|
45
|
-
/** The form's own bytes. The mechanism grounds a FORM, never a slice of
|
|
46
|
-
* one: slicing at the query's end would cut at an offset the geometry has
|
|
47
|
-
* no reason to treat as a boundary. */
|
|
48
|
-
form: Uint8Array;
|
|
49
|
-
/** The bytes past the query — carried for the rationale and for the
|
|
50
|
-
* uniqueness comparison, not voiced on its own. */
|
|
51
|
-
continuation: Uint8Array;
|
|
52
|
-
}
|
|
53
|
-
/** The sole trained form the query opens — or null when no candidate opens with
|
|
54
|
-
* it, when the continuation is sub-quantum, when a candidate's continuation
|
|
55
|
-
* cannot be read through, or when the candidates disagree.
|
|
56
|
-
*
|
|
57
|
-
* `ranked` must be a list the caller has ALREADY fetched; this mechanism never
|
|
58
|
-
* resonates on its own (see the header's cost note). */
|
|
59
|
-
export declare function prefixCompletion(ctx: MindContext, query: Uint8Array, ranked: ReadonlyArray<number>): PrefixCompletion | null;
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
// prefix-completion.ts — Grounding a query that IS the opening of a trained
|
|
2
|
-
// form.
|
|
3
|
-
//
|
|
4
|
-
// THE SHAPE. `The capital of France is` grounds nothing, while
|
|
5
|
-
// `The capital of France is Paris.` is trained and reads back byte-exact. The
|
|
6
|
-
// query is not SIMILAR to that form, it is a PROPER PREFIX of it: every query
|
|
7
|
-
// byte is a literal match, in order, from offset zero. That is the strongest
|
|
8
|
-
// grounding relation in the store — stronger than the bridge's corroborated
|
|
9
|
-
// substitution, which pays a CONCEPT per substituted span, and stronger than
|
|
10
|
-
// resonance, which only claims an angle. Nothing is invented: the answer IS a
|
|
11
|
-
// trained form, voiced whole.
|
|
12
|
-
//
|
|
13
|
-
// NO NOTION OF TEXT. This mechanism reads bytes and geometry only. It has no
|
|
14
|
-
// separator, no character class, no "word": the only structural quantity it
|
|
15
|
-
// uses is W, the river's grouping window, which is the same capacity the
|
|
16
|
-
// perception tree groups by and the same bar the argument-binding tier holds
|
|
17
|
-
// its constituents to. A completion shorter than one grouping window carries
|
|
18
|
-
// no structure the geometry can perceive, whatever the modality — that is a
|
|
19
|
-
// statement about the fold, not about punctuation. Presentation (what is
|
|
20
|
-
// "spacing", what is "case") belongs to the injected canon and to the modality
|
|
21
|
-
// entry point, never here; see src/canon.ts.
|
|
22
|
-
//
|
|
23
|
-
// WHY THE EARLIER TIERS CANNOT DO IT. Two independent reasons, both measured:
|
|
24
|
-
//
|
|
25
|
-
// 1. `resolve(prefix)` is null. A proper prefix of a deposited stream has no
|
|
26
|
-
// branch of its own unless it was itself deposited, so the exact tiers
|
|
27
|
-
// have nothing to find.
|
|
28
|
-
// 2. The form is not among the resonance candidates AT ALL. Measured on the
|
|
29
|
-
// trained store: cos(query, that form) = 0.5752, yet the form is absent
|
|
30
|
-
// from `resonate(k)` at k = 24, 256 AND 2048 — while forms scoring LOWER
|
|
31
|
-
// (Germany 0.5670, Yemen 0.5591) are returned. `k` only reorders WITHIN
|
|
32
|
-
// the IVF clusters already probed, exactly as Store.resonate's doc warns,
|
|
33
|
-
// so no k recovers it. With `exhaustive` it ranks 8.
|
|
34
|
-
//
|
|
35
|
-
// So this is a RETRIEVABILITY gap, not a semantic one, and it is repaired by
|
|
36
|
-
// reading the candidate list recall's refusal path has ALREADY fetched
|
|
37
|
-
// exhaustively for the substitution bridge — never by resonating on its own.
|
|
38
|
-
// Measured cost of the scan over those 570 candidates: 2.9 ms warm, 20.4 ms
|
|
39
|
-
// cold, against a ~700 ms refusal path. Issuing a FRESH exhaustive call would
|
|
40
|
-
// cost 490 ms median against 13 ms non-exhaustive (36×), which is why this tier
|
|
41
|
-
// takes the candidate list as an argument and adds nothing to it.
|
|
42
|
-
//
|
|
43
|
-
// THREE GUARDS, each falsified into existence by measurement — do not drop any:
|
|
44
|
-
//
|
|
45
|
-
// 1. AN UNREADABLE CONTINUATION VETOES. Reads are bounded (a stored span can
|
|
46
|
-
// run to hundreds of kilobytes), so a candidate that opens with the query
|
|
47
|
-
// but SATURATES the read continues in a way nobody can see. It is a
|
|
48
|
-
// standing disagreement: if any such candidate exists, nothing is grounded.
|
|
49
|
-
// It must NOT be quietly skipped, and that is not a stylistic point — the
|
|
50
|
-
// skip is what MANUFACTURES a fragment. Measured on a one-deposit fixture
|
|
51
|
-
// whose form exceeds the cap: the query matched BOTH the whole 138-byte
|
|
52
|
-
// form (saturating) AND an interior fold node of 34 bytes (unsaturated,
|
|
53
|
-
// continuing `" Paris, an"`). Skipping the saturated candidate removed the
|
|
54
|
-
// only evidence that disagreed, uniqueness then passed on the interior
|
|
55
|
-
// node, and a mid-form slice was voiced as an answer. Suppressing the
|
|
56
|
-
// disagreement is what created the fabrication.
|
|
57
|
-
// (Testing instead whether a candidate is a "complete form" via the fold
|
|
58
|
-
// does NOT work and was measured: content addressing makes an interior
|
|
59
|
-
// node resolve to ITSELF, so self-resolution says nothing about
|
|
60
|
-
// completeness.)
|
|
61
|
-
// 2. THE CONTINUATION MUST REACH ONE GROUPING WINDOW. A trained
|
|
62
|
-
// `What is the capital of France??` opens with `What is the capital of
|
|
63
|
-
// France?` and continues by a single byte. Below W the continuation is
|
|
64
|
-
// sub-quantum — the fold groups nothing from it — and voicing it produces
|
|
65
|
-
// the degenerate reply that is a known failure smell.
|
|
66
|
-
// 3. UNIQUENESS. Several trained forms may open with the query and continue
|
|
67
|
-
// differently, and then the corpus does not say which continuation the
|
|
68
|
-
// asker means. Distinct continuations ⇒ refuse. This is the documented
|
|
69
|
-
// PREFIX TRAP, and it is real — just not for every prefix. Measured: of
|
|
70
|
-
// 15 battery probes exactly ONE yields a unique continuation, and all
|
|
71
|
-
// three honest-silence probes yield none (including `What is the capital
|
|
72
|
-
// of Zamunda?`, whose top hit scores 0.83).
|
|
73
|
-
//
|
|
74
|
-
// Uniqueness is judged on the continuation BYTES, not on the candidate id: the
|
|
75
|
-
// same continuation reached through two trained forms is one answer, not an
|
|
76
|
-
// ambiguity.
|
|
77
|
-
import { bytesEqual } from "../bytes.js";
|
|
78
|
-
import { rItem } from "./trace.js";
|
|
79
|
-
import { canonicalWindows, leafIdPrefix } from "./canonical.js";
|
|
80
|
-
import { hubBound } from "./traverse.js";
|
|
81
|
-
/** Trained forms the query may OPEN, proposed from the write side's own
|
|
82
|
-
* leaf-id window index — the supply of last resort for {@link
|
|
83
|
-
* prefixCompletion}.
|
|
84
|
-
*
|
|
85
|
-
* WHY A SECOND SUPPLY EXISTS. The ranked list this mechanism normally reads
|
|
86
|
-
* is a resonance list, and resonance cannot rank a proper prefix: measured on
|
|
87
|
-
* the trained store, cos(prefix, form) falls from 0.9629 at a one-byte
|
|
88
|
-
* truncation to 0.6206 at three bytes, against a reachThreshold of 0.8750.
|
|
89
|
-
* Three bytes of truncation put the answer out of reach on GEOMETRY, not on a
|
|
90
|
-
* bug, so no k and no re-ranking recovers it.
|
|
91
|
-
*
|
|
92
|
-
* WHY THIS ROUTE WORKS WHERE THE FOLD DOES NOT. A query's own fold is
|
|
93
|
-
* useless here: content addressing is not phrase-position-invariant, so a
|
|
94
|
-
* standalone prefix folds to a DIFFERENT node than the same bytes sitting
|
|
95
|
-
* inside a longer deposit, and neither the prefix's own node nor its
|
|
96
|
-
* ancestors lead to the deposit (measured: the 22-byte prefix of the
|
|
97
|
-
* photosynthesis form resolves, is shared by 6 contexts, and does not have
|
|
98
|
-
* the form among its ancestors). Leaf ids ARE position-invariant — they are
|
|
99
|
-
* content-addressed on single bytes — and `indexSubSpans` already interns a
|
|
100
|
-
* flat branch over every canonical WINDOW of a deposit's leaf-id stream, with
|
|
101
|
-
* containment edges to the chunks that window spans. A query that is a
|
|
102
|
-
* prefix therefore shares those window nodes exactly, and reaches the deposit
|
|
103
|
-
* by climbing containment then parents. Nothing is added to the write side;
|
|
104
|
-
* this reads an index training already built.
|
|
105
|
-
*
|
|
106
|
-
* BOUNDED (§2.8), AND WITH NO NEW THRESHOLD. The window whose containment is
|
|
107
|
-
* SMALLEST carries the most evidence, and one saturated at `hubBound` carries
|
|
108
|
-
* none — that is the same √N reading of "hub" the rest of the mind uses, not
|
|
109
|
-
* a tuned knob. The upward walk spends a budget of `hubBound` nodes and
|
|
110
|
-
* fans out by W, so a hub query enumerates nothing and the caller stays
|
|
111
|
-
* silent rather than guessing (§2.13). Measured on the trained store: the
|
|
112
|
-
* photosynthesis form at a one-byte truncation picks a window with 52
|
|
113
|
-
* containers, visits 446 nodes, and yields exactly ONE candidate that
|
|
114
|
-
* survives the caller's byte compare — the form itself.
|
|
115
|
-
*
|
|
116
|
-
* These are PROPOSALS only. Every candidate still faces the byte-exact
|
|
117
|
-
* prefix compare and all three guards below, so a wrong proposal costs one
|
|
118
|
-
* bounded read and can never be voiced (§2.3). */
|
|
119
|
-
export function prefixCandidates(ctx, query) {
|
|
120
|
-
const store = ctx.store;
|
|
121
|
-
const W = ctx.space.maxGroup;
|
|
122
|
-
const run = leafIdPrefix(ctx, query);
|
|
123
|
-
// The widest canonical window is the most discriminative one the write side
|
|
124
|
-
// ever interned; a query too short to spell one carries no window evidence.
|
|
125
|
-
const len = canonicalWindows(W)[1];
|
|
126
|
-
if (run.length < len)
|
|
127
|
-
return [];
|
|
128
|
-
const bound = hubBound(ctx);
|
|
129
|
-
let best = null;
|
|
130
|
-
let bestN = 0;
|
|
131
|
-
for (let off = 0; off + len <= run.length; off++) {
|
|
132
|
-
const wid = store.findBranch(run.slice(off, off + len));
|
|
133
|
-
if (wid === null)
|
|
134
|
-
continue;
|
|
135
|
-
const n = store.containersSlice(wid, 0, bound).length;
|
|
136
|
-
// Empty says the window spans no chunk; saturated says it is a hub, whose
|
|
137
|
-
// containment discriminates nothing. Neither is evidence.
|
|
138
|
-
if (n === 0 || n >= bound)
|
|
139
|
-
continue;
|
|
140
|
-
if (best === null || n < bestN) {
|
|
141
|
-
best = wid;
|
|
142
|
-
bestN = n;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (best === null)
|
|
146
|
-
return [];
|
|
147
|
-
let frontier = store.containersSlice(best, 0, bound);
|
|
148
|
-
const seen = new Set(frontier);
|
|
149
|
-
let budget = bound;
|
|
150
|
-
while (frontier.length > 0 && budget > 0) {
|
|
151
|
-
const next = [];
|
|
152
|
-
for (const f of frontier) {
|
|
153
|
-
if (budget-- <= 0)
|
|
154
|
-
break;
|
|
155
|
-
for (const p of store.parentsFirst(f, W)) {
|
|
156
|
-
if (seen.has(p))
|
|
157
|
-
continue;
|
|
158
|
-
seen.add(p);
|
|
159
|
-
next.push(p);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
frontier = next;
|
|
163
|
-
}
|
|
164
|
-
return [...seen];
|
|
165
|
-
}
|
|
166
|
-
/** The sole trained form the query opens — or null when no candidate opens with
|
|
167
|
-
* it, when the continuation is sub-quantum, when a candidate's continuation
|
|
168
|
-
* cannot be read through, or when the candidates disagree.
|
|
169
|
-
*
|
|
170
|
-
* `ranked` must be a list the caller has ALREADY fetched; this mechanism never
|
|
171
|
-
* resonates on its own (see the header's cost note). */
|
|
172
|
-
export function prefixCompletion(ctx, query, ranked) {
|
|
173
|
-
const W = ctx.space.maxGroup;
|
|
174
|
-
const t = ctx.trace?.enter("prefixCompletion", [rItem(query, "query")]);
|
|
175
|
-
const done = (hit, note, data) => {
|
|
176
|
-
t?.done(hit === null ? [] : [rItem(hit.continuation, "continuation", hit.id)], note, data);
|
|
177
|
-
return hit;
|
|
178
|
-
};
|
|
179
|
-
// Reads are bounded to phrase scale, the same bound the frame filler uses.
|
|
180
|
-
// A query with no room for a whole grouping window past its own length
|
|
181
|
-
// cannot clear guard 2, so it is not worth a single read.
|
|
182
|
-
const cap = query.length * W;
|
|
183
|
-
if (query.length === 0 || cap < query.length + W) {
|
|
184
|
-
return done(null, "no room for a perceivable continuation within the cap");
|
|
185
|
-
}
|
|
186
|
-
// Distinct continuations, each with the first form that offered it. Held as
|
|
187
|
-
// a list, not a byte-keyed map: candidates that open with the query are few
|
|
188
|
-
// (measured: 1 on the trained store's winning query), and a linear byte
|
|
189
|
-
// compare needs no string encoding of content. Uniqueness (guard 3) is
|
|
190
|
-
// decided over this list, so the scan cannot stop early — a second
|
|
191
|
-
// continuation IS the refusal, and finding it is the point.
|
|
192
|
-
const found = [];
|
|
193
|
-
let opened = 0;
|
|
194
|
-
let unreadable = 0;
|
|
195
|
-
let subQuantum = 0;
|
|
196
|
-
for (const id of ranked) {
|
|
197
|
-
const form = ctx.store.bytesPrefix(id, cap);
|
|
198
|
-
if (form.length <= query.length)
|
|
199
|
-
continue;
|
|
200
|
-
let opens = true;
|
|
201
|
-
for (let i = 0; i < query.length; i++) {
|
|
202
|
-
if (form[i] !== query[i]) {
|
|
203
|
-
opens = false;
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (!opens)
|
|
208
|
-
continue;
|
|
209
|
-
opened++;
|
|
210
|
-
// Guard 1: a saturated read continues out of sight — a disagreement that
|
|
211
|
-
// cannot be resolved, so it ends the search rather than being skipped.
|
|
212
|
-
if (form.length >= cap) {
|
|
213
|
-
unreadable++;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
const rest = form.subarray(query.length);
|
|
217
|
-
// Guard 2: below one grouping window there is no structure to voice.
|
|
218
|
-
if (rest.length < W) {
|
|
219
|
-
subQuantum++;
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
if (!found.some((f) => bytesEqual(f.continuation, rest))) {
|
|
223
|
-
found.push({ id, form, continuation: rest });
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
const data = {
|
|
227
|
-
candidates: ranked.length,
|
|
228
|
-
opened,
|
|
229
|
-
unreadable,
|
|
230
|
-
subQuantum,
|
|
231
|
-
distinctContinuations: found.length,
|
|
232
|
-
};
|
|
233
|
-
if (unreadable > 0 && found.length > 0) {
|
|
234
|
-
return done(null, "a form opens with this query but continues past the read bound — " +
|
|
235
|
-
"its continuation cannot be read, so none is licensed", data);
|
|
236
|
-
}
|
|
237
|
-
// Guard 2b: A SUB-QUANTUM CONTINUATION IS STILL A DISAGREEMENT. Guard 2
|
|
238
|
-
// refuses to VOICE a below-window continuation, and rightly — there is no
|
|
239
|
-
// structure there to speak. But dropping such a candidate from the
|
|
240
|
-
// uniqueness tally silently converts "the corpus offers many continuations,
|
|
241
|
-
// most of them unvoiceable" into "the corpus offers exactly one", and
|
|
242
|
-
// guard 3 then passes VACUOUSLY on the sole survivor. That is precisely
|
|
243
|
-
// the failure guard 1 documents for unreadable continuations — suppressing
|
|
244
|
-
// the disagreement is what manufactures the answer — so it is answered the
|
|
245
|
-
// same way, and for the same reason.
|
|
246
|
-
//
|
|
247
|
-
// Measured on a 4,300-fact fixture of "what is the value of <i>?": the
|
|
248
|
-
// query "what is the value of" drew candidates continuing " 0?", " 4?",
|
|
249
|
-
// " 8?" (3 bytes, sub-quantum at W=4) and " 10?" (4 bytes). The first
|
|
250
|
-
// three were dropped, leaving one survivor, and the mechanism reported
|
|
251
|
-
// "exactly one trained form" and voiced "the value of 10 is 20" — an
|
|
252
|
-
// arbitrary pick from thousands of equally-good readings, with the
|
|
253
|
-
// evidence of ambiguity discarded on the way.
|
|
254
|
-
//
|
|
255
|
-
// Note this can only ever cause SILENCE, never a different answer: it
|
|
256
|
-
// withholds a completion the corpus does not uniquely license.
|
|
257
|
-
if (subQuantum > 0 && found.length > 0) {
|
|
258
|
-
return done(null, "other trained forms open with this query but continue below one " +
|
|
259
|
-
"grouping window — the corpus offers competing readings, so no " +
|
|
260
|
-
"single completion is licensed", data);
|
|
261
|
-
}
|
|
262
|
-
// Guard 3: the corpus must agree on ONE continuation.
|
|
263
|
-
if (found.length !== 1) {
|
|
264
|
-
return done(null, found.length === 0
|
|
265
|
-
? "no trained form opens with this query and continues perceivably"
|
|
266
|
-
: "trained forms open with this query but continue differently — " +
|
|
267
|
-
"the corpus does not say which continuation is meant", data);
|
|
268
|
-
}
|
|
269
|
-
return done(found[0], "one trained form opens with this query, and continues perceivably", data);
|
|
270
|
-
}
|