@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
package/dist/src/store.js CHANGED
@@ -516,6 +516,8 @@ export class AbstractStore {
516
516
  }
517
517
  // ── DAG traversal ──────────────────────────────────────────────────────
518
518
  get(id) {
519
+ if (this.meter)
520
+ this.meter.nodeRecords++;
519
521
  // Byte leaves are implicit — fabricate from the id.
520
522
  if (id < 0) {
521
523
  return { id, leaf: new Uint8Array([-(id + 1)]), kids: null };
@@ -536,7 +538,20 @@ export class AbstractStore {
536
538
  * outlive their records — the read degrades safely to empty bytes, this
537
539
  * counter is what keeps that degradation observable. */
538
540
  danglingReads = 0;
541
+ /** {@link Store.meter} — the per-response work accumulator, or null when
542
+ * nothing is profiling. Every read below bumps it through `?.`, so an
543
+ * unprofiled store pays one null check per read and allocates nothing. */
544
+ meter = null;
539
545
  bytes(id) {
546
+ if (this.meter) {
547
+ this.meter.byteReads++;
548
+ // Charged BEFORE the fast path returns, and from the cache entry when
549
+ // there is one: the volume a caller pulled through is the cost signal
550
+ // (an unbounded read is unbounded whether or not it was cached).
551
+ const c = this._bytesCache.get(id);
552
+ if (c)
553
+ this.meter.bytesRead += c.length;
554
+ }
540
555
  // Fast path.
541
556
  const hit = this._bytesCache.get(id);
542
557
  if (hit)
@@ -583,7 +598,10 @@ export class AbstractStore {
583
598
  const out = concat(kids.map((k) => cache.get(k)));
584
599
  cache.set(nid, out);
585
600
  }
586
- return cache.get(id) ?? _ZERO;
601
+ const out = cache.get(id) ?? _ZERO;
602
+ if (this.meter)
603
+ this.meter.bytesRead += out.length;
604
+ return out;
587
605
  }
588
606
  /** First `maxLen` bytes of a node. Walks only the leftmost branch,
589
607
  * stopping at `maxLen` — so a 1 MB document root costs the same as a
@@ -593,8 +611,6 @@ export class AbstractStore {
593
611
  * may be shared with the byte cache and with other callers — treat them
594
612
  * as read-only. Mutating one would corrupt every subsequent read. */
595
613
  bytesPrefix(id, maxLen) {
596
- if (maxLen <= 0)
597
- return _ZERO;
598
614
  // A FULL read (the ALL sentinel) routes through bytes(), whose
599
615
  // reconstruction enters the byte-budget cache. Without this, the mind's
600
616
  // read() — which always passes ALL — re-walked the DAG and re-concatenated
@@ -602,6 +618,23 @@ export class AbstractStore {
602
618
  // exists precisely for those reconstructions.
603
619
  if (maxLen >= 0x7fffffff)
604
620
  return this.bytes(id);
621
+ // METERING BOUNDARY: this public entry point is charged ONCE per logical
622
+ // read; the walk below recurses through `_prefix`, which is not charged.
623
+ // Counting the recursion instead made a single read of an N-byte branch
624
+ // report as N reads (one per node descended), so `byteReads` measured
625
+ // tree size rather than read requests — it read as ~1 byte per read.
626
+ if (this.meter)
627
+ this.meter.byteReads++;
628
+ const out = this._prefix(id, maxLen);
629
+ if (this.meter)
630
+ this.meter.bytesRead += out.length;
631
+ return out;
632
+ }
633
+ /** {@link bytesPrefix}'s recursive body — uncharged; see the metering
634
+ * boundary note there. */
635
+ _prefix(id, maxLen) {
636
+ if (maxLen <= 0)
637
+ return _ZERO;
605
638
  // Full-cache hit: bytes() already reconstructed the whole node.
606
639
  const full = this._bytesCache.get(id);
607
640
  if (full)
@@ -625,13 +658,15 @@ export class AbstractStore {
625
658
  for (const k of kids) {
626
659
  if (got >= maxLen)
627
660
  break;
628
- const child = this.bytesPrefix(k, maxLen - got);
661
+ const child = this._prefix(k, maxLen - got);
629
662
  parts.push(child);
630
663
  got += child.length;
631
664
  }
632
665
  return concat(parts);
633
666
  }
634
667
  contentLen(id, cap = Infinity) {
668
+ if (this.meter)
669
+ this.meter.lenReads++;
635
670
  if (id < 0)
636
671
  return 1; // implicit single-byte leaf
637
672
  const hit = this._lenCache.get(id);
@@ -661,6 +696,8 @@ export class AbstractStore {
661
696
  }
662
697
  // ── Content-addressed lookup ───────────────────────────────────────────
663
698
  findLeaf(bytes) {
699
+ if (this.meter)
700
+ this.meter.leafLookups++;
664
701
  if (bytes.length === 1)
665
702
  return -(bytes[0] + 1);
666
703
  const key = keyOf(bytes);
@@ -673,6 +710,8 @@ export class AbstractStore {
673
710
  return id;
674
711
  }
675
712
  findBranch(kids) {
713
+ if (this.meter)
714
+ this.meter.branchLookups++;
676
715
  const key = kids.join(",");
677
716
  const cached = this._branchKey.get(key);
678
717
  if (cached !== undefined)
@@ -693,15 +732,23 @@ export class AbstractStore {
693
732
  }
694
733
  // ── Structural parents ─────────────────────────────────────────────────
695
734
  parents(id) {
735
+ if (this.meter)
736
+ this.meter.parentReads++;
696
737
  return this._dbGetParents(id);
697
738
  }
698
739
  parentsFirst(id, limit) {
740
+ if (this.meter)
741
+ this.meter.parentReads++;
699
742
  return this._dbGetParentsFirst(id, limit);
700
743
  }
701
744
  hasParents(id) {
745
+ if (this.meter)
746
+ this.meter.parentProbes++;
702
747
  return this._dbGetParentsFirst(id, 1).length > 0;
703
748
  }
704
749
  chainRun(id) {
750
+ if (this.meter)
751
+ this.meter.chainRuns++;
705
752
  const hit = this._chainMemo.get(id);
706
753
  if (hit !== undefined)
707
754
  return hit;
@@ -736,12 +783,16 @@ export class AbstractStore {
736
783
  set.add(parent);
737
784
  }
738
785
  hasContainers(child) {
786
+ if (this.meter)
787
+ this.meter.containerProbes++;
739
788
  if (this._dbContainExists(child))
740
789
  return true;
741
790
  const buf = this._containBuf.get(child);
742
791
  return buf !== undefined && buf.size > 0;
743
792
  }
744
793
  containersSlice(child, offset, limit) {
794
+ if (this.meter)
795
+ this.meter.containerReads++;
745
796
  const out = this._dbGetContainParentsSlice(child, offset, limit);
746
797
  if (out.length >= limit)
747
798
  return out;
@@ -765,6 +816,8 @@ export class AbstractStore {
765
816
  return out;
766
817
  }
767
818
  containers(child) {
819
+ if (this.meter)
820
+ this.meter.containerReads++;
768
821
  const stored = this._dbGetContainParents(child);
769
822
  const buf = this._containBuf.get(child);
770
823
  if (stored.length === 0)
@@ -1152,9 +1205,14 @@ export class AbstractStore {
1152
1205
  const cache = this._resonateCache;
1153
1206
  if (cache) {
1154
1207
  const hit = cache.get(rk);
1155
- if (hit !== undefined)
1208
+ if (hit !== undefined) {
1209
+ if (this.meter)
1210
+ this.meter.annCacheHits++;
1156
1211
  return hit;
1212
+ }
1157
1213
  }
1214
+ if (this.meter)
1215
+ this.meter.annQueries++;
1158
1216
  const clusters = this._vecContentClusterCount();
1159
1217
  const results = this._vecContentQuery(normalize(copy(v)), k * this.overfetch,
1160
1218
  // Exhaustive: probe every cluster (ef ≥ 4·clusters guarantees the
@@ -1169,6 +1227,10 @@ export class AbstractStore {
1169
1227
  if (out.length >= k)
1170
1228
  break;
1171
1229
  }
1230
+ // Vectors the index actually scored for THIS descent — the counter that
1231
+ // exposes a query whose ANN cost is growing with the corpus.
1232
+ if (this.meter)
1233
+ this.meter.annVectorReads += this._vecContentLastReads();
1172
1234
  const rc = this._resonateCache ??= new Map();
1173
1235
  if (rc.size >= RESONATE_CACHE_MAX)
1174
1236
  rc.clear();
@@ -1390,24 +1452,36 @@ export class AbstractStore {
1390
1452
  this._dbInsertEdge(from, to);
1391
1453
  }
1392
1454
  next(id) {
1455
+ if (this.meter)
1456
+ this.meter.edgeReads++;
1393
1457
  return this._dbGetNextEdges(id);
1394
1458
  }
1395
1459
  /** {@link Store.hasNext} — one indexed point probe, never a range read. */
1396
1460
  hasNext(id) {
1461
+ if (this.meter)
1462
+ this.meter.edgeProbes++;
1397
1463
  return this._dbEdgeSrcExists(id);
1398
1464
  }
1399
1465
  prev(id) {
1466
+ if (this.meter)
1467
+ this.meter.prevReads++;
1400
1468
  return this._dbGetPrevEdges(id);
1401
1469
  }
1402
1470
  nextFirst(id, limit) {
1471
+ if (this.meter)
1472
+ this.meter.edgeReads++;
1403
1473
  return this._dbGetNextEdgesFirst(id, limit);
1404
1474
  }
1405
1475
  prevFirst(id, limit) {
1476
+ if (this.meter)
1477
+ this.meter.prevReads++;
1406
1478
  return this._dbGetPrevEdgesFirst(id, limit);
1407
1479
  }
1408
1480
  /** {@link Store.prevCount}. Subclasses with an indexed reverse-edge count
1409
1481
  * should override; this default materialises (correct, not optimal). */
1410
1482
  prevCount(id) {
1483
+ if (this.meter)
1484
+ this.meter.prevProbes++;
1411
1485
  return this._dbGetPrevEdges(id).length;
1412
1486
  }
1413
1487
  edgeSourceCount() {
@@ -1422,10 +1496,14 @@ export class AbstractStore {
1422
1496
  }
1423
1497
  // ── Halos ──────────────────────────────────────────────────────────────
1424
1498
  haloMass(id) {
1499
+ if (this.meter)
1500
+ this.meter.haloProbes++;
1425
1501
  const r = this._dbGetHalo(id);
1426
1502
  return r ? r.mass : 0;
1427
1503
  }
1428
1504
  halo(id) {
1505
+ if (this.meter)
1506
+ this.meter.haloReads++;
1429
1507
  const cached = this._haloNorm.get(id);
1430
1508
  if (cached !== undefined)
1431
1509
  return copy(cached);
@@ -1440,6 +1518,8 @@ export class AbstractStore {
1440
1518
  /** {@link Store.hasHalo} — MUST mirror {@link halo}'s null condition
1441
1519
  * exactly (row present AND mass ≥ minHaloMass), minus the decode. */
1442
1520
  hasHalo(id) {
1521
+ if (this.meter)
1522
+ this.meter.haloProbes++;
1443
1523
  const r = this._dbGetHalo(id);
1444
1524
  return r !== null && r.mass >= this.minHaloMass;
1445
1525
  }
@@ -1473,9 +1553,14 @@ export class AbstractStore {
1473
1553
  const cache = this._resonateHaloCache;
1474
1554
  if (cache) {
1475
1555
  const hit = cache.get(rk);
1476
- if (hit !== undefined)
1556
+ if (hit !== undefined) {
1557
+ if (this.meter)
1558
+ this.meter.annCacheHits++;
1477
1559
  return hit;
1560
+ }
1478
1561
  }
1562
+ if (this.meter)
1563
+ this.meter.haloQueries++;
1479
1564
  const results = this._vecHaloQuery(normalize(copy(v)), k * this.overfetch, this.efFor(this._vecHaloClusterCount()));
1480
1565
  const out = [];
1481
1566
  for (const r of results) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hviana/sema",
3
- "version": "0.2.9",
3
+ "version": "0.4.0",
4
4
  "description": "Sema: a non-parametric, instance-based reasoning system.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -128,12 +128,25 @@ export interface Derivation<I> {
128
128
  premises: Array<Derivation<I>>;
129
129
  }
130
130
 
131
+ /** Optional search-effort read-out, filled in place while the search runs.
132
+ * A plain structural record so the sublibrary stays firewalled (it imports
133
+ * nothing from Sema, and Sema copies these into its own accounting). Purely
134
+ * observational: the search never reads it back. */
135
+ export interface SearchStats {
136
+ /** Agenda entries popped, stale lazy-deletion entries included — the chart
137
+ * work actually done. */
138
+ pops: number;
139
+ /** Agenda entries pushed by a successful relaxation. */
140
+ pushes: number;
141
+ }
142
+
131
143
  /**
132
144
  * Find a lightest derivation of a goal item, or `null` if none exists.
133
145
  * `cost` on the returned root is the total derivation cost.
134
146
  */
135
147
  export function lightestDerivation<I>(
136
148
  system: DeductionSystem<I>,
149
+ stats?: SearchStats,
137
150
  ): Derivation<I> | null {
138
151
  const g = new Map<string, number>(); // best known cost per item
139
152
  const proof = new Map<string, Rule<I> | null>(); // producing rule per item
@@ -177,6 +190,7 @@ export function lightestDerivation<I>(
177
190
  g.set(key, cost);
178
191
  proof.set(key, rule);
179
192
  items.set(key, item);
193
+ if (stats) stats.pushes++;
180
194
  agenda.push(cost + h(item, key), { key, g: cost });
181
195
  }
182
196
  };
@@ -185,6 +199,7 @@ export function lightestDerivation<I>(
185
199
 
186
200
  while (agenda.size > 0) {
187
201
  const { value } = agenda.pop() as { value: { key: string; g: number } };
202
+ if (stats) stats.pops++;
188
203
  const key = value.key;
189
204
  // Lazy deletion: an entry is stale if a cheaper derivation has since been
190
205
  // recorded for the same item.
@@ -7,6 +7,7 @@
7
7
  // symbolic-rewriting mechanism. See ./../README.md for the design.
8
8
 
9
9
  export { lightestDerivation } from "./deduction.js";
10
+ export type { SearchStats } from "./deduction.js";
10
11
  export type {
11
12
  DeductionSystem,
12
13
  Derivation,