@hviana/sema 0.6.0 → 0.7.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/.github/workflows/release.yml +80 -0
- package/AGENTS.md +53 -9
- package/HOW_IT_WORKS.md +17 -16
- package/dist/src/meter.d.ts +14 -4
- package/dist/src/meter.js +27 -3
- package/dist/src/mind/attention.js +22 -20
- package/dist/src/mind/graph-search.d.ts +43 -9
- package/dist/src/mind/graph-search.js +82 -15
- package/dist/src/mind/junction.d.ts +13 -0
- package/dist/src/mind/junction.js +26 -1
- package/dist/src/mind/mechanisms/cover.js +23 -2
- package/dist/src/mind/mechanisms/prefix-completion.d.ts +2 -1
- package/dist/src/mind/mechanisms/prefix-completion.js +40 -20
- package/dist/src/mind/mechanisms/recall.js +8 -4
- package/dist/src/mind/pipeline-mechanism.d.ts +0 -24
- package/dist/src/mind/pipeline-mechanism.js +13 -36
- package/dist/src/mind/pipeline.d.ts +24 -0
- package/dist/src/mind/pipeline.js +71 -5
- package/dist/src/mind/recognition.d.ts +15 -1
- package/dist/src/mind/recognition.js +15 -1
- package/dist/src/mind/resonance.js +54 -12
- package/dist/src/store.js +22 -1
- package/jsr.json +1 -1
- package/package.json +7 -2
- package/src/meter.ts +27 -4
- package/src/mind/attention.ts +22 -19
- package/src/mind/graph-search.ts +93 -16
- package/src/mind/junction.ts +25 -1
- package/src/mind/mechanisms/cover.ts +23 -4
- package/src/mind/mechanisms/prefix-completion.ts +40 -20
- package/src/mind/mechanisms/recall.ts +8 -4
- package/src/mind/pipeline-mechanism.ts +13 -42
- package/src/mind/pipeline.ts +106 -5
- package/src/mind/recognition.ts +19 -2
- package/src/mind/resonance.ts +84 -49
- package/src/store.ts +21 -1
- package/test/89-completion-recursion.test.mjs +230 -0
- package/test/90-connector-read-cap.test.mjs +130 -0
- package/test/91-branch-bytes-cache.test.mjs +152 -0
- package/test/93-regime-prediction.test.mjs +148 -0
- package/test/94-cross-region-budget.test.mjs +67 -0
- package/test/95-wide-resonance-removed.test.mjs +109 -0
|
@@ -19,7 +19,7 @@ import type { ComputedSpan } from "../extension.js";
|
|
|
19
19
|
import type { Hit } from "../store.js";
|
|
20
20
|
import type { Vec } from "../vec.js";
|
|
21
21
|
import { indexOf } from "../bytes.js";
|
|
22
|
-
import {
|
|
22
|
+
import { dominates } from "../geometry.js";
|
|
23
23
|
import { windowIds } from "./canonical.js";
|
|
24
24
|
import { read, resolve } from "./primitives.js";
|
|
25
25
|
import {
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
skillExemplar,
|
|
31
31
|
} from "./match.js";
|
|
32
32
|
import { climbAttentionAll } from "./attention.js";
|
|
33
|
-
import {
|
|
33
|
+
import { sharedReachMemo } from "./traverse.js";
|
|
34
34
|
|
|
35
35
|
// ── Precomputed ──────────────────────────────────────────────────────────────
|
|
36
36
|
//
|
|
@@ -148,46 +148,17 @@ export class Precomputed {
|
|
|
148
148
|
);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
* THE CONDITION IS THE TOP HIT'S SCORE, NOT THE CORPUS SIZE. When nothing
|
|
163
|
-
* ranks at concept level, an exhaustive ANN only scores more vectors below
|
|
164
|
-
* the bar (profiled at 38K–40K annVectorReads per refusing query on a 325K-
|
|
165
|
-
* context store); the structural channels — junction walks, anchor climbs,
|
|
166
|
-
* the write side's window index — are the correct proposal source there,
|
|
167
|
-
* because the ANN cannot propose what the gist cannot rank. This was once
|
|
168
|
-
* spelled `corpusN(ctx) <= (k · W)³`, which asks a different question and
|
|
169
|
-
* answers it wrongly at exactly the scale it was written from: at N =
|
|
170
|
-
* 325,608 with k = 24 and W = 4 the cube is 884,736, so that store took the
|
|
171
|
-
* exhaustive branch — the very branch measured above. Measured cost of the
|
|
172
|
-
* mismatch: substitutionBridge 8,544 ms of a 19,548 ms think (44%), against
|
|
173
|
-
* 1,248 ms and 14,218 ms without it, every answer byte-identical. */
|
|
174
|
-
wideResonance(): Promise<ReadonlyArray<number>> {
|
|
175
|
-
return this._wide ??= this.shared("wideResonance", async () => {
|
|
176
|
-
const hits = await this.resonance();
|
|
177
|
-
if (
|
|
178
|
-
hits.length > 0 &&
|
|
179
|
-
hits[0].score >= conceptThreshold(this.ctx.store.D)
|
|
180
|
-
) {
|
|
181
|
-
const exhaustive = await this.ctx.store.resonate(
|
|
182
|
-
this.guide,
|
|
183
|
-
hubBound(this.ctx),
|
|
184
|
-
true,
|
|
185
|
-
);
|
|
186
|
-
return exhaustive.map((h) => h.id);
|
|
187
|
-
}
|
|
188
|
-
return hits.map((h) => h.id);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
151
|
+
// REMOVED — the WIDE exhaustive-√N resonance list (`wideResonance`). It ran
|
|
152
|
+
// `resonate(guide, √N, exhaustive=true)` whenever the top hit cleared
|
|
153
|
+
// conceptThreshold, so consumers could look "past the top-k". Every consumer
|
|
154
|
+
// only ever needed ≤ 2·recallQueryK proposals (the substitution bridge's own
|
|
155
|
+
// candidate cap) or a content-addressed answer (prefix completion's
|
|
156
|
+
// formsOpenedBy), and every proposal is byte-verified downstream (§2.3), so
|
|
157
|
+
// the exhaustive scan bought recall at O(index) cost for an O(k) need —
|
|
158
|
+
// measured: 244K annVectorReads per refusing query, ~1.5 s, every answer
|
|
159
|
+
// byte-identical to a top-k read. The two consumers now read `resonance()`
|
|
160
|
+
// (the one top-k read) and the write side's window index respectively — see
|
|
161
|
+
// recall.ts and prefix-completion.ts.
|
|
191
162
|
|
|
192
163
|
private _frames?: Promise<ReadonlyArray<FrameInstance>>;
|
|
193
164
|
/** THE FRAME INVENTORY — every ranked candidate that reads as an instance of
|
package/src/mind/pipeline.ts
CHANGED
|
@@ -129,6 +129,31 @@ export interface NarrowDecisionData {
|
|
|
129
129
|
margin: number;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/** Structured payload of the "regimePrediction" rationale step — the R8
|
|
133
|
+
* observation exposed as data. After the first mechanism (cover, which §2.6
|
|
134
|
+
* runs first) grounds or abstains, the market's whole outcome is already
|
|
135
|
+
* determined by the one cost ladder: the consensus climb runs exactly when
|
|
136
|
+
* `worthRunning(2 * STEP)` is true — CAST (floor 2·STEP) is the cheapest
|
|
137
|
+
* mechanism that first-touches it, and confluence (3·STEP) / extraction
|
|
138
|
+
* (CONCEPT+STEP) are only reached after CAST is. An incumbent at or below
|
|
139
|
+
* that floor prunes CAST and, with it, the climb (retrieval); anything above
|
|
140
|
+
* — or no incumbent — runs the full market and the climb (composition).
|
|
141
|
+
* Purely observational; never read by inference. */
|
|
142
|
+
export interface RegimePredictionData {
|
|
143
|
+
version: 1;
|
|
144
|
+
/** retrieval | composition — the two regimes R1 measured as a ~100× cost
|
|
145
|
+
* step. */
|
|
146
|
+
regime: "retrieval" | "composition";
|
|
147
|
+
/** The incumbent's grade once the first mechanism's turn is over (it ran, or
|
|
148
|
+
* it was skipped), or null when nothing has grounded — `best === null`,
|
|
149
|
+
* which is composition with no incumbent. */
|
|
150
|
+
incumbentGrade: number | null;
|
|
151
|
+
/** The cheapest composition floor in grade units (`grade(2 * STEP)` = 2,
|
|
152
|
+
* CAST's floor) — the bar the incumbent must sit at or below for the
|
|
153
|
+
* consensus climb to be skipped. */
|
|
154
|
+
climbFloorGrade: number;
|
|
155
|
+
}
|
|
156
|
+
|
|
132
157
|
/** Think: a single lightest-derivation exploration of the Sema graph.
|
|
133
158
|
*
|
|
134
159
|
* Every answer travels the same path:
|
|
@@ -161,10 +186,25 @@ export async function think(
|
|
|
161
186
|
|
|
162
187
|
// ── Pre-computation ──────────────────────────────────────────────────
|
|
163
188
|
const mechanisms = mechs ?? defaultMechanisms;
|
|
164
|
-
const
|
|
189
|
+
const meter = ctx.meter;
|
|
190
|
+
// recognition is a shared analysis (§2.14 contract 5): it does the query's
|
|
191
|
+
// own store work (perceive → foldTree → resolve), which used to land in
|
|
192
|
+
// `think` and in nothing narrower — the meter's one accounting surface must
|
|
193
|
+
// charge it to itself, exactly as attention/weave/resonance are charged.
|
|
194
|
+
// SYNCHRONOUS phase: recognition is on the sync side of §2.10's seam, so it
|
|
195
|
+
// is timed with `timeSync` — wrapping it in a promise would make a profiled
|
|
196
|
+
// response await where an unprofiled one does not.
|
|
197
|
+
const rec = meter
|
|
198
|
+
? meter.timeSync("recognise", () => recognise(ctx, query))
|
|
199
|
+
: recognise(ctx, query);
|
|
165
200
|
|
|
166
201
|
// Phase 1: collect computed spans from mechanisms that implement parse()
|
|
167
|
-
const computed =
|
|
202
|
+
const computed = meter
|
|
203
|
+
? await meter.time(
|
|
204
|
+
"collectComputed",
|
|
205
|
+
() => collectComputed(ctx, mechanisms, query),
|
|
206
|
+
)
|
|
207
|
+
: await collectComputed(ctx, mechanisms, query);
|
|
168
208
|
|
|
169
209
|
if (computed.length > 0) {
|
|
170
210
|
ctx.trace?.step(
|
|
@@ -191,6 +231,9 @@ export async function think(
|
|
|
191
231
|
// method on Precomputed, first-touched by whichever mechanism's floor
|
|
192
232
|
// survives its cheap gates and the worthRunning check. A query no
|
|
193
233
|
// mechanism climbs for (e.g. one an extension decided) never climbs.
|
|
234
|
+
// NOT phased: the constructor itself is trivial (it only derives `k`), so a
|
|
235
|
+
// phase here would add a zero-work entry to every profiled report — the meter
|
|
236
|
+
// attributes WORK (§2.14); the trace already represents structure.
|
|
194
237
|
const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
|
|
195
238
|
|
|
196
239
|
// ── Grounding: ONE lightest-derivation choice among the mechanisms ────
|
|
@@ -255,12 +298,66 @@ export async function think(
|
|
|
255
298
|
const worthRunning = (floor: number) =>
|
|
256
299
|
best === null || grade(floor) < grade(best.weight);
|
|
257
300
|
|
|
301
|
+
// REGIME PREDICTION (R8) — observational only. Once the FIRST mechanism has
|
|
302
|
+
// had its turn (cover, which §2.6 places first and floors at 0), the market's
|
|
303
|
+
// outcome is already determined by the one cost ladder: the consensus climb
|
|
304
|
+
// runs exactly when `worthRunning(2 * STEP)` is true — CAST (floor 2·STEP) is
|
|
305
|
+
// the cheapest mechanism that first-touches it, so an incumbent at or below
|
|
306
|
+
// grade 2 prunes CAST and, with it, confluence (3·STEP) and extraction
|
|
307
|
+
// (CONCEPT+STEP) (retrieval); anything above — or no incumbent — runs the
|
|
308
|
+
// full market and the climb (composition). The predicate is `worthRunning`,
|
|
309
|
+
// the same function the loop itself uses — nothing is computed here that the
|
|
310
|
+
// engine had not already computed, and nothing is read back by inference.
|
|
311
|
+
//
|
|
312
|
+
// EMITTED BEFORE THE SECOND MECHANISM'S FLOOR, never after some mechanism's
|
|
313
|
+
// run: a "prediction" published after the fact could assert "the climb will
|
|
314
|
+
// not run" about a climb that already ran — which is what happens whenever
|
|
315
|
+
// the first mechanism is SKIPPED (null floor or pruned) and the block sits at
|
|
316
|
+
// the end of the first mechanism that actually ran. Emitting on entry to
|
|
317
|
+
// iteration 1 makes the claim true by construction, whatever the first
|
|
318
|
+
// mechanism did, and keeps the payload identical on the ordinary path (the
|
|
319
|
+
// incumbent cannot change between the two positions).
|
|
320
|
+
let regimeReported = false;
|
|
321
|
+
const reportRegime = () => {
|
|
322
|
+
if (regimeReported) return;
|
|
323
|
+
regimeReported = true;
|
|
324
|
+
const climbFloorGrade = grade(2 * STEP);
|
|
325
|
+
// TS narrows `best` to null in the outer flow (it cannot see the closure
|
|
326
|
+
// assignments in `consider`) — cast back, the same read-back as `decided`
|
|
327
|
+
// below.
|
|
328
|
+
const incumbent = best as Candidate | null;
|
|
329
|
+
const incumbentGrade = incumbent === null ? null : grade(incumbent.weight);
|
|
330
|
+
const regime: "retrieval" | "composition" = worthRunning(2 * STEP)
|
|
331
|
+
? "composition"
|
|
332
|
+
: "retrieval";
|
|
333
|
+
ctx.trace?.step(
|
|
334
|
+
"regimePrediction",
|
|
335
|
+
[rItem(query, "query")],
|
|
336
|
+
[],
|
|
337
|
+
regime === "retrieval"
|
|
338
|
+
? `retrieval regime — incumbent grade ${incumbentGrade} ≤ climb floor ${climbFloorGrade}, ` +
|
|
339
|
+
`so no mechanism floored above that grade runs; the consensus climb will not run`
|
|
340
|
+
: `composition regime — ${
|
|
341
|
+
incumbentGrade === null
|
|
342
|
+
? "no incumbent (nothing grounded)"
|
|
343
|
+
: `incumbent grade ${incumbentGrade}`
|
|
344
|
+
} above climb floor ${climbFloorGrade}, so the full market and climb run`,
|
|
345
|
+
undefined,
|
|
346
|
+
{
|
|
347
|
+
version: 1,
|
|
348
|
+
regime,
|
|
349
|
+
incumbentGrade,
|
|
350
|
+
climbFloorGrade,
|
|
351
|
+
} satisfies RegimePredictionData,
|
|
352
|
+
);
|
|
353
|
+
};
|
|
258
354
|
// Phase 3: grounding loop
|
|
259
355
|
// Per-mechanism accounting (src/meter.ts). The market's whole premise is
|
|
260
356
|
// that mechanisms compete on one cost scale — so the profiling read-out is
|
|
261
357
|
// also per-mechanism, uniformly: the loop never asks which one it holds.
|
|
262
|
-
|
|
263
|
-
|
|
358
|
+
for (let mi = 0; mi < mechanisms.length; mi++) {
|
|
359
|
+
const mech = mechanisms[mi];
|
|
360
|
+
if (mi > 0) reportRegime();
|
|
264
361
|
const floor = meter
|
|
265
362
|
? await meter.time(
|
|
266
363
|
`${mech.name}.floor`,
|
|
@@ -309,6 +406,10 @@ export async function think(
|
|
|
309
406
|
});
|
|
310
407
|
}
|
|
311
408
|
}
|
|
409
|
+
// A market of ONE mechanism never reaches iteration 1; the step is still
|
|
410
|
+
// emitted exactly once per think(), so a consumer never has to ask whether
|
|
411
|
+
// the list was long enough for the prediction to exist.
|
|
412
|
+
reportRegime();
|
|
312
413
|
|
|
313
414
|
// (TS cannot see the closure assignments into `best` and narrows it to its
|
|
314
415
|
// initial null, so the read-back needs the assertion.)
|
|
@@ -508,7 +609,7 @@ export async function think(
|
|
|
508
609
|
reasoned,
|
|
509
610
|
pre,
|
|
510
611
|
unclimbed,
|
|
511
|
-
|
|
612
|
+
primarySpans,
|
|
512
613
|
);
|
|
513
614
|
|
|
514
615
|
done(
|
package/src/mind/recognition.ts
CHANGED
|
@@ -32,8 +32,25 @@ import type { Leaf, Site } from "./graph-search.js";
|
|
|
32
32
|
* the longest known leaf, chained into flat branches. Names forms the
|
|
33
33
|
* query's own cut cannot, and records sub-leaf boundaries as `splits`.
|
|
34
34
|
*
|
|
35
|
-
* Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus.
|
|
36
|
-
|
|
35
|
+
* Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus.
|
|
36
|
+
*
|
|
37
|
+
* ONE READING PER BYTE STREAM, deliberately: there is no "cheap mode" that
|
|
38
|
+
* skips the edge-trim fallbacks. A `trimmed` variant was tried and REFUTED
|
|
39
|
+
* twice over. Its premise — "the trims only recover misaligned FRAGMENTS, so
|
|
40
|
+
* a consumer whose gate rejects fragments loses nothing" — is false: the
|
|
41
|
+
* left/right trim loops below exist precisely to find WHOLE trained forms
|
|
42
|
+
* embedded at an offset the query's own fold did not cut, and such a form has
|
|
43
|
+
* no structural parents or containers, so it passes the pivot's fragment gate
|
|
44
|
+
* and is exactly the candidate a multi-hop chain steps through. Skipping them
|
|
45
|
+
* narrows the pivot's evidence silently. And a per-caller variant has to key
|
|
46
|
+
* the memo by the variant, which breaks the "computed at most once" property
|
|
47
|
+
* (§2.11): the pipeline recognises a grounded answer untrimmed for
|
|
48
|
+
* `preConsumed`, and the pivot then recognises the same bytes again — the
|
|
49
|
+
* saving inverts into a doubling on the path it was measured for. */
|
|
50
|
+
export function recognise(
|
|
51
|
+
ctx: MindContext,
|
|
52
|
+
bytes: Uint8Array,
|
|
53
|
+
): Recognition {
|
|
37
54
|
// Content-keyed memo — works for both single-turn respond() and multi-turn
|
|
38
55
|
// respondTurn() (where the map persists across calls). ALWAYS consulted,
|
|
39
56
|
// regardless of tracing — matching perceive()'s own memo, which carries no
|
package/src/mind/resonance.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { concat2, concatBytes, indexOf } from "../bytes.js";
|
|
|
12
12
|
import type { MindContext } from "./types.js";
|
|
13
13
|
import { gistOf, read, resolve, walkTree } from "./primitives.js";
|
|
14
14
|
import { perceive } from "./primitives.js";
|
|
15
|
-
import {
|
|
15
|
+
import { argmaxCosine, candidateGist, hubBound } from "./traverse.js";
|
|
16
16
|
import {
|
|
17
17
|
cachedRead,
|
|
18
18
|
type Junction,
|
|
@@ -365,6 +365,12 @@ export async function pivotInto(
|
|
|
365
365
|
}
|
|
366
366
|
for (const c of n.kids) queue.push(c); // breadth-first: larger regions first
|
|
367
367
|
}
|
|
368
|
+
// THE FULL recognition, memo-shared with every other reader of these bytes.
|
|
369
|
+
// A "skip the edge trims here" variant was refuted (see recognise's own
|
|
370
|
+
// note): those trims are what find a WHOLE trained form embedded at an
|
|
371
|
+
// offset the answer's fold did not cut, and such a form is parentless,
|
|
372
|
+
// container-free and edge-bearing — i.e. exactly what the filter below
|
|
373
|
+
// ADMITS as a pivot, not what it rejects.
|
|
368
374
|
const rec = recognise(ctx, answer);
|
|
369
375
|
for (const s of rec.sites) {
|
|
370
376
|
if (!consumed.has(s.payload) && ctx.store.hasNext(s.payload)) {
|
|
@@ -373,54 +379,83 @@ export async function pivotInto(
|
|
|
373
379
|
}
|
|
374
380
|
// Byte containment, longest wins — the answer literally contains the
|
|
375
381
|
// pivot's bytes, and the biggest well-evidenced span is the real pivot.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
382
|
+
//
|
|
383
|
+
// REAL SATURATION, not a hard cap: the score IS the candidate's byte
|
|
384
|
+
// length, so the scan is DECIDED the moment the first candidate that passes
|
|
385
|
+
// every filter is found in DESCENDING length order — a shorter candidate can
|
|
386
|
+
// never outscore it. `contentLen` (the prefix-capped length read, §2.8) is
|
|
387
|
+
// the cheap ordering key, and the first-inserted tie-break is made explicit
|
|
388
|
+
// (`a.index - b.index`) so equal lengths keep `scored`'s insertion order —
|
|
389
|
+
// exactly the tie argmaxBy(strict) used to keep. The bytes of at most ONE
|
|
390
|
+
// winning candidate are read; every shorter candidate the probes proposed is
|
|
391
|
+
// skipped without reconstruction, where the old argmax read them all.
|
|
392
|
+
const ranked = [...scored.keys()]
|
|
393
|
+
.map((id, index) => ({
|
|
394
|
+
id,
|
|
395
|
+
index,
|
|
396
|
+
len: ctx.store.contentLen(id, answer.length + 1),
|
|
397
|
+
}))
|
|
398
|
+
.sort((a, b) => b.len - a.len || a.index - b.index);
|
|
399
|
+
let pivotId: number | null = null;
|
|
400
|
+
for (const c of ranked) {
|
|
401
|
+
const id = c.id;
|
|
402
|
+
// A ZERO-LENGTH candidate is not a pivot. `argmaxBy(…, 0, strict)` used to
|
|
403
|
+
// carry this floor in its threshold argument, and dropping it here would
|
|
404
|
+
// admit an empty node: `indexOf(answer, <empty>)` returns 0, so every
|
|
405
|
+
// filter below passes and the chain would hop through nothing (§2.13 —
|
|
406
|
+
// empty bytes are truthy).
|
|
407
|
+
if (c.len === 0) continue;
|
|
408
|
+
// A PIVOT MUST BE A THING THE CORPUS DEPOSITED, NOT A PIECE OF ONE.
|
|
409
|
+
// "Longest wins" ranks candidates but never asks whether the winner is
|
|
410
|
+
// an entity at all, and by the time a chain reaches here `consumeAll`
|
|
411
|
+
// has taken the answer's real contexts — so on a corpus of
|
|
412
|
+
// near-identical records the field is left to whatever interned
|
|
413
|
+
// fragments remain. Measured on a 200-line templated log corpus, query
|
|
414
|
+
// "what happened to request_id=1042 and request_id=1077?": CAST
|
|
415
|
+
// produced the correct comparison and one `pivotStep` replaced it
|
|
416
|
+
// wholesale, pivoting through `s=70` — a four-byte tail of
|
|
417
|
+
// `latency_ms=70` — onto an unrelated record (`handled 1130`).
|
|
418
|
+
//
|
|
419
|
+
// The separator is NOT length. Measured against the multi-hop tests'
|
|
420
|
+
// own pivots: `Paris` (5 bytes), `Jupiter` (7), `lithium` (7), `Mona
|
|
421
|
+
// Lisa` (9) against junk `s=70` (4) — a two-quantum floor, which
|
|
422
|
+
// confluence.ts applies to a meet for the same "one window is not an
|
|
423
|
+
// entity" reason, discards three of the four legitimate pivots.
|
|
424
|
+
// Entities are simply short.
|
|
425
|
+
//
|
|
426
|
+
// What separates them is STRUCTURAL, and the store already holds it:
|
|
427
|
+
//
|
|
428
|
+
// s=70 parents 2 containers 1 prevCount 0 halo no
|
|
429
|
+
// Paris parents 0 containers 0 prevCount 1 halo yes
|
|
430
|
+
// Jupiter parents 0 containers 0 prevCount 1 halo yes
|
|
431
|
+
// lithium parents 0 containers 0 prevCount 1 halo yes
|
|
432
|
+
// Mona Lisa parents 0 containers 0 prevCount 1 halo yes
|
|
433
|
+
//
|
|
434
|
+
// A deposited whole — a context or an answer — is interned in its own
|
|
435
|
+
// right and has neither structural parents nor containment links. A
|
|
436
|
+
// fragment is addressable ONLY because window interning made its span
|
|
437
|
+
// addressable inside something bigger, and that containment is exactly
|
|
438
|
+
// what `parents`/`containers` record. Reasoning steps THROUGH a fact;
|
|
439
|
+
// a span that was never a fact on its own is not one to step through.
|
|
440
|
+
// No constant enters — it is a structural predicate, not a threshold.
|
|
441
|
+
if (ctx.store.hasParents(id) || ctx.store.hasContainers(id)) continue;
|
|
442
|
+
// A candidate whose bytes are LONGER than the answer cannot be a
|
|
443
|
+
// substring of it — `indexOf` would return −1 regardless. Prune by
|
|
444
|
+
// length BEFORE reconstructing the bytes: `read` is an UNCAPPED read
|
|
445
|
+
// (AGENTS §2.8), and a resonated context far longer than the answer is
|
|
446
|
+
// exactly the candidate that makes it cost a whole deposit's worth of
|
|
447
|
+
// reconstruction for a containment test that must fail. `contentLen`
|
|
448
|
+
// with the `answer.length + 1` cap is the prefix-capped length read the
|
|
449
|
+
// same contract prescribes; the prune is byte-identical to the old
|
|
450
|
+
// `indexOf` miss (it returns −1 for a needle longer than the haystack).
|
|
451
|
+
if (c.len > answer.length) continue;
|
|
452
|
+
const bytes = read(ctx, id);
|
|
453
|
+
if (indexOf(answer, bytes, 0) < 0) continue;
|
|
454
|
+
if (voiced.some((v) => indexOf(v, bytes, 0) >= 0)) continue;
|
|
455
|
+
pivotId = id;
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
return pivotId;
|
|
424
459
|
}
|
|
425
460
|
|
|
426
461
|
/** Which of the given labelled forms a span MEANS — generic resonance over
|
package/src/store.ts
CHANGED
|
@@ -1269,7 +1269,27 @@ export abstract class AbstractStore implements Store {
|
|
|
1269
1269
|
parts.push(child);
|
|
1270
1270
|
got += child.length;
|
|
1271
1271
|
}
|
|
1272
|
-
|
|
1272
|
+
const out = concat(parts);
|
|
1273
|
+
// Cache the BRANCH too, not just the leaf above. Reconstruction is a pure
|
|
1274
|
+
// function of the store, so this is a transparent cache in the strict sense
|
|
1275
|
+
// — an eviction costs a re-walk and nothing else — which is exactly what
|
|
1276
|
+
// `_bytesCache`'s "smallest"/"clock" configuration is for.
|
|
1277
|
+
//
|
|
1278
|
+
// Caching only leaves made every branch re-walk its whole subtree on every
|
|
1279
|
+
// request, and the DAG is hash-consed, so the same children recur under many
|
|
1280
|
+
// parents. Measured on the 18.9M-node store, ONE 1,314-byte query:
|
|
1281
|
+
// 20,021,474 `_prefix` calls over 469,083 distinct ids (42.7x reuse) to
|
|
1282
|
+
// produce 87,789 results — 97.7% of the work re-derived bytes it had already
|
|
1283
|
+
// built. One single-byte leaf was reconstructed 2,599,984 times. Measuring
|
|
1284
|
+
// reuse at the TOP level only shows 1.1x and hides all of it.
|
|
1285
|
+
//
|
|
1286
|
+
// Only a COMPLETE reconstruction may be cached: `_prefix` is also called
|
|
1287
|
+
// with a cap, and a truncated prefix stored under `id` would be served as
|
|
1288
|
+
// if it were the node's whole content by the `_bytesCache` hit above.
|
|
1289
|
+
// `got < maxLen` is that proof — the walk ran out of children before it ran
|
|
1290
|
+
// out of budget, so nothing below was truncated either.
|
|
1291
|
+
if (got < maxLen) this._bytesCache.set(id, out);
|
|
1292
|
+
return out;
|
|
1273
1293
|
}
|
|
1274
1294
|
|
|
1275
1295
|
contentLen(id: NodeId, cap = Infinity): number {
|