@hviana/sema 0.2.9 → 0.4.0

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.
Files changed (84) hide show
  1. package/AGENTS.md +71 -5
  2. package/dist/src/derive/src/deduction.d.ts +12 -1
  3. package/dist/src/derive/src/deduction.js +5 -1
  4. package/dist/src/derive/src/index.d.ts +1 -0
  5. package/dist/src/geometry.d.ts +3 -30
  6. package/dist/src/geometry.js +330 -82
  7. package/dist/src/index.d.ts +1 -0
  8. package/dist/src/index.js +1 -0
  9. package/dist/src/meter.d.ts +171 -0
  10. package/dist/src/meter.js +269 -0
  11. package/dist/src/mind/attention.d.ts +5 -4
  12. package/dist/src/mind/attention.js +254 -23
  13. package/dist/src/mind/bridge.d.ts +10 -1
  14. package/dist/src/mind/bridge.js +179 -10
  15. package/dist/src/mind/canonical.d.ts +6 -1
  16. package/dist/src/mind/canonical.js +6 -1
  17. package/dist/src/mind/graph-search.d.ts +9 -0
  18. package/dist/src/mind/graph-search.js +59 -19
  19. package/dist/src/mind/index.d.ts +2 -0
  20. package/dist/src/mind/junction.d.ts +10 -0
  21. package/dist/src/mind/junction.js +14 -0
  22. package/dist/src/mind/learning.d.ts +32 -4
  23. package/dist/src/mind/learning.js +26 -4
  24. package/dist/src/mind/match.d.ts +40 -0
  25. package/dist/src/mind/match.js +125 -1
  26. package/dist/src/mind/mechanisms/cast.js +63 -6
  27. package/dist/src/mind/mechanisms/extraction.d.ts +0 -34
  28. package/dist/src/mind/mechanisms/extraction.js +1 -88
  29. package/dist/src/mind/mechanisms/recall.d.ts +3 -0
  30. package/dist/src/mind/mechanisms/recall.js +77 -14
  31. package/dist/src/mind/mind.d.ts +59 -5
  32. package/dist/src/mind/mind.js +115 -93
  33. package/dist/src/mind/pipeline-mechanism.d.ts +33 -3
  34. package/dist/src/mind/pipeline-mechanism.js +179 -10
  35. package/dist/src/mind/pipeline.d.ts +29 -0
  36. package/dist/src/mind/pipeline.js +79 -21
  37. package/dist/src/mind/primitives.d.ts +11 -15
  38. package/dist/src/mind/primitives.js +47 -28
  39. package/dist/src/mind/reasoning.d.ts +7 -1
  40. package/dist/src/mind/reasoning.js +40 -8
  41. package/dist/src/mind/recognition.js +93 -20
  42. package/dist/src/mind/traverse.d.ts +11 -0
  43. package/dist/src/mind/traverse.js +88 -7
  44. package/dist/src/mind/types.d.ts +39 -5
  45. package/dist/src/store.d.ts +15 -0
  46. package/dist/src/store.js +91 -6
  47. package/package.json +1 -1
  48. package/src/derive/src/deduction.ts +15 -0
  49. package/src/derive/src/index.ts +1 -0
  50. package/src/geometry.ts +350 -122
  51. package/src/index.ts +1 -0
  52. package/src/meter.ts +333 -0
  53. package/src/mind/attention.ts +276 -31
  54. package/src/mind/bridge.ts +187 -10
  55. package/src/mind/canonical.ts +6 -1
  56. package/src/mind/graph-search.ts +60 -21
  57. package/src/mind/index.ts +6 -0
  58. package/src/mind/junction.ts +12 -0
  59. package/src/mind/learning.ts +46 -5
  60. package/src/mind/match.ts +146 -1
  61. package/src/mind/mechanisms/cast.ts +62 -6
  62. package/src/mind/mechanisms/extraction.ts +2 -103
  63. package/src/mind/mechanisms/recall.ts +84 -17
  64. package/src/mind/mind.ts +144 -99
  65. package/src/mind/pipeline-mechanism.ts +203 -13
  66. package/src/mind/pipeline.ts +144 -36
  67. package/src/mind/primitives.ts +49 -33
  68. package/src/mind/reasoning.ts +39 -7
  69. package/src/mind/recognition.ts +89 -19
  70. package/src/mind/traverse.ts +89 -8
  71. package/src/mind/types.ts +39 -5
  72. package/src/store.ts +75 -6
  73. package/test/14-scaling.test.mjs +17 -7
  74. package/test/31-audit.test.mjs +4 -1
  75. package/test/33-multi-candidate.test.mjs +13 -1
  76. package/test/36-already-answered-fusion.test.mjs +10 -3
  77. package/test/46-recognise-multibyte-edge.test.mjs +3 -3
  78. package/test/53-cross-region-probe-instrumentation.test.mjs +36 -6
  79. package/test/54-evidence-k-instrumentation.test.mjs +175 -0
  80. package/test/55-cost-meter.test.mjs +284 -0
  81. package/test/56-bridge-identity-admission.test.mjs +209 -0
  82. package/test/57-fusion-order.test.mjs +104 -0
  83. package/test/58-subquantum-sites.test.mjs +112 -0
  84. package/test/59-fold-invariance.test.mjs +226 -0
@@ -28,7 +28,7 @@ import { cosine } from "../vec.js";
28
28
  import { conceptThreshold, identityBar, significanceBar } from "../geometry.js";
29
29
  import { indexOf } from "../bytes.js";
30
30
  import { leafIdRun } from "./canonical.js";
31
- import { gistOf, read, resolve } from "./primitives.js";
31
+ import { foldTree, gistOf, perceive, read, resolve } from "./primitives.js";
32
32
  import { argmaxCosine, chooseAmong, chooseNext, guidedFirst, hubBound, hubCap, } from "./traverse.js";
33
33
  import { recognise, segment } from "./recognition.js";
34
34
  // ═══════════════════════════════════════════════════════════════════════════
@@ -84,6 +84,25 @@ export function locate(ctx, haystack, needle, fromPos, sites) {
84
84
  * matcher CAST detects a woven query with. Returns non-overlapping runs
85
85
  * sorted by query position. */
86
86
  export function alignRuns(ctx, query, ct) {
87
+ if (ctx.meter) {
88
+ ctx.meter.alignments++;
89
+ // The alignment family's honest unit: the seed index is O(|query|) but
90
+ // the run extension is O(|query|·|ct|) in the worst case, and a weave
91
+ // that starts scanning conversation-length contexts shows up HERE long
92
+ // before it shows up in a call count.
93
+ ctx.meter.alignCells += query.length * ct.length;
94
+ }
95
+ // MEASURED AND REFUTED — seeding at the write side's unit floor W−1 instead
96
+ // of W. `canonicalWindows` interns both lengths precisely so a form
97
+ // straddling a group boundary is reachable from either cut, and the runs
98
+ // found here are exactly such remnants: test/29 C1's query shares only `ce `
99
+ // with `Ice is cold` — three bytes, never seeded at W, so that structure
100
+ // enters the weave carrying nothing but the scaffolding run every exemplar
101
+ // shares. But this is a byte MATCHER between two streams, not an index, and
102
+ // at W−1 the corpus is dense with spurious 3-byte agreements: the extra runs
103
+ // reshuffle which point claims which span, and test/29 A2 loses its analog.
104
+ // C1 does not pass either way. The store's unit floor does not transfer to
105
+ // the aligner's seed length.
87
106
  const quantum = Math.min(ctx.space.maxGroup, ct.length);
88
107
  if (quantum < 1 || query.length < quantum)
89
108
  return [];
@@ -439,3 +458,108 @@ export async function project(ctx, id, guide) {
439
458
  return fc;
440
459
  return reverseContext(ctx, id, guide);
441
460
  }
461
+ // ── The span-shape family ───────────────────────────────────────────────────
462
+ //
463
+ // "Is this answer drawn from this context?" has TWO formally distinct
464
+ // readings, and the pair plus the anchor classifier built on them are SHARED
465
+ // machinery — extraction proposes span-shaped exemplars with them, the
466
+ // shared `Precomputed.spanShapedOf` container computes them, and fusion
467
+ // (reasoning.ts) gates on the strict one. They lived inside
468
+ // mechanisms/extraction.ts, so `pipeline-mechanism.ts` and `reasoning.ts`
469
+ // both had to import back OUT of a specific mechanism — an inversion the
470
+ // mechanism market forbids (AGENTS §2.6: the shared contract may not depend
471
+ // on any one mechanism; §2.5: a shared matcher belongs to this family, never
472
+ // to a mechanism's private helpers). Deleting extraction must not break the
473
+ // shared container, so they live here.
474
+ //
475
+ // • isSpanShaped — the OPEN reading (sparse in-order embedding).
476
+ // • containsSpan — the STRICT reading (contiguous run or resolved node).
477
+ // • skillExemplar — classify one anchor into (context, answer) using them.
478
+ //
479
+ // The two readings are NOT interchangeable; AGENTS §2.5 pins the distinction
480
+ // and each function's own doc states what breaks if it is substituted.
481
+ /** Check whether an anchor is a span-shaped skill exemplar: it represents a
482
+ * fact whose context and answer together form a span-in-context pattern.
483
+ * If the anchor has a nextOf continuation, that is the answer and the anchor
484
+ * itself is the context. Otherwise the anchor's prevOf parents provide
485
+ * candidate contexts, and the longest one whose span is span-shaped wins. */
486
+ export async function skillExemplar(ctx, anchor, guide) {
487
+ if (ctx.store.hasNext(anchor)) {
488
+ const contextBytes = read(ctx, anchor);
489
+ const answerBytes = await follow(ctx, anchor, guide);
490
+ if (answerBytes !== null && isSpanShaped(ctx, contextBytes, answerBytes)) {
491
+ return { contextBytes, answerBytes };
492
+ }
493
+ return null;
494
+ }
495
+ const answerBytes = read(ctx, anchor);
496
+ // Candidate contexts, capped at the hub bound (a common answer's reverse
497
+ // fan-in is corpus-sized).
498
+ const capped = ctx.store.prevFirst(anchor, hubBound(ctx));
499
+ const spanShaped = [];
500
+ for (const p of capped) {
501
+ const ctxB = read(ctx, p);
502
+ if (ctxB.length > 0 && isSpanShaped(ctx, ctxB, answerBytes)) {
503
+ spanShaped.push({ id: p, bytes: ctxB });
504
+ }
505
+ }
506
+ if (spanShaped.length === 0)
507
+ return null;
508
+ // Among span-shaped contexts, the longest wins (the smallest spanning frame
509
+ // heuristic's dual: more frame to locate in the query); the query gist,
510
+ // when given, breaks LENGTH TIES via chooseAmong — the same reverse-regime
511
+ // disambiguator every context pick uses, whose gist cache spares the
512
+ // re-fold this block once paid per tied candidate. Same strict first-seen
513
+ // tie-break as the hand loop it replaces.
514
+ const maxLen = Math.max(...spanShaped.map((s) => s.bytes.length));
515
+ const longest = spanShaped.filter((s) => s.bytes.length === maxLen);
516
+ let contextBytes = longest[0].bytes;
517
+ if (guide && longest.length > 1) {
518
+ const pick = chooseAmong(ctx, longest.map((s) => s.id), guide).id;
519
+ contextBytes = longest.find((s) => s.id === pick).bytes;
520
+ }
521
+ return { contextBytes, answerBytes };
522
+ }
523
+ /** Whether the answer is a SPARSE subsequence of the context (bytes in
524
+ * order, arbitrary gaps) — the OPEN span-shape reading (see the section
525
+ * note above). This is what lets extraction validate a MULTI-PIECE
526
+ * exemplar whose answer is stitched from several context runs — but it is
527
+ * deliberately permissive, so it must never be used as evidence that one
528
+ * span was "drawn from" another (see {@link containsSpan} for that).
529
+ *
530
+ * There is deliberately NO containsSpan pre-check here: strict containment
531
+ * IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
532
+ * whose content-addressed identity means its bytes occur contiguously — is
533
+ * an in-order embedding with zero gaps), so the scan below decides alone,
534
+ * with the same truth value. The old pre-check re-perceived the context
535
+ * (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
536
+ * pure cost, no discrimination. */
537
+ export function isSpanShaped(_ctx, context, answer) {
538
+ let ai = 0;
539
+ for (let ci = 0; ci < context.length && ai < answer.length; ci++) {
540
+ if (context[ci] === answer[ai])
541
+ ai++;
542
+ }
543
+ return ai === answer.length;
544
+ }
545
+ /** STRICT containment: the answer's resolved node appears in the context's
546
+ * folded tree, or the answer occurs as one CONTIGUOUS byte run of the
547
+ * context. This is real evidence the answer was drawn from the context.
548
+ * Fusion gates on this — the sparse-subsequence reading of
549
+ * {@link isSpanShaped} is trivially satisfied by short answers over long
550
+ * queries ("cold" is a gap-tolerant subsequence of most sentences holding
551
+ * c…o…l…d in order), and gating fusion on it silently starved multi-topic
552
+ * queries of their further points of attention. */
553
+ export function containsSpan(ctx, context, answer) {
554
+ const ansId = resolve(ctx, answer);
555
+ if (ansId !== null) {
556
+ let found = false;
557
+ foldTree(ctx, perceive(ctx, context), 0, (_n, _s, _e, node) => {
558
+ if (node === ansId)
559
+ found = true;
560
+ });
561
+ if (found)
562
+ return true;
563
+ }
564
+ return indexOf(context, answer, 0) >= 0;
565
+ }
@@ -273,9 +273,22 @@ export async function counterfactualTransfer(ctx, query, pre) {
273
273
  ? s.ctx.subarray(0, r.cs + (r.qe - r.qs))
274
274
  : query.subarray(r.qs, r.qe);
275
275
  };
276
- const beforeOf = (p, r) => argmaxBy(points.filter((s) => s !== p && lastRun(s).qe <= r.qs &&
276
+ // The subject is the closest structure whose FILLER RUN precedes the seat.
277
+ // The gate is on `runs[0]` — the run `fillerOf` actually reads — not on the
278
+ // point's LAST run: requiring the subject's whole alignment to end before
279
+ // the seat disqualifies any structure the query mentions on BOTH sides of
280
+ // it, which is the shape CAST exists for. Measured on
281
+ // `steel is frigid so steel is ???` against `steel is hard so steel is
282
+ // strong` (runs "steel is " at 0-9 and "d so steel is " at 14-28) and
283
+ // `water is frigid so water is freezing` (seat "frigi" at 9-14): the
284
+ // subject's filler run sits squarely before the seat, but its second run —
285
+ // the recurrence AFTER it, the very thing that makes the sentence an
286
+ // analogy — pushed lastRun past the seat and no substitution fired at all.
287
+ // The ordering key follows the gate to the same run, so "closest preceding"
288
+ // still means closest by the evidence actually used.
289
+ const beforeOf = (p, r) => argmaxBy(points.filter((s) => s !== p && s.runs[0].qe <= r.qs &&
277
290
  s.runs[0].cs < quantum &&
278
- usable(s.runs[0].qs, s.runs[0].qe)), (s) => lastRun(s).qs, -Infinity, true)?.item;
291
+ usable(s.runs[0].qs, s.runs[0].qe)), (s) => s.runs[0].qs, -Infinity, true)?.item;
279
292
  const displacement = points
280
293
  .map((p) => {
281
294
  const r = p.runs[0];
@@ -287,6 +300,19 @@ export async function counterfactualTransfer(ctx, query, pre) {
287
300
  return null;
288
301
  if (r.cs > fillerOf(before).length + quantum)
289
302
  return null;
303
+ // SUBSTITUTION MUST ACTUALLY DISPLACE. The schema's premise is that the
304
+ // displaced structure's seat is held by something ELSE, which the
305
+ // subject then replaces. When the subject's filler already occurs in
306
+ // that structure, there is nothing to displace — the "transfer" restates
307
+ // the structure with its own occupant put back, and the answer is a
308
+ // tautology. Measured on `Michelangelo is to sculpture as who is to
309
+ // literature?`: the weave aligned the concept `Michelangelo` and the
310
+ // exemplar `The David was sculpted by Michelangelo.`, and substitution
311
+ // produced `Michelangelo sculpted by Michelangelo.` — then outbid every
312
+ // honest candidate with it (test/29 A2). Byte containment, the same
313
+ // primitive the self-evidence and contradiction guards use.
314
+ if (indexOf(p.ctx, fillerOf(before), 0) >= 0)
315
+ return null;
290
316
  return { p, before, depth: p.ctx.length - r.cs };
291
317
  })
292
318
  .filter((c) => c !== null);
@@ -358,13 +384,27 @@ export async function counterfactualTransfer(ctx, query, pre) {
358
384
  // overlap trimming.
359
385
  // The seat that establishes a candidate's role — see {@link seatOfNode}.
360
386
  const seatOf = (p, allowForward = true) => seatOfNode(ctx, p.anchor, qv, p.ctx, allowForward);
387
+ // QUERY-SCALE — "this learnt context is the same size as the question", the
388
+ // bound comparison holds its dominant and its analogs to. A byte-exact
389
+ // `<= query.length` made that judgement turn on a difference the
390
+ // architecture cannot perceive: on `The Weeping Woman was painted by Pablo
391
+ // Picasso.` (47 bytes) the weave's dominant was `The Night Watch was painted
392
+ // by Rembrandt van Rijn.` (50) — three bytes over, so comparison refused
393
+ // outright, while the interchangeable `The Mona Lisa was painted by Leonardo
394
+ // da Vinci.` (47) would have passed. WHICH exemplar becomes dominant is
395
+ // settled by run-claiming order among equals, so a 3-byte difference was
396
+ // deciding whether the schema fires at all (test/33 1b). W is the smallest
397
+ // distinction perception can make — the same quantum countClusters separates
398
+ // neighbourhoods by — so a context within one quantum of the query's length
399
+ // carries no independently perceivable unit beyond it and is the same scale.
400
+ const queryScale = (n) => n - query.length < quantum;
361
401
  const analogs = [];
362
402
  for (const p of points) {
363
403
  if (p === dominant)
364
404
  continue;
365
405
  // Push the point's own anchor only when its context fits within
366
406
  // the query (the seat sentence must not dominate the comparison).
367
- if (p.ctx.length <= query.length &&
407
+ if (queryScale(p.ctx.length) &&
368
408
  indexOf(dominant.ctx, p.ctx, 0) < 0 &&
369
409
  indexOf(p.ctx, dominant.ctx, 0) < 0 &&
370
410
  indexOf(query, p.ctx, 0) < 0) {
@@ -381,7 +421,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
381
421
  // chooseNext caps by) are reachable as analogs.
382
422
  for (const nid of ctx.store.nextFirst(p.anchor, hubBound(ctx))) {
383
423
  const nctx = read(ctx, nid);
384
- if (nctx.length > query.length ||
424
+ if (!queryScale(nctx.length) ||
385
425
  indexOf(dominant.ctx, nctx, 0) >= 0 ||
386
426
  indexOf(nctx, dominant.ctx, 0) >= 0 ||
387
427
  indexOf(query, nctx, 0) >= 0)
@@ -389,6 +429,23 @@ export async function counterfactualTransfer(ctx, query, pre) {
389
429
  analogs.push({ anchor: nid, point: null, src: p });
390
430
  }
391
431
  }
432
+ // MEASURED AND REFUTED — proposing analogs from the dominant's halo when the
433
+ // query-local generator finds none. Both loops above are query-local (an
434
+ // aligned point, or one forward hop off one), while the gate that judges
435
+ // candidates — analogyStrength's halo tier — is cross-domain by construction
436
+ // and is licence enough on its own (`bestHalo` exempts it from the naming and
437
+ // trusted-root bars). So the gate reads as strictly more capable than the
438
+ // generator, and closing that asymmetry looks like the fix for test/29 A2
439
+ // (`Michelangelo is to sculpture as who is to literature?`, whose only stored
440
+ // content is `Michelangelo`: the weave aligns the concept and its own
441
+ // exemplar, each contains the other, so every candidate is excluded and
442
+ // comparison checks zero).
443
+ //
444
+ // It proposes nothing. Measured on A2's own 13-pair corpus: `Michelangelo`
445
+ // HAS a halo, and `haloSiblings` returns not one sibling above
446
+ // significanceBar — the distributional company that would make Shakespeare a
447
+ // cross-domain analog was never trained. The asymmetry is real but it is not
448
+ // what stops A2; the corpus is.
392
449
  let bestAnalog = null;
393
450
  let bestSim = 0;
394
451
  let bestHalo = false;
@@ -614,7 +671,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
614
671
  if (bestAnalog !== null &&
615
672
  (bestHalo || analogNamed || rootTrusted) &&
616
673
  !cmpDismisses &&
617
- dominant.ctx.length <= query.length &&
674
+ queryScale(dominant.ctx.length) &&
618
675
  roots.length <= 1 &&
619
676
  !dominates(cmpMaxGap, query.length) &&
620
677
  cmpMaxGap < dominant.ctx.length) {
@@ -650,7 +707,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
650
707
  cmpAccounted);
651
708
  }
652
709
  else if (bestAnalog !== null &&
653
- dominant.ctx.length <= query.length &&
710
+ queryScale(dominant.ctx.length) &&
654
711
  roots.length <= 1) {
655
712
  ctx.trace?.step("validateAnalogy", [
656
713
  rNode(ctx, dominant.anchor, "analog", bestSim),
@@ -1,4 +1,3 @@
1
- import type { Vec } from "../../vec.js";
2
1
  import type { MindContext } from "../types.js";
3
2
  import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
4
3
  /** Find the first span-shaped skill exemplar among the ranked anchors from
@@ -31,37 +30,4 @@ export declare function answerRunsInContext(_ctx: MindContext, context: Uint8Arr
31
30
  end: number;
32
31
  ansLen: number;
33
32
  }> | null;
34
- /** Check whether an anchor is a span-shaped skill exemplar: it represents a
35
- * fact whose context and answer together form a span-in-context pattern.
36
- * If the anchor has a nextOf continuation, that is the answer and the anchor
37
- * itself is the context. Otherwise the anchor's prevOf parents provide
38
- * candidate contexts, and the longest one whose span is span-shaped wins. */
39
- export declare function skillExemplar(ctx: MindContext, anchor: number, guide?: Vec | null): Promise<{
40
- contextBytes: Uint8Array;
41
- answerBytes: Uint8Array;
42
- } | null>;
43
- /** Whether the answer is a SPARSE subsequence of the context (bytes in
44
- * order, arbitrary gaps) — the OPEN span-shape reading (see the section
45
- * note above). This is what lets extraction validate a MULTI-PIECE
46
- * exemplar whose answer is stitched from several context runs — but it is
47
- * deliberately permissive, so it must never be used as evidence that one
48
- * span was "drawn from" another (see {@link containsSpan} for that).
49
- *
50
- * There is deliberately NO containsSpan pre-check here: strict containment
51
- * IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
52
- * whose content-addressed identity means its bytes occur contiguously — is
53
- * an in-order embedding with zero gaps), so the scan below decides alone,
54
- * with the same truth value. The old pre-check re-perceived the context
55
- * (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
56
- * pure cost, no discrimination. */
57
- export declare function isSpanShaped(_ctx: MindContext, context: Uint8Array, answer: Uint8Array): boolean;
58
- /** STRICT containment: the answer's resolved node appears in the context's
59
- * folded tree, or the answer occurs as one CONTIGUOUS byte run of the
60
- * context. This is real evidence the answer was drawn from the context.
61
- * Fusion gates on this — the sparse-subsequence reading of
62
- * {@link isSpanShaped} is trivially satisfied by short answers over long
63
- * queries ("cold" is a gap-tolerant subsequence of most sentences holding
64
- * c…o…l…d in order), and gating fusion on it silently starved multi-topic
65
- * queries of their further points of attention. */
66
- export declare function containsSpan(ctx: MindContext, context: Uint8Array, answer: Uint8Array): boolean;
67
33
  export declare const extractionMechanism: PipelineMechanism;
@@ -6,9 +6,7 @@
6
6
  // form a span-in-context pattern: the answer is a subsequence of the context
7
7
  // (or one of its pieces is), and the context is the smallest spanning frame
8
8
  // that contains it.
9
- import { foldTree, perceive, read, resolve } from "../primitives.js";
10
- import { follow, locate } from "../match.js";
11
- import { chooseAmong, hubBound } from "../traverse.js";
9
+ import { locate } from "../match.js";
12
10
  import { concatBytes, indexOf } from "../../bytes.js";
13
11
  import { decodeText, unexplainedLabel } from "../rationale.js";
14
12
  import { CONCEPT, STEP } from "../graph-search.js";
@@ -273,91 +271,6 @@ export function answerRunsInContext(_ctx, context, answer) {
273
271
  }
274
272
  return merged.length > 0 ? merged : null;
275
273
  }
276
- /** Check whether an anchor is a span-shaped skill exemplar: it represents a
277
- * fact whose context and answer together form a span-in-context pattern.
278
- * If the anchor has a nextOf continuation, that is the answer and the anchor
279
- * itself is the context. Otherwise the anchor's prevOf parents provide
280
- * candidate contexts, and the longest one whose span is span-shaped wins. */
281
- export async function skillExemplar(ctx, anchor, guide) {
282
- if (ctx.store.hasNext(anchor)) {
283
- const contextBytes = read(ctx, anchor);
284
- const answerBytes = await follow(ctx, anchor, guide);
285
- if (answerBytes !== null && isSpanShaped(ctx, contextBytes, answerBytes)) {
286
- return { contextBytes, answerBytes };
287
- }
288
- return null;
289
- }
290
- const answerBytes = read(ctx, anchor);
291
- // Candidate contexts, capped at the hub bound (a common answer's reverse
292
- // fan-in is corpus-sized).
293
- const capped = ctx.store.prevFirst(anchor, hubBound(ctx));
294
- const spanShaped = [];
295
- for (const p of capped) {
296
- const ctxB = read(ctx, p);
297
- if (ctxB.length > 0 && isSpanShaped(ctx, ctxB, answerBytes)) {
298
- spanShaped.push({ id: p, bytes: ctxB });
299
- }
300
- }
301
- if (spanShaped.length === 0)
302
- return null;
303
- // Among span-shaped contexts, the longest wins (the smallest spanning frame
304
- // heuristic's dual: more frame to locate in the query); the query gist,
305
- // when given, breaks LENGTH TIES via chooseAmong — the same reverse-regime
306
- // disambiguator every context pick uses, whose gist cache spares the
307
- // re-fold this block once paid per tied candidate. Same strict first-seen
308
- // tie-break as the hand loop it replaces.
309
- const maxLen = Math.max(...spanShaped.map((s) => s.bytes.length));
310
- const longest = spanShaped.filter((s) => s.bytes.length === maxLen);
311
- let contextBytes = longest[0].bytes;
312
- if (guide && longest.length > 1) {
313
- const pick = chooseAmong(ctx, longest.map((s) => s.id), guide).id;
314
- contextBytes = longest.find((s) => s.id === pick).bytes;
315
- }
316
- return { contextBytes, answerBytes };
317
- }
318
- /** Whether the answer is a SPARSE subsequence of the context (bytes in
319
- * order, arbitrary gaps) — the OPEN span-shape reading (see the section
320
- * note above). This is what lets extraction validate a MULTI-PIECE
321
- * exemplar whose answer is stitched from several context runs — but it is
322
- * deliberately permissive, so it must never be used as evidence that one
323
- * span was "drawn from" another (see {@link containsSpan} for that).
324
- *
325
- * There is deliberately NO containsSpan pre-check here: strict containment
326
- * IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
327
- * whose content-addressed identity means its bytes occur contiguously — is
328
- * an in-order embedding with zero gaps), so the scan below decides alone,
329
- * with the same truth value. The old pre-check re-perceived the context
330
- * (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
331
- * pure cost, no discrimination. */
332
- export function isSpanShaped(_ctx, context, answer) {
333
- let ai = 0;
334
- for (let ci = 0; ci < context.length && ai < answer.length; ci++) {
335
- if (context[ci] === answer[ai])
336
- ai++;
337
- }
338
- return ai === answer.length;
339
- }
340
- /** STRICT containment: the answer's resolved node appears in the context's
341
- * folded tree, or the answer occurs as one CONTIGUOUS byte run of the
342
- * context. This is real evidence the answer was drawn from the context.
343
- * Fusion gates on this — the sparse-subsequence reading of
344
- * {@link isSpanShaped} is trivially satisfied by short answers over long
345
- * queries ("cold" is a gap-tolerant subsequence of most sentences holding
346
- * c…o…l…d in order), and gating fusion on it silently starved multi-topic
347
- * queries of their further points of attention. */
348
- export function containsSpan(ctx, context, answer) {
349
- const ansId = resolve(ctx, answer);
350
- if (ansId !== null) {
351
- let found = false;
352
- foldTree(ctx, perceive(ctx, context), 0, (_n, _s, _e, node) => {
353
- if (node === ansId)
354
- found = true;
355
- });
356
- if (found)
357
- return true;
358
- }
359
- return indexOf(context, answer, 0) >= 0;
360
- }
361
274
  // ── Pipeline mechanism ──────────────────────────────────────────────────────
362
275
  export const extractionMechanism = {
363
276
  name: "extraction",
@@ -7,6 +7,9 @@ export interface RecallResult {
7
7
  accounted: Array<[number, number]>;
8
8
  moves: number;
9
9
  unexplained: string;
10
+ /** See {@link import("../pipeline-mechanism.js").MechanismResult.complete}
11
+ * — set by the IDENTITY-bridge tier alone. */
12
+ complete?: boolean;
10
13
  }
11
14
  /** Recall the answer by resonating the whole query against the content index. */
12
15
  export declare function recallByResonance(ctx: MindContext, query: Uint8Array, pre: Precomputed): Promise<RecallResult | null>;
@@ -20,7 +20,7 @@ export async function recallByResonance(ctx, query, pre) {
20
20
  ]);
21
21
  const whole_ = [[0, query.length]];
22
22
  const nothing = [];
23
- const ground = (bytes, note, accounted, moves, echoed = false) => {
23
+ const ground = (bytes, note, accounted, moves, echoed = false, complete = false) => {
24
24
  t?.done(bytes === null
25
25
  ? []
26
26
  : [rItem(bytes, "answer", resolve(ctx, bytes) ?? undefined)], note);
@@ -30,6 +30,7 @@ export async function recallByResonance(ctx, query, pre) {
30
30
  accounted,
31
31
  moves,
32
32
  unexplained: unexplainedLabel(query, accounted),
33
+ ...(complete ? { complete } : {}),
33
34
  };
34
35
  };
35
36
  const k = pre.k;
@@ -245,10 +246,32 @@ export async function recallByResonance(ctx, query, pre) {
245
246
  // natural, tuning-free ceiling (probe every cluster) for a call that
246
247
  // is ALREADY refusal-path-only and must not miss a candidate hiding
247
248
  // behind an unlucky structural distance.
248
- const wide = k < hubBound(ctx)
249
- ? await ctx.store.resonate(queryGist, hubBound(ctx), true)
250
- : whole;
251
- const bridged = await substitutionBridge(ctx, query, wide.map((h) => h.id));
249
+ //
250
+ // MEASURED COST, AND WHY IT STAYS (17.9M vectors / 325K contexts):
251
+ // this one call is ~570 ms and ~45% of all inference time on a refusing
252
+ // query. The cost is entirely `exhaustive` (nprobe = every cluster),
253
+ // NOT the widened k — timed on that store: k=571 exhaustive 632 ms,
254
+ // k=24 exhaustive 536 ms, k=571 NON-exhaustive 12 ms. So narrowing k
255
+ // buys nothing and the 50x is the whole-index scan itself.
256
+ // It is load-bearing: over an 18-query battery the bridge produced a
257
+ // winner 4 times, and ALL FOUR winners came from this proposal channel
258
+ // — the anchor-climb channel won nothing on its own. Reordering the
259
+ // channels (climb first, resonate only on failure) would therefore pay
260
+ // the climb, fail, and pay this anyway. Do not weaken it without
261
+ // re-running that measurement.
262
+ // Handed to the bridge as a THUNK: this exhaustive whole-index probe is
263
+ // the most expensive single act on the refusal path, and the bridge's
264
+ // own cheap gates (query length, the O(|query|) stored-window anchor
265
+ // scan) can refuse without any proposal at all. See substitutionBridge.
266
+ const wideIds = async () => {
267
+ const wide = k >= hubBound(ctx)
268
+ ? whole
269
+ : ctx.meter
270
+ ? await ctx.meter.time("recall.exhaustiveResonate", () => ctx.store.resonate(queryGist, hubBound(ctx), true))
271
+ : await ctx.store.resonate(queryGist, hubBound(ctx), true);
272
+ return wide.map((h) => h.id);
273
+ };
274
+ const bridged = await substitutionBridge(ctx, query, wideIds);
252
275
  if (bridged !== null) {
253
276
  const g = await project(ctx, bridged.id, queryGist);
254
277
  // A projection contained in a substituted candidate-side span is the
@@ -263,15 +286,54 @@ export async function recallByResonance(ctx, query, pre) {
263
286
  bridged.subs.some((s) => indexOf(cBytes.subarray(s.cs, s.ce), g, 0) >= 0);
264
287
  if (g !== null && g.length > 0 && !restates(g) && !manufactured &&
265
288
  !(g.length < query.length && indexOf(query, g, 0) >= 0)) {
266
- return ground(g, `substitution bridge — a trained context accounts for the query ` +
267
- `up to ${bridged.subs.length} corroborated substitution(s)`,
268
- // Accounted NOTHING — the same epistemic humility as the echo
269
- // tier below: a substitution-bridged grounding is a last resort
270
- // that must lose to ANY mechanism that actually explained the
271
- // query (observed: pricing the aligned spans here outweighed
272
- // extraction's correct answer in the grounding decider), while
273
- // still beating silence when everything else refused.
274
- [], CONCEPT * bridged.subs.length + STEP);
289
+ return ground(g, bridged.subs.length === 0
290
+ ? `identity bridge — a trained context IS this query, up to ` +
291
+ `scaffolding the corpus itself treats as filler`
292
+ : `substitution bridge a trained context accounts for the ` +
293
+ `query up to ${bridged.subs.length} corroborated ` +
294
+ `substitution(s)`,
295
+ // WHAT THIS GROUNDING EXPLAINS the spans its alignment covers,
296
+ // matched AND substituted, for BOTH tiers.
297
+ //
298
+ // A corroborated substitution is not a gap in the explanation; it is
299
+ // an explanation the mechanism PAID for, one CONCEPT each in `moves`
300
+ // just below. Leaving its span unaccounted charges the same act
301
+ // twice — once as a move, once as PASS-per-unexplained-byte — and the
302
+ // second charge is far the larger, so a bridge that matched 28 of 29
303
+ // bytes declared the whole query unexplained and lost to any
304
+ // mechanism with a smaller honest claim. Measured on test/49's
305
+ // paraphrase: it FOUND the trained fact through two corroborated case
306
+ // substitutions and was outbid 29011 to 1012 by a CAST comparison
307
+ // that voiced the wrong country.
308
+ //
309
+ // This tier DID once report `[]` for the substituted case, against
310
+ // the observation that "pricing the aligned spans outweighed
311
+ // extraction's correct answer in the grounding decider". That is no
312
+ // longer so and the suite is the witness: with the fold's regions
313
+ // content-defined and the junction tiers no longer consuming each
314
+ // other's candidates, full accounting here passes every test that
315
+ // refutation was recorded for. Reporting only the LITERALLY matched
316
+ // spans (accounted minus the substituted ones) was also implemented
317
+ // and is a strictly worse reading of the same ladder — it still
318
+ // double-charges, just less.
319
+ //
320
+ // An IDENTITY bridge (zero substitutions) substituted nothing, so
321
+ // there is nothing to be humble about: every accounted byte is a
322
+ // LITERAL match against a trained form, and the query is that form
323
+ // up to scaffolding. Reporting `[]` for it was actively wrong in
324
+ // two ways — it priced a full explanation at PASS-per-byte so junk
325
+ // outweighed it, and, because the honest-remainder test in think()
326
+ // reads the same spans, it left the whole query "unaccounted" and
327
+ // forced the multi-topic fusion gate open. Observed live: the
328
+ // correct "What is the process of photosynthesis?" grounding was
329
+ // fused away into an unrelated "Hello! How can I assist you
330
+ // today?" point of attention.
331
+ [...bridged.accounted], CONCEPT * bridged.subs.length + STEP, false,
332
+ // COMPLETE only for the identity tier: the query IS this trained
333
+ // context, so `g` is that context's own continuation — the whole
334
+ // read-out. A SUBSTITUTED bridge makes no such claim (it stood a
335
+ // different word in the query's place), so it stays extendable.
336
+ bridged.subs.length === 0);
275
337
  }
276
338
  }
277
339
  }
@@ -320,6 +382,7 @@ export const recallMechanism = {
320
382
  moves: r.moves,
321
383
  unexplained: r.unexplained,
322
384
  provenance: r.echoed ? "recall-echo" : "recall",
385
+ ...(r.complete ? { complete: true } : {}),
323
386
  }];
324
387
  },
325
388
  };