@hviana/sema 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/src/mind/articulation.js +1 -1
  2. package/dist/src/mind/attention.d.ts +18 -2
  3. package/dist/src/mind/attention.js +88 -18
  4. package/dist/src/mind/bridge.d.ts +30 -0
  5. package/dist/src/mind/bridge.js +569 -0
  6. package/dist/src/mind/graph-search.d.ts +16 -1
  7. package/dist/src/mind/graph-search.js +34 -5
  8. package/dist/src/mind/match.d.ts +15 -2
  9. package/dist/src/mind/match.js +3 -8
  10. package/dist/src/mind/mechanisms/alu.js +8 -1
  11. package/dist/src/mind/mechanisms/cast.d.ts +54 -0
  12. package/dist/src/mind/mechanisms/cast.js +303 -48
  13. package/dist/src/mind/mechanisms/cover.js +24 -32
  14. package/dist/src/mind/mechanisms/extraction.js +75 -30
  15. package/dist/src/mind/mechanisms/recall.js +66 -0
  16. package/dist/src/mind/mind.d.ts +1 -0
  17. package/dist/src/mind/mind.js +6 -1
  18. package/dist/src/mind/pipeline.js +34 -2
  19. package/dist/src/mind/reasoning.d.ts +20 -1
  20. package/dist/src/mind/reasoning.js +84 -6
  21. package/dist/src/mind/recognition.js +157 -13
  22. package/dist/src/mind/traverse.js +16 -0
  23. package/dist/src/mind/types.d.ts +65 -2
  24. package/dist/src/mind/types.js +53 -7
  25. package/dist/src/store.d.ts +12 -3
  26. package/dist/src/store.js +9 -3
  27. package/package.json +1 -1
  28. package/src/mind/articulation.ts +1 -0
  29. package/src/mind/attention.ts +105 -17
  30. package/src/mind/bridge.ts +596 -0
  31. package/src/mind/graph-search.ts +59 -2
  32. package/src/mind/match.ts +19 -5
  33. package/src/mind/mechanisms/alu.ts +8 -1
  34. package/src/mind/mechanisms/cast.ts +336 -46
  35. package/src/mind/mechanisms/cover.ts +31 -36
  36. package/src/mind/mechanisms/extraction.ts +101 -40
  37. package/src/mind/mechanisms/recall.ts +79 -0
  38. package/src/mind/mind.ts +7 -1
  39. package/src/mind/pipeline.ts +37 -2
  40. package/src/mind/reasoning.ts +97 -5
  41. package/src/mind/recognition.ts +160 -12
  42. package/src/mind/traverse.ts +17 -0
  43. package/src/mind/types.ts +110 -6
  44. package/src/store.ts +19 -5
  45. package/test/35-attention-confidence.test.mjs +139 -0
  46. package/test/36-already-answered-fusion.test.mjs +128 -0
  47. package/test/37-cluster-dispersion-fusion.test.mjs +190 -0
  48. package/test/38-reason-restate-guard.test.mjs +94 -0
  49. package/test/39-cast-restate-guard.test.mjs +102 -0
  50. package/test/40-choosenext-scale-guard.test.mjs +75 -0
  51. package/test/41-seatofnode-direction.test.mjs +85 -0
  52. package/test/42-recognise-trace-idempotence.test.mjs +106 -0
  53. package/test/43-cast-analog-seat.test.mjs +244 -0
  54. package/test/44-recognise-edge-whitespace.test.mjs +63 -0
  55. package/test/45-liftanswer-restated-trim.test.mjs +60 -0
  56. package/test/46-recognise-multibyte-edge.test.mjs +85 -0
  57. package/test/47-cast-comparison-coverage.test.mjs +134 -0
  58. package/test/48-recognise-turn-connective.test.mjs +125 -0
  59. package/test/49-natural-units-synonym-bridge.test.mjs +175 -0
  60. package/test/50-cast-analog-consensus-floor.test.mjs +179 -0
@@ -83,7 +83,7 @@ export async function articulate(ctx, answer, query) {
83
83
  s.end,
84
84
  ])),
85
85
  ]);
86
- const solved = ctx.search.cover(answer.length, voicedSites, new Map(), ans.leaves, ans.splits, substitutions, undefined, undefined, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
86
+ const solved = ctx.search.cover(answer.length, voicedSites, new Map(), ans.leaves, ans.splits, ans.starts, substitutions, undefined, undefined, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
87
87
  const segs = solved && solved.segs;
88
88
  tArtCover?.done(segs === null
89
89
  ? []
@@ -5,8 +5,17 @@ import type { AncestorReach, Attention, AttentionRead, DFMode, MindContext, Regi
5
5
  * attention — those that cleared commitVotes' significance floor. */
6
6
  export declare function climbAttention(ctx: MindContext, query: Uint8Array, k: number, mode?: DFMode): Promise<Attention[]>;
7
7
  /** Full read-out of one consensus climb: both the roots (dominant points of
8
- * attention) and the entire ranked list. Cached via ctx.climbMemo when
9
- * ctx.trace is null. */
8
+ * attention) and the entire ranked list. Cached via ctx.climbMemo, ALWAYS —
9
+ * see {@link recognise} for why this memo (and recognise()'s own) must
10
+ * never be skipped while tracing: computeAttention's collectRegions walks
11
+ * the query's perceived tree via the same foldTree whose subtree-resolution
12
+ * fast path makes a second call on identical bytes non-idempotent once
13
+ * ctx._resolvedSubtrees is warm (which a multi-turn conversation's shared
14
+ * prefix subtrees guarantee by the second turn). A cache hit still emits
15
+ * a trace step — abbreviated, since the full per-sub-region voting detail
16
+ * {@link traceAttention} builds isn't preserved by the cached read-out —
17
+ * so a traced response is never silently blacked out for a repeated
18
+ * query. */
10
19
  export declare function climbAttentionAll(ctx: MindContext, query: Uint8Array, k: number, mode?: DFMode): Promise<AttentionRead>;
11
20
  export declare function computeAttention(ctx: MindContext, query: Uint8Array, k: number, mode: DFMode): Promise<AttentionRead>;
12
21
  export declare function collectRegions(ctx: MindContext, query: Uint8Array): Region[];
@@ -39,6 +48,11 @@ export declare function poolVotes(ctx: MindContext, regionVotes: readonly Region
39
48
  end: number;
40
49
  w: number;
41
50
  }>;
51
+ /** Per-anchor SCALE-INVARIANT support: Σ RegionVote.absorbed over the
52
+ * distinct contributing regions — see Attention.breadth. */
53
+ regionSupport: Map<number, number>;
54
+ /** Per-anchor contributing region spans — see Attention.clusters. */
55
+ regionSpans: Map<number, Array<[number, number]>>;
42
56
  steps: DerivationStep[];
43
57
  };
44
58
  export declare function commitVotes(ctx: MindContext, pooled: {
@@ -49,6 +63,8 @@ export declare function commitVotes(ctx: MindContext, pooled: {
49
63
  end: number;
50
64
  w: number;
51
65
  }>;
66
+ regionSupport: Map<number, number>;
67
+ regionSpans: Map<number, Array<[number, number]>>;
52
68
  steps: DerivationStep[];
53
69
  }, sat: SaturationInfo, regions: readonly Region[], regionVoter: ReadonlyArray<{
54
70
  id: number;
@@ -15,7 +15,7 @@ import { leafIdRun } from "./canonical.js";
15
15
  import { corpusN, edgeAncestors } from "./traverse.js";
16
16
  import { cachedRead, junctionContainersFrom, junctionSeeds, junctionSynonyms, walkCache, } from "./junction.js";
17
17
  import { indexOf } from "../bytes.js";
18
- import { rNode, traceDerivation } from "./trace.js";
18
+ import { rItem, rNode, traceDerivation } from "./trace.js";
19
19
  // ── Public entry points ───────────────────────────────────────────────────
20
20
  /** Climb the query's perceived byte regions up the structural DAG via
21
21
  * resonance, pool the evidence, and return only the ROOT points of
@@ -24,12 +24,21 @@ export async function climbAttention(ctx, query, k, mode = "inverse") {
24
24
  return (await climbAttentionAll(ctx, query, k, mode)).roots;
25
25
  }
26
26
  /** Full read-out of one consensus climb: both the roots (dominant points of
27
- * attention) and the entire ranked list. Cached via ctx.climbMemo when
28
- * ctx.trace is null. */
27
+ * attention) and the entire ranked list. Cached via ctx.climbMemo, ALWAYS —
28
+ * see {@link recognise} for why this memo (and recognise()'s own) must
29
+ * never be skipped while tracing: computeAttention's collectRegions walks
30
+ * the query's perceived tree via the same foldTree whose subtree-resolution
31
+ * fast path makes a second call on identical bytes non-idempotent once
32
+ * ctx._resolvedSubtrees is warm (which a multi-turn conversation's shared
33
+ * prefix subtrees guarantee by the second turn). A cache hit still emits
34
+ * a trace step — abbreviated, since the full per-sub-region voting detail
35
+ * {@link traceAttention} builds isn't preserved by the cached read-out —
36
+ * so a traced response is never silently blacked out for a repeated
37
+ * query. */
29
38
  export async function climbAttentionAll(ctx, query, k, mode = "inverse") {
30
39
  // Content-keyed memo — works for both single-turn respond() and multi-turn
31
- // respondTurn(). Skipped while tracing.
32
- if (ctx.climbMemo && !ctx.trace) {
40
+ // respondTurn().
41
+ if (ctx.climbMemo) {
33
42
  const contentKey = latin1Key(query);
34
43
  const modeKey = `${k}:${mode}`;
35
44
  let byRead = ctx.climbMemo.get(contentKey);
@@ -37,8 +46,11 @@ export async function climbAttentionAll(ctx, query, k, mode = "inverse") {
37
46
  ctx.climbMemo.set(contentKey, byRead = new Map());
38
47
  }
39
48
  const hit = byRead.get(modeKey);
40
- if (hit !== undefined)
49
+ if (hit !== undefined) {
50
+ ctx.trace?.step("climbConsensus", [rItem(query, "query")], hit.roots.map((r) => rNode(ctx, r.anchor, "anchor", r.vote)), `(cached) consensus already computed for this query — ` +
51
+ `${hit.roots.length} point(s) of attention`);
41
52
  return hit;
53
+ }
42
54
  const read = await computeAttention(ctx, query, k, mode);
43
55
  byRead.set(modeKey, read);
44
56
  return read;
@@ -400,6 +412,8 @@ export function poolVotes(ctx, regionVotes, sat, N) {
400
412
  const votes = new Map();
401
413
  const votesIdf = new Map();
402
414
  const support = new Map();
415
+ const regionSupport = new Map();
416
+ const regionSpans = new Map();
403
417
  const steps = [];
404
418
  let order = 0;
405
419
  for (const pc of pool.values()) {
@@ -407,14 +421,20 @@ export function poolVotes(ctx, regionVotes, sat, N) {
407
421
  votes.set(pc.item.id, pc.cost);
408
422
  const premises = [];
409
423
  const seenRi = new Set();
424
+ let breadthSum = 0;
425
+ const spans = [];
410
426
  for (const c of pc.contributions) {
411
427
  const p0 = c.premises[0].item;
412
428
  if (p0.kind !== "region" || seenRi.has(p0.ri))
413
429
  continue;
414
430
  seenRi.add(p0.ri);
415
431
  const rv = regionVotes[p0.ri];
432
+ breadthSum += rv.absorbed ?? 1;
416
433
  premises.push({ kind: "form", span: [rv.start, rv.end] });
434
+ spans.push([rv.start, rv.end]);
417
435
  }
436
+ regionSupport.set(pc.item.id, breadthSum);
437
+ regionSpans.set(pc.item.id, spans);
418
438
  steps.push({
419
439
  order: order++,
420
440
  move: "pool-vote",
@@ -444,18 +464,59 @@ export function poolVotes(ctx, regionVotes, sat, N) {
444
464
  }
445
465
  }
446
466
  }
447
- return { votes, votesIdf, support, steps };
467
+ return { votes, votesIdf, support, regionSupport, regionSpans, steps };
468
+ }
469
+ /** The number of DISTINCT clusters a root's contributing regions form —
470
+ * see Attention.clusters. Two regions belong to the same cluster iff the
471
+ * gap between them is strictly less than one river-fold quantum W: at
472
+ * that distance there is no room for a genuinely separate, independently
473
+ * perceivable unit of content between them (the same "smallest meaningful
474
+ * distinction" quantum {@link reachThreshold}'s own doc invokes). A gap
475
+ * of a full quantum or more means real, separate structure could sit
476
+ * between the two spans, so they count as independent corroboration.
477
+ * Strict `<` (not `<=`): verified against gap 3.1's own "gender equality"
478
+ * root, whose two genuine clusters sit EXACTLY W bytes apart — `<= W`
479
+ * would wrongly merge them into one and break that pinned requirement. */
480
+ function countClusters(spans, W) {
481
+ if (spans.length === 0)
482
+ return 0;
483
+ const sorted = [...spans].sort((a, b) => a[0] - b[0]);
484
+ let clusters = 1;
485
+ let curEnd = sorted[0][1];
486
+ for (let i = 1; i < sorted.length; i++) {
487
+ const [s, e] = sorted[i];
488
+ if (s - curEnd < W) {
489
+ curEnd = Math.max(curEnd, e);
490
+ }
491
+ else {
492
+ clusters++;
493
+ curEnd = e;
494
+ }
495
+ }
496
+ return clusters;
448
497
  }
449
498
  export function commitVotes(ctx, pooled, sat, regions, regionVoter, N) {
450
- const { votes, votesIdf, support, steps } = pooled;
499
+ const { votes, votesIdf, support, regionSupport, regionSpans, steps } = pooled;
451
500
  if (votes.size === 0) {
452
501
  traceAttention(ctx, regions, regionVoter, [], steps);
453
502
  return { roots: [], ranked: [] };
454
503
  }
504
+ // SCALE-INVARIANT confidence — see Attention.breadth's doc. regions.length
505
+ // is the query's OWN full candidate count (most never vote at all), the
506
+ // same denominator the "N of M sub-regions voted" rationale text already
507
+ // reports; regionSupport is that same accounting read PER ANCHOR.
508
+ const totalRegions = Math.max(1, regions.length);
455
509
  const ranked = [...votes.entries()]
456
510
  .map(([anchor, vote]) => {
457
511
  const s = support.get(anchor);
458
- return { anchor, vote, start: s.start, end: s.end };
512
+ return {
513
+ anchor,
514
+ vote,
515
+ start: s.start,
516
+ end: s.end,
517
+ breadth: (regionSupport.get(anchor) ?? 0) / totalRegions,
518
+ clusters: countClusters(regionSpans.get(anchor) ?? [], ctx.space.maxGroup),
519
+ };
459
520
  })
460
521
  .sort((a, b) => b.vote - a.vote);
461
522
  const overlaps = (a, b) => a.start < b.end && b.start < a.end;
@@ -855,14 +916,6 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo) {
855
916
  spanStart = Math.min(spanStart, regions[ei].start);
856
917
  spanEnd = Math.max(spanEnd, regions[ei].end);
857
918
  }
858
- out.push({
859
- start: spanStart,
860
- end: spanEnd,
861
- canonicalFailed: false, // content-addressed: never saturation-masked
862
- roots: reach.roots,
863
- w,
864
- wFocus: w,
865
- });
866
919
  consumed.add(cand[a]);
867
920
  consumed.add(cand[b]);
868
921
  for (const ei of bestExtras)
@@ -872,15 +925,32 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo) {
872
925
  // vote's bytes are literally part of the learnt whole), and FULL root
873
926
  // disjointness is the disagreement test: a vote sharing even one root
874
927
  // with the junction corroborates it and keeps its say elsewhere.
928
+ // Counted BEFORE pushing the junction's own vote below: each ORIGINAL
929
+ // region this ascent explains away is evidence the junction speaks
930
+ // for, not evidence lost — `absorbed` (RegionVote's breadth-accounting
931
+ // field) must credit the junction with all of it, not just the ONE
932
+ // pooled axiom it collapses to.
875
933
  const containerBytes = cachedRead(ctx, cache, best.id, cap);
876
934
  const jointRoots = new Set(reach.roots);
935
+ let explainedAway = 0;
877
936
  for (const rv of rvs.votes) {
878
937
  if (rv.roots.some((r) => jointRoots.has(r)))
879
938
  continue;
880
939
  const bytes = query.subarray(rv.start, rv.end);
881
- if (indexOf(containerBytes, bytes, 0) >= 0)
940
+ if (indexOf(containerBytes, bytes, 0) >= 0 && !superseded.has(rv)) {
882
941
  superseded.add(rv);
942
+ explainedAway++;
943
+ }
883
944
  }
945
+ out.push({
946
+ start: spanStart,
947
+ end: spanEnd,
948
+ canonicalFailed: false, // content-addressed: never saturation-masked
949
+ roots: reach.roots,
950
+ w,
951
+ wFocus: w,
952
+ absorbed: 1 + explainedAway,
953
+ });
884
954
  const label = [cand[a], cand[b], ...bestExtras]
885
955
  .sort((x, y) => regions[x].start - regions[y].start)
886
956
  .map((ri) => dec(query.subarray(regions[ri].start, regions[ri].end)))
@@ -0,0 +1,30 @@
1
+ import type { MindContext } from "./types.js";
2
+ /** One accepted substitution: query span [qs,qe) stands in for the
3
+ * candidate context's span — recorded for the rationale trace. */
4
+ interface Substitution {
5
+ qs: number;
6
+ qe: number;
7
+ cs: number;
8
+ ce: number;
9
+ }
10
+ /** A bridged grounding proposal: the trained context to ground, the query
11
+ * spans its alignment accounts for, and the substitutions that closed it. */
12
+ export interface BridgeHit {
13
+ id: number;
14
+ accounted: Array<[number, number]>;
15
+ subs: Substitution[];
16
+ }
17
+ /** True when some query byte-range left UNACCOUNTED by `spans` contains a
18
+ * STORED window — content the store has seen that the proposed reading
19
+ * simply ignores. The IGNORED-KNOWN principle: a span may be dismissed
20
+ * only when the store itself has never seen it; known content the
21
+ * alignment failed to account for is grounds for refusal, while genuinely
22
+ * novel spans (an untrained word, stray punctuation) remain tolerable.
23
+ * Shared by the substitution bridge's own acceptance and CAST's
24
+ * frame-tier comparison gate (cast.ts). Pure attestation — no
25
+ * similarity, no constants. */
26
+ export declare function dismissedKnownContent(ctx: MindContext, query: Uint8Array, spans: ReadonlyArray<readonly [number, number]>): boolean;
27
+ /** Recall's corroborated-substitution bridge — see the module comment.
28
+ * Returns the best bridged grounding proposal, or null. */
29
+ export declare function substitutionBridge(ctx: MindContext, query: Uint8Array, proposed?: ReadonlyArray<number>): Promise<BridgeHit | null>;
30
+ export {};