@hviana/sema 0.4.7 → 0.5.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 (63) hide show
  1. package/AGENTS.md +290 -77
  2. package/HOW_IT_WORKS.md +2170 -735
  3. package/dist/example/train_base.d.ts +9 -3
  4. package/dist/example/train_base.js +21 -4
  5. package/dist/src/canon.d.ts +19 -0
  6. package/dist/src/canon.js +28 -0
  7. package/dist/src/geometry.d.ts +52 -0
  8. package/dist/src/geometry.js +87 -1
  9. package/dist/src/mind/bridge.js +27 -1
  10. package/dist/src/mind/frame-filler.d.ts +15 -0
  11. package/dist/src/mind/frame-filler.js +535 -0
  12. package/dist/src/mind/learning.js +6 -11
  13. package/dist/src/mind/mechanisms/cast.js +72 -2
  14. package/dist/src/mind/mechanisms/cover.js +6 -1
  15. package/dist/src/mind/mechanisms/extraction.js +27 -0
  16. package/dist/src/mind/mechanisms/recall.js +214 -34
  17. package/dist/src/mind/mind.d.ts +49 -1
  18. package/dist/src/mind/mind.js +137 -10
  19. package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
  20. package/dist/src/mind/pipeline.js +29 -1
  21. package/dist/src/mind/prefix-completion.d.ts +59 -0
  22. package/dist/src/mind/prefix-completion.js +270 -0
  23. package/dist/src/mind/primitives.d.ts +29 -10
  24. package/dist/src/mind/primitives.js +52 -61
  25. package/dist/src/mind/recognition.js +119 -9
  26. package/dist/src/mind/traverse.d.ts +32 -0
  27. package/dist/src/mind/traverse.js +52 -0
  28. package/dist/src/mind/types.d.ts +55 -16
  29. package/dist/src/mind/types.js +68 -19
  30. package/dist/src/store.d.ts +21 -0
  31. package/dist/src/store.js +21 -0
  32. package/example/train_base.ts +21 -4
  33. package/package.json +1 -1
  34. package/src/canon.ts +28 -0
  35. package/src/geometry.ts +100 -1
  36. package/src/mind/bridge.ts +34 -0
  37. package/src/mind/frame-filler.ts +604 -0
  38. package/src/mind/learning.ts +5 -9
  39. package/src/mind/mechanisms/cast.ts +70 -2
  40. package/src/mind/mechanisms/cover.ts +6 -1
  41. package/src/mind/mechanisms/extraction.ts +27 -0
  42. package/src/mind/mechanisms/recall.ts +236 -37
  43. package/src/mind/mind.ts +154 -14
  44. package/src/mind/pipeline-mechanism.ts +7 -0
  45. package/src/mind/pipeline.ts +33 -1
  46. package/src/mind/prefix-completion.ts +314 -0
  47. package/src/mind/primitives.ts +59 -70
  48. package/src/mind/recognition.ts +117 -6
  49. package/src/mind/traverse.ts +52 -0
  50. package/src/mind/types.ts +98 -42
  51. package/src/store.ts +25 -0
  52. package/test/13-conversation.test.mjs +13 -0
  53. package/test/57-fusion-order.test.mjs +65 -0
  54. package/test/66-query-edge-whitespace.test.mjs +99 -0
  55. package/test/67-climb-anchor-breadth.test.mjs +113 -0
  56. package/test/68-extraction-unanchored.test.mjs +79 -0
  57. package/test/69-frame-filler.test.mjs +115 -0
  58. package/test/70-prefix-completion.test.mjs +170 -0
  59. package/test/71-embedded-canon-equivalence.test.mjs +121 -0
  60. package/test/72-prefix-candidate-supply.test.mjs +114 -0
  61. package/test/73-scaffolding-only-bridge-abstains.test.mjs +178 -0
  62. package/test/74-prefix-trap-not-sprung-early.test.mjs +114 -0
  63. package/test/75-multiturn-context-optimisation.test.mjs +1082 -0
package/src/geometry.ts CHANGED
@@ -688,6 +688,97 @@ function contentFoldSpan(
688
688
  return segs[0];
689
689
  }
690
690
 
691
+ /** A plain content fold's reusable state: the level-0 cut edges over the whole
692
+ * stream and each segment's independently-folded root. See
693
+ * {@link contentFoldIncremental}. */
694
+ export interface ContentFold {
695
+ edges: number[];
696
+ segs: Folded[];
697
+ }
698
+
699
+ /** {@link contentFoldSpan} over a WHOLE stream, reusing the segments a previous
700
+ * fold of a byte-identical prefix already produced.
701
+ *
702
+ * WHY THIS IS SOUND, AND WHY IT NEEDS NO BOUNDARIES. A level-0 segment is a
703
+ * pure function of its own bytes ({@link flatFold} reads nothing else), so
704
+ * reusing one whose [start,end) is unchanged is bit-identical to refolding it
705
+ * — the cache can never change the tree, only skip work. And the cuts
706
+ * themselves are stable under APPEND: {@link contentLevels} decides each cut
707
+ * from a rolling hash over a local window, so bytes added at the right edge
708
+ * cannot move a cut to their left (measured over a growing 12-turn context:
709
+ * 100% of prior cuts survive every append, zero tail churn). Together those
710
+ * two facts are the whole optimisation — a grown stream refolds only the
711
+ * segments at its right edge.
712
+ *
713
+ * This is the reuse the conversation path wants, and it costs NOTHING in
714
+ * structure: the tree is exactly the tree {@link bytesToTree} builds for the
715
+ * same bytes with no boundary set at all. Turn boundaries buy prefix-ROOT
716
+ * identity, which is a different property from incremental reuse; conflating
717
+ * the two is what put an imposed boundary set on the inference path and left
718
+ * it folding differently from the deposits it was querying.
719
+ *
720
+ * `groupByLevel` above the segments is re-run whole. It operates on segment
721
+ * ROOTS (a few dozen items for a several-hundred-byte context), not on bytes,
722
+ * and only its right edge actually changes shape — measured at ~40 rebuilt
723
+ * nodes per turn, flat as the context grows sevenfold.
724
+ *
725
+ * PRECONDITION — `prev` MUST have been folded over a BYTE-IDENTICAL PREFIX of
726
+ * `bytes`. Reuse is keyed on a segment's [start,end) OFFSETS, which is what
727
+ * makes it O(1) per segment; offsets alone cannot witness that the underlying
728
+ * bytes agree. Hand it a fold of DIFFERENT bytes whose cuts happen to land
729
+ * in the same places and it will splice those foreign segments in — measured,
730
+ * a deliberately mismatched `prev` produced a wrong tree on 336 of 400 random
731
+ * streams. Verifying the bytes here would cost O(prefix) and defeat the
732
+ * whole point, so the obligation sits with the caller, and every caller
733
+ * discharges it structurally rather than by care: `perceiveDeposit` looks the
734
+ * entry up under `latin1Key(bytes.subarray(0, L))` — the prefix's own bytes
735
+ * ARE the cache key — and a conversation's fold state advances only by
736
+ * append. A new caller that cannot make the same structural argument must
737
+ * pass no `prev` at all; the cold path is always correct.
738
+ * ({@link stablePrefixFoldIncremental} carries the identical precondition for
739
+ * the identical reason.) */
740
+ export function contentFoldIncremental(
741
+ space: Space,
742
+ alphabet: Alphabet,
743
+ bytes: Uint8Array,
744
+ prev?: ContentFold,
745
+ ): { tree: Sema; fold: ContentFold } {
746
+ if (bytes.length === 0) {
747
+ return {
748
+ tree: sema(alphabet.vecs[0], new Uint8Array(0), null),
749
+ fold: { edges: [0], segs: [] },
750
+ };
751
+ }
752
+ const { cuts, levels } = contentLevels(space, bytes);
753
+ const edges = [0, ...cuts, bytes.length];
754
+ const segs: Folded[] = [];
755
+ for (let i = 0; i + 1 < edges.length; i++) {
756
+ const hit = prev !== undefined && prev.edges[i] === edges[i] &&
757
+ prev.edges[i + 1] === edges[i + 1]
758
+ ? prev.segs[i]
759
+ : undefined;
760
+ segs.push(hit ?? flatFold(space, alphabet, bytes, edges[i], edges[i + 1]));
761
+ }
762
+ const folded = segs.length > 1
763
+ ? groupByLevel(space, segs, levels, 1)
764
+ : segs[0];
765
+ // THE ROOT IS NORMALIZED IN PLACE, A CACHED SEGMENT NEVER IS. With one
766
+ // segment — or with a grouping that passes a lone item through — `folded`
767
+ // IS a cached seg, and a later turn will reuse it as an interior node whose
768
+ // magnitude must stay byte-proportional. Copy before normalizing, exactly
769
+ // as the stable-prefix twin does. A single LEAF is copied too: its vector
770
+ // is the shared alphabet entry and must never be written.
771
+ const aliased = segs.some((s) => s.tree === folded.tree);
772
+ let tree = folded.tree;
773
+ if (aliased) {
774
+ tree = tree.kids === null
775
+ ? sema(tree.v, tree.leaf, null)
776
+ : sema(Float32Array.from(tree.v), null, tree.kids);
777
+ }
778
+ if (tree.kids !== null) normalize(tree.v);
779
+ return { tree, fold: { edges, segs } };
780
+ }
781
+
691
782
  /** Group a row of items by the level of the cut BETWEEN them: items separated
692
783
  * by a cut of level < L belong to the same parent, and a cut of level ≥ L ends
693
784
  * it. Recurses upward until one root remains, so the shape at every level is
@@ -923,9 +1014,17 @@ export function stablePrefixFoldIncremental(
923
1014
  boundaries: readonly number[],
924
1015
  prev?: StableFold,
925
1016
  ): { tree: Sema; fold: StableFold } {
1017
+ // SORTED, like {@link bytesToTree} does before calling the non-incremental
1018
+ // twin. The filter below is sequential (`b > prevB`), so an out-of-order
1019
+ // entry is silently DROPPED rather than rejected — and these two functions
1020
+ // are documented as producing the same cuts, so a caller that hands the
1021
+ // same set to each and gets different trees has hit a trap, not a contract.
1022
+ // Sorting here makes the twins genuinely interchangeable; the set is one
1023
+ // entry per conversation turn, so the cost is nil.
1024
+ const sorted = [...boundaries].sort((a, b) => a - b);
926
1025
  const cuts: number[] = [];
927
1026
  let prevB = 0;
928
- for (const b of boundaries) {
1027
+ for (const b of sorted) {
929
1028
  if (b > prevB && b < bytes.length) {
930
1029
  cuts.push(b);
931
1030
  prevB = b;
@@ -97,6 +97,7 @@ import type { MindContext } from "./types.js";
97
97
  import { foldTree, perceive, read } from "./primitives.js";
98
98
  import { chainReach, leafIdRun } from "./canonical.js";
99
99
  import {
100
+ allWindowsAreScaffolding,
100
101
  corpusN,
101
102
  edgeAncestors,
102
103
  hubBound,
@@ -383,6 +384,39 @@ async function bridgeImpl(
383
384
  );
384
385
  return null;
385
386
  }
387
+ // NO DISCRIMINATING LITERAL EVIDENCE — abstain (§2.13). A bridge grounds
388
+ // through the literal spans it did NOT substitute; those anchors are the
389
+ // whole of its evidence. When every one of them is SATURATED — containment
390
+ // clamped at the √N hub bound, i.e. the window is corpus-global scaffolding
391
+ // — the query's unsubstituted part discriminates nothing, and the single
392
+ // substituted span is carrying the entire semantic load. That is not a
393
+ // corroborated bridge; it is a template match, and it FABRICATES.
394
+ //
395
+ // Measured on the trained store (hubBound 571). "What is the capital of"
396
+ // has 19 anchors, ALL saturated ("What":572, "hat ":572, "at i":572 …), and
397
+ // bridged to an unrelated trained context about an integral, voiced
398
+ // confidently. Every query the bridge answers CORRECTLY has at least one
399
+ // unsaturated anchor, by a wide margin and with no near miss:
400
+ // "Who is the author of Hamlet?" → "let?":12, "How do you say 'thank you'
401
+ // in French?" → "y 't":3, "…largest planet…" → "tem?":31, "What is the
402
+ // capital of France?" → "f Fr":114. The honest-silence probes sit on the
403
+ // same side as the correct ones ("Zamu":3), so this gate is not what makes
404
+ // them silent and cannot be credited for them.
405
+ //
406
+ // This introduces NO new threshold: `bound` is the same √N reading of "hub"
407
+ // the anchor scan already clamps its own containment read to (§2.2, §2.7).
408
+ if (allWindowsAreScaffolding(ctx, query)) {
409
+ ctx.trace?.step(
410
+ "substitutionBridge",
411
+ [rItem(query, "query")],
412
+ [],
413
+ "every query window that could anchor is corpus-global scaffolding — " +
414
+ "no literal evidence to corroborate a substitution",
415
+ undefined,
416
+ diagnostics!,
417
+ );
418
+ return null;
419
+ }
386
420
  // CORROBORATION (see the module-level doc) over the precomputed window
387
421
  // facts: the query span [qs,qe) attests when every full W-window inside
388
422
  // it is a stored flat form and at least one is reused across ≥ 2