@hviana/sema 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +29 -29
- package/TRADEMARKS.md +0 -1
- package/dist/src/config.d.ts +28 -0
- package/dist/src/config.js +20 -0
- package/dist/src/geometry.d.ts +21 -0
- package/dist/src/geometry.js +21 -0
- package/dist/src/meter.d.ts +76 -0
- package/dist/src/meter.js +95 -0
- package/dist/src/mind/attention.d.ts +4 -0
- package/dist/src/mind/attention.js +165 -16
- package/dist/src/mind/canonical.d.ts +16 -0
- package/dist/src/mind/canonical.js +41 -0
- package/dist/src/mind/corpus.d.ts +40 -0
- package/dist/src/mind/corpus.js +149 -0
- package/dist/src/mind/graph-search.d.ts +7 -0
- package/dist/src/mind/graph-search.js +254 -24
- package/dist/src/mind/index.d.ts +3 -1
- package/dist/src/mind/index.js +1 -0
- package/dist/src/mind/match.d.ts +9 -4
- package/dist/src/mind/match.js +147 -61
- package/dist/src/mind/mechanisms/cast.js +19 -3
- package/dist/src/mind/mechanisms/confluence.js +24 -0
- package/dist/src/mind/mechanisms/cover.js +6 -0
- package/dist/src/mind/mechanisms/recall.js +32 -4
- package/dist/src/mind/mind.d.ts +57 -0
- package/dist/src/mind/mind.js +72 -1
- package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
- package/dist/src/mind/pipeline.js +66 -20
- package/dist/src/mind/primitives.js +9 -1
- package/dist/src/mind/rationale.d.ts +28 -1
- package/dist/src/mind/rationale.js +22 -1
- package/dist/src/mind/reasoning.d.ts +25 -3
- package/dist/src/mind/reasoning.js +125 -20
- package/dist/src/mind/recognition.js +4 -8
- package/dist/src/mind/resonance.js +20 -1
- package/dist/src/mind/trace.js +1 -0
- package/dist/src/mind/traverse.js +15 -3
- package/dist/src/mind/types.d.ts +49 -4
- package/docs/INVARIANTS.md +2 -2
- package/docs/architecture/bounded-reads.md +1 -1
- package/docs/architecture/commonality.md +2 -2
- package/docs/architecture/cost-model.md +2 -2
- package/docs/architecture/determinism.md +7 -7
- package/docs/architecture/match-project.md +2 -3
- package/docs/architecture/mechanism-market.md +10 -10
- package/docs/architecture/meter.md +5 -5
- package/docs/architecture/store.md +3 -3
- package/docs/failures/tempting-but-wrong.md +34 -6
- package/docs/harness/gates.md +2 -2
- package/docs/mechanisms/cast.md +2 -2
- package/docs/mechanisms/cover.md +2 -3
- package/docs/mechanisms/extraction.md +7 -7
- package/docs/mechanisms/recall.md +8 -9
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alu/README.md +11 -12
- package/src/config.ts +48 -0
- package/src/geometry.ts +21 -0
- package/src/meter.ts +98 -0
- package/src/mind/attention.ts +167 -16
- package/src/mind/canonical.ts +43 -0
- package/src/mind/corpus.ts +202 -0
- package/src/mind/graph-search.ts +277 -23
- package/src/mind/index.ts +8 -1
- package/src/mind/match.ts +148 -57
- package/src/mind/mechanisms/cast.ts +20 -2
- package/src/mind/mechanisms/confluence.ts +24 -0
- package/src/mind/mechanisms/cover.ts +5 -0
- package/src/mind/mechanisms/recall.ts +32 -4
- package/src/mind/mind.ts +125 -0
- package/src/mind/pipeline-mechanism.ts +7 -0
- package/src/mind/pipeline.ts +79 -22
- package/src/mind/primitives.ts +9 -1
- package/src/mind/rationale.ts +35 -1
- package/src/mind/reasoning.ts +145 -13
- package/src/mind/recognition.ts +4 -8
- package/src/mind/resonance.ts +19 -1
- package/src/mind/trace.ts +1 -0
- package/src/mind/traverse.ts +16 -6
- package/src/mind/types.ts +53 -4
- package/test/100-complete-grounding-trace.test.mjs +109 -0
- package/test/101-alignment-gap-bound.test.mjs +106 -0
- package/test/102-production-composes-at-scale.test.mjs +110 -0
- package/test/103-alignment-gap-budget.test.mjs +89 -0
- package/test/104-composition-is-reported.test.mjs +90 -0
- package/test/105-derive-through-reports-its-refusal.test.mjs +137 -0
- package/test/106-the-join-fires.test.mjs +94 -0
- package/test/107-the-join-is-counted.test.mjs +81 -0
- package/test/108-the-join-chains.test.mjs +78 -0
- package/test/109-the-pivot-is-counted.test.mjs +60 -0
- package/test/110-the-reasoner-stops-when-the-question-is-answered.test.mjs +91 -0
- package/test/111-the-cover-assembly-is-counted.test.mjs +74 -0
- package/test/112-the-exploration-does-not-grow-with-the-hub.test.mjs +89 -0
- package/test/113-the-rationale-payload-is-bounded.test.mjs +84 -0
- package/test/114-alignment-budget-is-per-sweep.test.mjs +93 -0
- package/test/116-the-extension-is-gated-by-the-pipelines-own-remainder.test.mjs +100 -0
- package/test/117-corpus-search.test.mjs +171 -0
- package/test/118-the-join-reaches-a-key-off-the-cut.test.mjs +74 -0
- package/test/119-the-work-does-not-grow-with-the-corpus.test.mjs +122 -0
- package/test/120-composition-is-consequence.test.mjs +132 -0
- package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +128 -0
- package/test/122-the-climb-search-does-not-grow-with-the-corpus.test.mjs +117 -0
- package/test/123-the-paired-formulas-agree.test.mjs +90 -0
- package/test/125-the-post-grounding-branch-publishes-its-operand.test.mjs +51 -0
- package/test/126-the-pipeline-does-not-name-mechanisms.test.mjs +42 -0
- package/test/128-the-leads-somewhere-pair-agrees.test.mjs +83 -0
- package/test/129-the-trace-payload-shape.test.mjs +164 -0
- package/test/14-scaling.test.mjs +10 -7
- package/test/32-confluence.test.mjs +68 -0
- package/test/38-reason-restate-guard.test.mjs +8 -2
- package/test/43-cast-analog-seat.test.mjs +10 -0
- package/test/55-cost-meter.test.mjs +859 -0
- package/test/76-reference-binding.test.mjs +6 -1
- package/test/89-completion-recursion.test.mjs +30 -5
package/dist/src/mind/match.js
CHANGED
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
// they gate.
|
|
32
32
|
import { addInto, cosine, dot, normalize, zeros } from "../vec.js";
|
|
33
33
|
import { conceptThreshold, dominates, identityBar, significanceBar, } from "../geometry.js";
|
|
34
|
-
import { bytesEqual, indexOf } from "../bytes.js";
|
|
35
|
-
import {
|
|
34
|
+
import { bytesEqual, indexOf, latin1 } from "../bytes.js";
|
|
35
|
+
import { leafIdRun } from "./canonical.js";
|
|
36
36
|
import { foldTree, gistOf, perceive, read, resolve } from "./primitives.js";
|
|
37
37
|
import { argmaxCosine, chooseAmong, chooseNext, corpusN, edgeAncestors, guidedFirst, hubBound, hubCap, sharedReachMemo, } from "./traverse.js";
|
|
38
38
|
import { recognise, segment } from "./recognition.js";
|
|
@@ -237,9 +237,14 @@ export function alignGraded(ctx, query, contextBytes, querySites) {
|
|
|
237
237
|
}
|
|
238
238
|
/** Extend a seed match (query offset qo ↔ candidate offset co) to its maximal
|
|
239
239
|
* common run, then walk outward in both directions collecting further common
|
|
240
|
-
* runs of at least W bytes across
|
|
241
|
-
*
|
|
242
|
-
*
|
|
240
|
+
* runs of at least W bytes across mismatch gaps. Each gap's LENGTH is the
|
|
241
|
+
* pair's own extent (a gap cannot be longer than the bytes it spans) and the
|
|
242
|
+
* sweep's WORK is proportional to the bytes a run spans (the context's windows
|
|
243
|
+
* are indexed once, then the query's are walked) — the arity bound
|
|
244
|
+
* (`chainReach`) used to cap BOTH, and truncated every learned frame whose
|
|
245
|
+
* slot was longer. Each sweep owns its own budget, so an exhausted right
|
|
246
|
+
* sweep never starves the left one. Returns the matched query spans and the
|
|
247
|
+
* mismatch pairs between consecutive runs.
|
|
243
248
|
*
|
|
244
249
|
* This is the SEEDED aligner, distinct from {@link alignRuns}: that one finds
|
|
245
250
|
* every run two structures share anywhere (a weave), this one reads two
|
|
@@ -253,7 +258,21 @@ export function alignGraded(ctx, query, contextBytes, querySites) {
|
|
|
253
258
|
* window test); {@link frameSlots} takes the other reading. */
|
|
254
259
|
export function alignAround(ctx, q, c, qo, co) {
|
|
255
260
|
const W = ctx.space.maxGroup;
|
|
256
|
-
|
|
261
|
+
// THE GAP LENGTH IS THE PAIR'S OWN EXTENT; THE WORK IS BUDGETED.
|
|
262
|
+
//
|
|
263
|
+
// The sweep walks (queryGap, contextGap) pairs by ASCENDING total, so reaching
|
|
264
|
+
// a gap of size G costs about G²/2 pairs. Bounding the LENGTH by the write
|
|
265
|
+
// side's arity (`chainReach(W)` = 16) therefore truncated every learned frame
|
|
266
|
+
// whose slot is longer — measured: `bindReference` reported the cap at 18, 24,
|
|
267
|
+
// 30 and 36 bytes and `recall` answered with ANOTHER instance's filler — while
|
|
268
|
+
// removing the bound outright took the corpus-cost guard (test/89) from
|
|
269
|
+
// milliseconds to 68 seconds.
|
|
270
|
+
//
|
|
271
|
+
// Bounding the PAIRS keeps a call's cost constant however long the pair is,
|
|
272
|
+
// and the ascending order means an exhausted budget drops the FAR
|
|
273
|
+
// continuations and never the near ones — the same degradation recognition.ts
|
|
274
|
+
// documents for its canon budget. Length and work are different questions;
|
|
275
|
+
// this is the one place they were conflated.
|
|
257
276
|
// Maximal run around the seed.
|
|
258
277
|
let qs = qo, ss = co;
|
|
259
278
|
while (qs > 0 && ss > 0 && q[qs - 1] === c[ss - 1]) {
|
|
@@ -267,8 +286,66 @@ export function alignAround(ctx, q, c, qo, co) {
|
|
|
267
286
|
}
|
|
268
287
|
const matched = [[qs, qe]];
|
|
269
288
|
const gaps = [];
|
|
270
|
-
//
|
|
271
|
-
//
|
|
289
|
+
// THE SWEEP IS STRUCTURAL, NOT ENUMERATIVE.
|
|
290
|
+
//
|
|
291
|
+
// The criterion is unchanged: the next common run, MINIMUM TOTAL GAP, ties to
|
|
292
|
+
// the smaller query gap. What changed is how it is found. Enumerating
|
|
293
|
+
// (queryGap, contextGap) pairs by ascending total reaches a run at total t in
|
|
294
|
+
// about t²/2 pairs — and that quadratic shape, not the reach, was the cost
|
|
295
|
+
// problem: capping the pairs dropped reach (a legitimate 24-byte slot stopped
|
|
296
|
+
// being found), while leaving them uncapped cost 68 seconds on the corpus
|
|
297
|
+
// guard. Neither is the answer, because the answer is the algorithm.
|
|
298
|
+
//
|
|
299
|
+
// The context's windows are indexed ONCE, for lengths 1..W — W being the
|
|
300
|
+
// geometry's own unit of composition, so nothing is chosen here. Each step
|
|
301
|
+
// then walks the query's windows outward from the anchor: for a given query
|
|
302
|
+
// gap the nearest context gap that continues a run is one O(1) lookup, and the
|
|
303
|
+
// walk stops the moment the query gap alone exceeds the best total already
|
|
304
|
+
// found. So the work is proportional to the bytes the run SPANS. No budget,
|
|
305
|
+
// no cap, no number: a long slot is reached, and its price is already the
|
|
306
|
+
// ladder's (its bytes are unaccounted, so the search pays PASS per byte).
|
|
307
|
+
const index = [];
|
|
308
|
+
for (let len = 1; len <= W; len++) {
|
|
309
|
+
const m = new Map();
|
|
310
|
+
for (let o = 0; o + len <= c.length; o++) {
|
|
311
|
+
const key = latin1(c.subarray(o, o + len));
|
|
312
|
+
const at = m.get(key);
|
|
313
|
+
if (at === undefined)
|
|
314
|
+
m.set(key, [o]);
|
|
315
|
+
else
|
|
316
|
+
at.push(o);
|
|
317
|
+
}
|
|
318
|
+
index.push(m);
|
|
319
|
+
}
|
|
320
|
+
/** Smallest listed offset at or after `from`, or -1. */
|
|
321
|
+
const fromAt = (list, from) => {
|
|
322
|
+
let lo = 0, hi = list.length - 1, best = -1;
|
|
323
|
+
while (lo <= hi) {
|
|
324
|
+
const mid = (lo + hi) >> 1;
|
|
325
|
+
if (list[mid] >= from) {
|
|
326
|
+
best = list[mid];
|
|
327
|
+
hi = mid - 1;
|
|
328
|
+
}
|
|
329
|
+
else
|
|
330
|
+
lo = mid + 1;
|
|
331
|
+
}
|
|
332
|
+
return best;
|
|
333
|
+
};
|
|
334
|
+
/** Largest listed offset at or before `to`, or -1. */
|
|
335
|
+
const toAt = (list, to) => {
|
|
336
|
+
let lo = 0, hi = list.length - 1, best = -1;
|
|
337
|
+
while (lo <= hi) {
|
|
338
|
+
const mid = (lo + hi) >> 1;
|
|
339
|
+
if (list[mid] <= to) {
|
|
340
|
+
best = list[mid];
|
|
341
|
+
lo = mid + 1;
|
|
342
|
+
}
|
|
343
|
+
else
|
|
344
|
+
hi = mid - 1;
|
|
345
|
+
}
|
|
346
|
+
return best;
|
|
347
|
+
};
|
|
348
|
+
/** Length of the common run STARTING at (qi, si). */
|
|
272
349
|
const runLenAt = (qi, si) => {
|
|
273
350
|
let n = 0;
|
|
274
351
|
while (qi + n < q.length && si + n < c.length && q[qi + n] === c[si + n]) {
|
|
@@ -276,69 +353,76 @@ export function alignAround(ctx, q, c, qo, co) {
|
|
|
276
353
|
}
|
|
277
354
|
return n;
|
|
278
355
|
};
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
356
|
+
/** Length of the common run ENDING at (qi, si). */
|
|
357
|
+
const runLenBefore = (qi, si) => {
|
|
358
|
+
let n = 0;
|
|
359
|
+
while (n < qi && n < si && q[qi - 1 - n] === c[si - 1 - n])
|
|
360
|
+
n++;
|
|
361
|
+
return n;
|
|
362
|
+
};
|
|
363
|
+
/** The next run outward from an anchor, or null when the bytes run out. */
|
|
364
|
+
const nextRun = (qi, si, forward) => {
|
|
365
|
+
const qLim = forward ? q.length - qi : qi;
|
|
366
|
+
let best = null;
|
|
367
|
+
for (let gq = 0; gq < qLim; gq++) {
|
|
368
|
+
// No later query gap can beat a total already found.
|
|
369
|
+
if (best !== null && gq > best.gq + best.gs)
|
|
370
|
+
break;
|
|
371
|
+
const left = qLim - gq;
|
|
372
|
+
// A run of >= W bytes, or — when the query itself ends inside one window —
|
|
373
|
+
// the run that REACHES that end. Exactly the acceptance the sweep had.
|
|
374
|
+
const lens = left >= W ? [W] : [left];
|
|
375
|
+
for (const len of lens) {
|
|
376
|
+
const key = latin1(q.subarray(forward ? qi + gq : qi - gq - len, forward ? qi + gq + len : qi - gq));
|
|
377
|
+
const list = index[len - 1].get(key);
|
|
378
|
+
if (list === undefined)
|
|
287
379
|
continue;
|
|
288
|
-
|
|
380
|
+
const o = forward ? fromAt(list, si) : toAt(list, si - len);
|
|
381
|
+
if (o < 0)
|
|
289
382
|
continue;
|
|
290
|
-
const n =
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
383
|
+
const n = forward
|
|
384
|
+
? runLenAt(qi + gq, o)
|
|
385
|
+
: runLenBefore(qi - gq, o + len);
|
|
386
|
+
if (n < 1)
|
|
387
|
+
continue;
|
|
388
|
+
if (forward ? n >= W || qi + gq + n === q.length : n >= W || n === qi - gq) {
|
|
389
|
+
const gs = forward ? o - si : si - len - o;
|
|
390
|
+
if (best === null || gq + gs < best.gq + best.gs) {
|
|
391
|
+
best = { gq, gs, n };
|
|
296
392
|
}
|
|
297
|
-
matched.push([qi + gq, qi + gq + n]);
|
|
298
|
-
qi = qi + gq + n;
|
|
299
|
-
si = si + gs + n;
|
|
300
|
-
found = true;
|
|
301
393
|
break;
|
|
302
394
|
}
|
|
303
395
|
}
|
|
304
396
|
}
|
|
305
|
-
|
|
397
|
+
return best;
|
|
398
|
+
};
|
|
399
|
+
// RIGHT sweep.
|
|
400
|
+
let qi = qe, si = se;
|
|
401
|
+
for (;;) {
|
|
402
|
+
const step = nextRun(qi, si, true);
|
|
403
|
+
if (step === null)
|
|
306
404
|
break;
|
|
405
|
+
if (step.gq > 0 || step.gs > 0) {
|
|
406
|
+
gaps.push({ qs: qi, qe: qi + step.gq, cs: si, ce: si + step.gs });
|
|
407
|
+
}
|
|
408
|
+
matched.push([qi + step.gq, qi + step.gq + step.n]);
|
|
409
|
+
qi = qi + step.gq + step.n;
|
|
410
|
+
si = si + step.gs + step.n;
|
|
307
411
|
}
|
|
308
|
-
// LEFT sweep (mirror)
|
|
412
|
+
// LEFT sweep (mirror): an independent walk, so an exhausted right side can
|
|
413
|
+
// never starve it (pinned by test/114).
|
|
309
414
|
qi = qs;
|
|
310
415
|
si = ss;
|
|
311
416
|
for (;;) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
|
|
315
|
-
const gs = total - gq;
|
|
316
|
-
if (gs > reachCap)
|
|
317
|
-
continue;
|
|
318
|
-
if (qi - gq <= 0 || si - gs <= 0)
|
|
319
|
-
continue;
|
|
320
|
-
// Run ENDING at (qi - gq, si - gs).
|
|
321
|
-
let n = 0;
|
|
322
|
-
while (n < qi - gq && n < si - gs &&
|
|
323
|
-
q[qi - gq - 1 - n] === c[si - gs - 1 - n]) {
|
|
324
|
-
n++;
|
|
325
|
-
}
|
|
326
|
-
if (n >= W || n === qi - gq) {
|
|
327
|
-
if (n === 0)
|
|
328
|
-
continue;
|
|
329
|
-
if (gq > 0 || gs > 0) {
|
|
330
|
-
gaps.push({ qs: qi - gq, qe: qi, cs: si - gs, ce: si });
|
|
331
|
-
}
|
|
332
|
-
matched.push([qi - gq - n, qi - gq]);
|
|
333
|
-
qi = qi - gq - n;
|
|
334
|
-
si = si - gs - n;
|
|
335
|
-
found = true;
|
|
336
|
-
break;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
if (!found)
|
|
417
|
+
const step = nextRun(qi, si, false);
|
|
418
|
+
if (step === null)
|
|
341
419
|
break;
|
|
420
|
+
if (step.gq > 0 || step.gs > 0) {
|
|
421
|
+
gaps.push({ qs: qi - step.gq, qe: qi, cs: si - step.gs, ce: si });
|
|
422
|
+
}
|
|
423
|
+
matched.push([qi - step.gq - step.n, qi - step.gq]);
|
|
424
|
+
qi = qi - step.gq - step.n;
|
|
425
|
+
si = si - step.gs - step.n;
|
|
342
426
|
}
|
|
343
427
|
return { matched, gaps };
|
|
344
428
|
}
|
|
@@ -462,7 +546,7 @@ export function frameSlots(ctx, query, cand, id) {
|
|
|
462
546
|
* ANCHOR that the query displaced. Neither implies the other, and the
|
|
463
547
|
* observed failures pass the restatement guard cleanly.
|
|
464
548
|
*
|
|
465
|
-
*
|
|
549
|
+
* Four conditions, all byte-exact and all necessary:
|
|
466
550
|
*
|
|
467
551
|
* 1. the query and the anchor must be ONE STRUCTURE — what they share has to
|
|
468
552
|
* dominate the query, or the query is not a variant of the anchor at all
|
|
@@ -547,8 +631,10 @@ export function substituteAll(hay, pairs) {
|
|
|
547
631
|
if (usable.length === 0)
|
|
548
632
|
return hay;
|
|
549
633
|
// Longest needle first, so a needle that is a prefix of another can never
|
|
550
|
-
// pre-empt it. Ties cannot arise:
|
|
551
|
-
// pairwise distinct
|
|
634
|
+
// pre-empt it. Ties cannot arise: a consumer that VOICES checks the
|
|
635
|
+
// fillers pairwise with `distinct` and refuses such an instance itself —
|
|
636
|
+
// `frameSlots` reports and does not judge (see its own doc), so the refusal
|
|
637
|
+
// lives with the mechanism that needs it, not here.
|
|
552
638
|
const order = [...usable].sort((a, b) => b.needle.length - a.needle.length);
|
|
553
639
|
const out = [];
|
|
554
640
|
let i = 0;
|
|
@@ -16,7 +16,7 @@ import { analogyStrength, follow, project, reverseContext, sharedFrameStrengthOf
|
|
|
16
16
|
import { joinWithBridge } from "../resonance.js";
|
|
17
17
|
import { restatesQuery } from "../reasoning.js";
|
|
18
18
|
import { CONCEPT, STEP } from "../graph-search.js";
|
|
19
|
-
import {
|
|
19
|
+
import { indexOf } from "../../bytes.js";
|
|
20
20
|
import { consensusFloor, dominates } from "../../geometry.js";
|
|
21
21
|
import { unexplainedLabel, unexplainedSpans, } from "../rationale.js";
|
|
22
22
|
import { rItem, rNode } from "../trace.js";
|
|
@@ -523,7 +523,23 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
523
523
|
const fwd = await follow(ctx, proj.anchor, qv);
|
|
524
524
|
if (fwd !== null && indexOf(answer, fwd, 0) < 0 &&
|
|
525
525
|
!restatesQuery(query, fwd)) {
|
|
526
|
-
|
|
526
|
+
// THROUGH THE SHARED JOINER, not a bare concatenation.
|
|
527
|
+
//
|
|
528
|
+
// `joinWithBridge` is the composition step every out-of-search assembly
|
|
529
|
+
// shares (multi-topic fusion, CAST's substitution and comparison): it
|
|
530
|
+
// asks the corpus for a learnt connector between the pieces and, on a
|
|
531
|
+
// miss, joins them BARE **and says so** — the `bridgeMiss` step (see
|
|
532
|
+
// resonance.ts). This site bypassed it, and that is the whole of the
|
|
533
|
+
// gluing the study measured: `"Steel is hard"` + `"wet"` came back as
|
|
534
|
+
// `"hardwet"`, `"eva director father"` + `"The father of…"` as
|
|
535
|
+
// `"fatherThe"` — compositions no rationale could show, because the one
|
|
536
|
+
// step that made them left no trace.
|
|
537
|
+
//
|
|
538
|
+
// Routing it through the shared joiner is the instrumentation fix that
|
|
539
|
+
// comes first: a bare join stays possible (the house rule is "joined
|
|
540
|
+
// bare, never silent") but it is now VISIBLE, and an attested connector
|
|
541
|
+
// is used when the corpus has one.
|
|
542
|
+
answer = await joinWithBridge(ctx, answer, fwd);
|
|
527
543
|
}
|
|
528
544
|
ctx.trace?.step("projectCounterfactual", [
|
|
529
545
|
rItem(filler, "filler", subj.point.anchor),
|
|
@@ -715,7 +731,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
715
731
|
// grounds") — fine for ORIENTING mechanisms, not for voicing learnt
|
|
716
732
|
// content the query never asked about. Computed once here; both the
|
|
717
733
|
// hub fallback below and the comparison gate consume it.
|
|
718
|
-
const rootTrusted = roots.some((r) => r.
|
|
734
|
+
const rootTrusted = roots.some((r) => r.idfVote >= consensusFloor(corpusN(ctx))); // the IDF sum: the bar's own quantity
|
|
719
735
|
// The context that ESTABLISHES a filler — the same reverse context, under
|
|
720
736
|
// the same naming test, `seatOfNode` uses to VOICE an analog (a predecessor
|
|
721
737
|
// whose bytes CONTAIN the node's: it names or describes it, rather than
|
|
@@ -98,6 +98,30 @@ export async function confluenceJoin(ctx, query, pre) {
|
|
|
98
98
|
// constraints). Shard-bound streams are no constraints, and their meets
|
|
99
99
|
// are connective debris (". Sure,", "ngul" — observed).
|
|
100
100
|
const bindsAConstituent = (cover) => cover.some(([cs, ce]) => ce - cs >= 2 * W);
|
|
101
|
+
// THE VOTE ENTERS AS ORDER, NEVER AS A BAR. This is the only one of the
|
|
102
|
+
// climb's four consumers (recall, fuseAttention, cast, here) that uses the
|
|
103
|
+
// evidence's MAGNITUDE without a floor, and it is legitimate by construction:
|
|
104
|
+
// `ranked` answers "which anchor is stronger" — a question about votes, so the
|
|
105
|
+
// comparison stays within one dimension — and the vote is otherwise only
|
|
106
|
+
// REPORTED (Stream.vote travels to the rationale's constraint nodes). What
|
|
107
|
+
// actually SELECTS a constraint is byte-structural and never the magnitude: a
|
|
108
|
+
// run of at least 2W (`bindsAConstituent`, with its accidental-sharing
|
|
109
|
+
// counter-examples above), disjoint covers (`disjoint`), and scaffolding never
|
|
110
|
+
// binds at all (`dominates(reachOf(…), N)`). The MEET such a stream may
|
|
111
|
+
// produce is selected the same way: a span shorter than 2W is rejected, and
|
|
112
|
+
// the winner is the one with the smallest `reach` (ties broken by the longer
|
|
113
|
+
// span) — a corpus quantity and bytes, never the vote, which appears only in
|
|
114
|
+
// the trace item.
|
|
115
|
+
// binds at all (`dominates(reachOf(…), N)`). The only cut in this loop is a
|
|
116
|
+
// BUDGET, and it is measured: stopping the scan at 2W anchors saves 50-70% of
|
|
117
|
+
// confluence's cost on non-conjunctive queries while preserving every genuinely
|
|
118
|
+
// conjunctive case, whose top anchors ARE its constraints.
|
|
119
|
+
// MEASURED (this goal, on THIS file's own conjunctive fixture): the two
|
|
120
|
+
// streams appear at ranks 1 and 4 against a budget of 2W = 8, on a query whose
|
|
121
|
+
// `ranked` is 9 — so the cut IS live (it would have returned null at the 8th
|
|
122
|
+
// anchor) and it does NOT prune the case it exists to protect. The other
|
|
123
|
+
// conjunctive fixture (the Leonardo one) finds them at ranks 0 and 1. Scope:
|
|
124
|
+
// these are the repo's conjunctive fixtures, and no more.
|
|
101
125
|
const streams = [];
|
|
102
126
|
const rankedCapped = ranked.length > pre.k ? ranked.slice(0, pre.k) : ranked;
|
|
103
127
|
// CONJUNCTIVITY EARLY-EXIT: a conjunctive query's top-ranked anchors
|
|
@@ -83,6 +83,8 @@ export async function resolveConnectors(ctx, sites, query) {
|
|
|
83
83
|
const bridgePair = async (l, r) => {
|
|
84
84
|
if (l === r || links.has(l + "," + r))
|
|
85
85
|
return;
|
|
86
|
+
if (ctx.meter)
|
|
87
|
+
ctx.meter.coverBridges++;
|
|
86
88
|
const link = await bridge(ctx, read(ctx, l), read(ctx, r));
|
|
87
89
|
if (link !== null)
|
|
88
90
|
links.set(l + "," + r, link);
|
|
@@ -122,6 +124,10 @@ export async function resolveConnectors(ctx, sites, query) {
|
|
|
122
124
|
// plus one W-quantum of glue per joint — pass that allowance so the
|
|
123
125
|
// bridge's phrase-scale cap admits the whole learnt run.
|
|
124
126
|
const allowance = middleBytes + (m + 1) * W;
|
|
127
|
+
if (ctx.meter) {
|
|
128
|
+
ctx.meter.coverBridges++;
|
|
129
|
+
ctx.meter.coverAllowanceBytes += allowance;
|
|
130
|
+
}
|
|
125
131
|
const interior = await bridge(ctx, first.bytes, orderedNodes[m].bytes, allowance);
|
|
126
132
|
if (interior !== null)
|
|
127
133
|
links.set(key, interior);
|
|
@@ -194,9 +194,36 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
194
194
|
// consensus", while breadth is the SCALE-INVARIANT reading — "a point whose
|
|
195
195
|
// breadth clears `dominates` (> half the query's regions corroborate it) is
|
|
196
196
|
// real consensus; one that does not is a coincidental single-region echo".
|
|
197
|
-
//
|
|
198
|
-
// comparing a POOLED SUM against a floor that prices ONE region's
|
|
199
|
-
// is a dimensional error.
|
|
197
|
+
// THIS USED TO CLAIM A DIMENSIONAL ERROR, AND THAT CLAIM WAS FALSE.
|
|
198
|
+
// It read: "comparing a POOLED SUM against a floor that prices ONE region's
|
|
199
|
+
// evidence is a dimensional error." `consensusFloor` is not priced for one
|
|
200
|
+
// region: thresholds.md §2 derives it as the POOLED-vote significance floor —
|
|
201
|
+
// "each region contributes at most ln(N/c) <= ln(N); ln(N)+1/2 demands ..." —
|
|
202
|
+
// and attention.ts says the same where it builds the vote ("the scale
|
|
203
|
+
// consensusFloor is derived for"). The comparison is in ONE dimension, and
|
|
204
|
+
// it is so because the climb WEIGHTS BY IDF: `wf` in voteRegions is
|
|
205
|
+
// `direct ? df : combined ? idf + df : idf`, and the engine only ever runs the
|
|
206
|
+
// last one (DFMode's default "inverse", the mode every non-test caller uses —
|
|
207
|
+
// `direct` and `combined` are exercised by test/24 and test/27 only, and
|
|
208
|
+
// test/24 pins that their votes DO differ). In those two the sum would leave
|
|
209
|
+
// the floor's dimension and the floor would need re-deriving.
|
|
210
|
+
//
|
|
211
|
+
// What the OR below is really for is SCALE, not dimension (the paragraph
|
|
212
|
+
// above says it): a vote that clears ln(N)+1/2 means "strong" on a small store
|
|
213
|
+
// and "weak" on a large one for the same genuine consensus, so the
|
|
214
|
+
// scale-invariant breadth reading is added beside it.
|
|
215
|
+
//
|
|
216
|
+
// AND THE PREMISE IS IDF. The deviation in the other two weighting modes is
|
|
217
|
+
// TWO-SIDED and DERIVED: `direct` DEFLATES a region (ln(1+c) < ln(N/c) for
|
|
218
|
+
// small c) and `combined` INFLATES it (ln N + ln(1+1/c)), both by at most
|
|
219
|
+
// `ln 2` — see `geometry.ts`'s `consensusFloor`, where the bound lives.
|
|
220
|
+
// MEASURED on 8 anchors across 5 queries, running the same climb in all
|
|
221
|
+
// three modes: ZERO gate inversions — every anchor's `vote >= floor` verdict
|
|
222
|
+
// is the same in `inverse`, `direct` and `combined`, even where the readings
|
|
223
|
+
// straddle the floor on opposite sides (#148: inverse 3.39, combined 4.71
|
|
224
|
+
// above it, direct 1.31 below). Pinned by test/55's test 20. The bar is not
|
|
225
|
+
// re-derived for those modes because nothing reachable needs it; the premise
|
|
226
|
+
// is IDF, and that is now written where the gate reads it.
|
|
200
227
|
//
|
|
201
228
|
// Measured on the 15.7M-node store (N=325,615, so the old floor was 13.19).
|
|
202
229
|
// The absolute vote cannot separate right from wrong at this scale, and the
|
|
@@ -265,7 +292,7 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
265
292
|
const minVote = consensusFloor(corpusN(ctx));
|
|
266
293
|
if (forest.length > 0 &&
|
|
267
294
|
!allWindowsAreScaffolding(ctx, query) &&
|
|
268
|
-
(forest[0].
|
|
295
|
+
(forest[0].idfVote >= minVote || // the IDF sum: the bar's own quantity
|
|
269
296
|
(dominates(forest[0].breadth, 1) && forest[0].peak > Math.LN2))) {
|
|
270
297
|
const g = await project(ctx, forest[0].anchor, queryGist);
|
|
271
298
|
// THE ANCHOR'S OCCUPANT IS NOT THE ASKER'S. This tier grounds an anchor
|
|
@@ -468,6 +495,7 @@ export const recallMechanism = {
|
|
|
468
495
|
moves: r.moves,
|
|
469
496
|
unexplained: r.unexplained,
|
|
470
497
|
provenance: r.echoed ? "recall-echo" : "recall",
|
|
498
|
+
used: new Set(),
|
|
471
499
|
...(r.complete ? { complete: true } : {}),
|
|
472
500
|
}];
|
|
473
501
|
},
|
package/dist/src/mind/mind.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Vec } from "../vec.js";
|
|
2
2
|
import { Sema, Space } from "../sema.js";
|
|
3
|
+
import type { CorpusResult } from "./corpus.js";
|
|
3
4
|
import { Alphabet } from "../alphabet.js";
|
|
4
5
|
import { Grid } from "../geometry.js";
|
|
5
6
|
import { BoundedMap, type Store } from "../store.js";
|
|
@@ -49,10 +50,42 @@ import type { AttentionRead, MindContext, Recognition } from "./types.js";
|
|
|
49
50
|
export type { AnchorRejectionReason, ClimbConsensusData, ConsensusAnchorTrace, ConsensusReachTrace, ConsensusRegionTrace, CrossRegionTier, JunctionVoteTrace, RegionOutcome, } from "./attention.js";
|
|
50
51
|
export type { AncestorReach, SaturationReason, SaturationStop, } from "./types.js";
|
|
51
52
|
import { type CostReport, Meter } from "../meter.js";
|
|
53
|
+
/** A stored pair as TEXT — the text helper's view of {@link CorpusPair}. */
|
|
54
|
+
export interface CorpusTextPair {
|
|
55
|
+
context: string;
|
|
56
|
+
continuation: string;
|
|
57
|
+
contextId: number;
|
|
58
|
+
continuationId: number;
|
|
59
|
+
matchedBytes: number;
|
|
60
|
+
contextTruncated: boolean;
|
|
61
|
+
continuationTruncated: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** {@link CorpusResult} as text, plus the prose for why nothing matched. The
|
|
64
|
+
* byte layer reports a STATE; saying it in words belongs to the text layer. */
|
|
65
|
+
export interface CorpusTextResult {
|
|
66
|
+
query: string;
|
|
67
|
+
pairs: CorpusTextPair[];
|
|
68
|
+
resolved: number;
|
|
69
|
+
reached: number;
|
|
70
|
+
totalContexts: number;
|
|
71
|
+
browsed: boolean;
|
|
72
|
+
note?: string;
|
|
73
|
+
}
|
|
52
74
|
export interface MindOptions {
|
|
53
75
|
seed?: number;
|
|
54
76
|
recallQueryK?: number;
|
|
55
77
|
haloQueryK?: number;
|
|
78
|
+
/** Branch nodes the pivot sweep may probe — see {@link MindConfig}. */
|
|
79
|
+
pivotProbeK?: number;
|
|
80
|
+
/** Items one rationale step may itemise — see {@link MindConfig}. */
|
|
81
|
+
rationaleSampleK?: number;
|
|
82
|
+
/** Corpus-reading capacities and budgets — see {@link MindConfig}. */
|
|
83
|
+
corpusLimitMax?: number;
|
|
84
|
+
corpusClimbs?: number;
|
|
85
|
+
corpusContextsPerClimb?: number;
|
|
86
|
+
corpusSampleProbes?: number;
|
|
87
|
+
corpusPreviewBytes?: number;
|
|
88
|
+
corpusSampleFloorBytes?: number;
|
|
56
89
|
normalizeEpsilon?: number;
|
|
57
90
|
cosineEpsilon?: number;
|
|
58
91
|
geometry?: Partial<import("../config.js").GeometryConfig>;
|
|
@@ -153,6 +186,10 @@ export declare class Mind implements MindContext {
|
|
|
153
186
|
* `traverse.ts`'s ONE definition (edge or halo, with its response-scoped
|
|
154
187
|
* cache). The search holds a bare Store and cannot reach that cache itself,
|
|
155
188
|
* so it asks through this hook; a bare host keeps its raw-store fallback. */
|
|
189
|
+
/** The canonical identity for the search (see GraphSearchHost). */
|
|
190
|
+
canonResolve(bytes: Uint8Array): number | null;
|
|
191
|
+
/** Feed a search refusal into the rationale (see GraphSearchHost). */
|
|
192
|
+
reportSearch(name: string, parts: ReadonlyArray<Uint8Array>, note: string): void;
|
|
156
193
|
leadsSomewhere(id: number): boolean;
|
|
157
194
|
recogniseSpan(bytes: Uint8Array): {
|
|
158
195
|
sites: ReadonlyArray<Site>;
|
|
@@ -167,6 +204,8 @@ export declare class Mind implements MindContext {
|
|
|
167
204
|
* with the most distributional evidence (highest `prevOf` count — the
|
|
168
205
|
* structural manifestation of its halo). When evidence is equal the
|
|
169
206
|
* first-inserted edge wins. */
|
|
207
|
+
/** See {@link GraphSearchHost.contentKeyEnds}. */
|
|
208
|
+
contentKeyEnds(prefix: Uint8Array, tail: Uint8Array): readonly number[];
|
|
170
209
|
chooseNext(node: number): number | undefined;
|
|
171
210
|
constructor(opts?: MindOptions);
|
|
172
211
|
constructor(cfg: MindConfig, store: Store, _fromStore: true);
|
|
@@ -249,6 +288,24 @@ export declare class Mind implements MindContext {
|
|
|
249
288
|
* as one form, provided the store's canon index is built
|
|
250
289
|
* ({@link buildCanonIndex}). */
|
|
251
290
|
respondText(input: string, inspectRationale?: InspectRationale): Promise<string>;
|
|
291
|
+
/** Which stored notes does this query REACH? BYTES in, BYTES out — this
|
|
292
|
+
* method has no notion of text or encoding; the text case is
|
|
293
|
+
* {@link searchCorpusText}, which is one caller of this.
|
|
294
|
+
*
|
|
295
|
+
* Exact content addressing through the machinery an answer already uses
|
|
296
|
+
* (see src/mind/corpus.ts): the query's recognised sites are the resolved
|
|
297
|
+
* subtrees, the climb goes up from the biggest, and a result is a context
|
|
298
|
+
* that carries a learnt continuation. Nothing is written and nothing is
|
|
299
|
+
* indexed. */
|
|
300
|
+
searchCorpus(queryBytes: Uint8Array, limit?: number): CorpusResult;
|
|
301
|
+
/** Browse real pairs. Deterministic: `from` is the caller's own offset in
|
|
302
|
+
* [0,1), so browsing twice with different offsets shows different notes
|
|
303
|
+
* without a random draw. */
|
|
304
|
+
sampleCorpus(limit?: number, from?: number): CorpusResult;
|
|
305
|
+
/** The TEXT case of {@link searchCorpus}: encode, search, decode. The search
|
|
306
|
+
* itself exists once, in the byte layer above; only the rendering lives
|
|
307
|
+
* here, with the rest of this class's text modality. */
|
|
308
|
+
searchCorpusText(query: string, limit?: number): CorpusTextResult;
|
|
252
309
|
/** Begin a new conversation, optionally restoring from a previously-saved
|
|
253
310
|
* {@link ConversationState}. The returned handle is required for
|
|
254
311
|
* {@link respondTurn} and {@link endConversation}.
|
package/dist/src/mind/mind.js
CHANGED
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
// Architecture: 4 primitives × 2 patterns = all inference.
|
|
10
10
|
// Implementation split across src/mind/*.ts — this file assembles the Mind class.
|
|
11
11
|
import { makeKeyring, rng, setVecConfig } from "../vec.js";
|
|
12
|
+
import { sampleCorpus, searchCorpus } from "./corpus.js";
|
|
12
13
|
import { Alphabet } from "../alphabet.js";
|
|
13
14
|
import { contentFoldIncremental, reachThreshold, } from "../geometry.js";
|
|
15
|
+
import { keyEnds } from "./canonical.js";
|
|
14
16
|
import { BoundedMap } from "../store.js";
|
|
15
17
|
import { SQliteStore } from "../store-sqlite.js";
|
|
16
18
|
import { resolveConfig } from "../config.js";
|
|
@@ -19,7 +21,7 @@ import { bytesEqual, concat2 } from "../bytes.js";
|
|
|
19
21
|
import { GraphSearch, } from "./graph-search.js";
|
|
20
22
|
import { Alu } from "../alu/src/index.js";
|
|
21
23
|
import { decodeText, Rationale, } from "./rationale.js";
|
|
22
|
-
import { gistOf, inputBytes, perceive as perceiveImpl, perceiveKey, resolve as resolveImpl, } from "./primitives.js";
|
|
24
|
+
import { canonResolve as canonResolveImpl, gistOf, inputBytes, perceive as perceiveImpl, perceiveKey, resolve as resolveImpl, } from "./primitives.js";
|
|
23
25
|
import { chooseNext, edgeAncestors as edgeAncestorsFn, invalidateStructuralCaches, leadsSomewhere, } from "./traverse.js";
|
|
24
26
|
import { invalidateJunctionCache } from "./junction.js";
|
|
25
27
|
import { follow } from "./match.js";
|
|
@@ -33,6 +35,21 @@ import { rItem } from "./trace.js";
|
|
|
33
35
|
// The work meter is exported from src/index.ts (via src/meter.ts) — the one
|
|
34
36
|
// definition; the Mind only consumes it.
|
|
35
37
|
import { Meter } from "../meter.js";
|
|
38
|
+
/** What the text helper says when the byte layer reports a miss. */
|
|
39
|
+
const CORPUS_NOTE = {
|
|
40
|
+
"nothing-resolved": "No trained note sits above the parts of that text the mind recognised. " +
|
|
41
|
+
"It addresses content exactly, so try wording closer to something it was " +
|
|
42
|
+
"actually given — or browse the examples instead.",
|
|
43
|
+
"no-continuations": "That text reaches stored nodes, but none of them carries a learnt " +
|
|
44
|
+
"continuation.",
|
|
45
|
+
};
|
|
46
|
+
/** UTF-8 of bytes for display: reuse {@link decodeText} (the mind's own text
|
|
47
|
+
* conversion), then drop the replacement character a byte-boundary cut leaves
|
|
48
|
+
* behind. Much of a real corpus is non-Latin, so that trailing U+FFFD is the
|
|
49
|
+
* common case, not an exotic one — and it is the ONLY thing added here. */
|
|
50
|
+
function previewCorpusText(bytes) {
|
|
51
|
+
return decodeText(bytes).replace(/\uFFFD+$/, "").replace(/\s+/g, " ").trim();
|
|
52
|
+
}
|
|
36
53
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
37
54
|
// THE MIND
|
|
38
55
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -116,6 +133,14 @@ export class Mind {
|
|
|
116
133
|
* `traverse.ts`'s ONE definition (edge or halo, with its response-scoped
|
|
117
134
|
* cache). The search holds a bare Store and cannot reach that cache itself,
|
|
118
135
|
* so it asks through this hook; a bare host keeps its raw-store fallback. */
|
|
136
|
+
/** The canonical identity for the search (see GraphSearchHost). */
|
|
137
|
+
canonResolve(bytes) {
|
|
138
|
+
return canonResolveImpl(this, bytes);
|
|
139
|
+
}
|
|
140
|
+
/** Feed a search refusal into the rationale (see GraphSearchHost). */
|
|
141
|
+
reportSearch(name, parts, note) {
|
|
142
|
+
this.trace?.step(name, parts.map((b) => rItem(b)), [], note);
|
|
143
|
+
}
|
|
119
144
|
leadsSomewhere(id) {
|
|
120
145
|
return leadsSomewhere(this, id);
|
|
121
146
|
}
|
|
@@ -136,6 +161,10 @@ export class Mind {
|
|
|
136
161
|
* with the most distributional evidence (highest `prevOf` count — the
|
|
137
162
|
* structural manifestation of its halo). When evidence is equal the
|
|
138
163
|
* first-inserted edge wins. */
|
|
164
|
+
/** See {@link GraphSearchHost.contentKeyEnds}. */
|
|
165
|
+
contentKeyEnds(prefix, tail) {
|
|
166
|
+
return keyEnds(this, prefix, tail);
|
|
167
|
+
}
|
|
139
168
|
chooseNext(node) {
|
|
140
169
|
return chooseNext(this, node, this._edgeGuide);
|
|
141
170
|
}
|
|
@@ -411,6 +440,48 @@ export class Mind {
|
|
|
411
440
|
const r = await this.respond(input, inspectRationale);
|
|
412
441
|
return decodeText(r.bytes);
|
|
413
442
|
}
|
|
443
|
+
// ── Reading the trained memory back ─────────────────────────────────────
|
|
444
|
+
/** Which stored notes does this query REACH? BYTES in, BYTES out — this
|
|
445
|
+
* method has no notion of text or encoding; the text case is
|
|
446
|
+
* {@link searchCorpusText}, which is one caller of this.
|
|
447
|
+
*
|
|
448
|
+
* Exact content addressing through the machinery an answer already uses
|
|
449
|
+
* (see src/mind/corpus.ts): the query's recognised sites are the resolved
|
|
450
|
+
* subtrees, the climb goes up from the biggest, and a result is a context
|
|
451
|
+
* that carries a learnt continuation. Nothing is written and nothing is
|
|
452
|
+
* indexed. */
|
|
453
|
+
searchCorpus(queryBytes, limit) {
|
|
454
|
+
return searchCorpus(this, queryBytes, limit);
|
|
455
|
+
}
|
|
456
|
+
/** Browse real pairs. Deterministic: `from` is the caller's own offset in
|
|
457
|
+
* [0,1), so browsing twice with different offsets shows different notes
|
|
458
|
+
* without a random draw. */
|
|
459
|
+
sampleCorpus(limit, from) {
|
|
460
|
+
return sampleCorpus(this, limit, from);
|
|
461
|
+
}
|
|
462
|
+
/** The TEXT case of {@link searchCorpus}: encode, search, decode. The search
|
|
463
|
+
* itself exists once, in the byte layer above; only the rendering lives
|
|
464
|
+
* here, with the rest of this class's text modality. */
|
|
465
|
+
searchCorpusText(query, limit) {
|
|
466
|
+
const result = this.searchCorpus(new TextEncoder().encode(query), limit);
|
|
467
|
+
return {
|
|
468
|
+
query,
|
|
469
|
+
pairs: result.pairs.map((p) => ({
|
|
470
|
+
context: previewCorpusText(p.context),
|
|
471
|
+
continuation: previewCorpusText(p.continuation),
|
|
472
|
+
contextId: p.contextId,
|
|
473
|
+
continuationId: p.continuationId,
|
|
474
|
+
matchedBytes: p.matchedBytes,
|
|
475
|
+
contextTruncated: p.contextTruncated,
|
|
476
|
+
continuationTruncated: p.continuationTruncated,
|
|
477
|
+
})),
|
|
478
|
+
resolved: result.resolved,
|
|
479
|
+
reached: result.reached,
|
|
480
|
+
totalContexts: result.totalContexts,
|
|
481
|
+
browsed: result.browsed,
|
|
482
|
+
note: result.miss === "matched" ? undefined : CORPUS_NOTE[result.miss],
|
|
483
|
+
};
|
|
484
|
+
}
|
|
414
485
|
// ── Conversation API ────────────────────────────────────────────────────
|
|
415
486
|
/** Begin a new conversation, optionally restoring from a previously-saved
|
|
416
487
|
* {@link ConversationState}. The returned handle is required for
|
|
@@ -151,6 +151,13 @@ export interface MechanismResult {
|
|
|
151
151
|
bytes: Uint8Array;
|
|
152
152
|
accounted: Array<[number, number]>;
|
|
153
153
|
moves: number;
|
|
154
|
+
/** WHAT THIS ANSWER SPEAKS FOR — the anchors it voices, and therefore the
|
|
155
|
+
* content the reasoner must not pivot back through. Declared by the
|
|
156
|
+
* mechanism about its OWN result, exactly like `accounted`/`unexplained`/
|
|
157
|
+
* `complete`: post-grounding honours the property and NEVER ASKS WHICH
|
|
158
|
+
* MECHANISM SET IT, so the market stays uniform. An EMPTY set is a real
|
|
159
|
+
* declaration — "this answer voices nothing" (recall) — and withholds
|
|
160
|
+
* nothing; omit the field and the pipeline re-recognises the answer. */
|
|
154
161
|
used?: ReadonlySet<number>;
|
|
155
162
|
unexplained: string;
|
|
156
163
|
/** Explicit weight override. When absent, weight = moves + PASS·unaccounted. */
|