@hviana/sema 0.9.0 → 0.9.2

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 (118) hide show
  1. package/AGENTS.md +7 -7
  2. package/dist/src/alu/src/index.d.ts +1 -1
  3. package/dist/src/alu/src/index.js +1 -1
  4. package/dist/src/alu/src/parser.js +2 -6
  5. package/dist/src/alu/src/resonance.d.ts +13 -0
  6. package/dist/src/alu/src/resonance.js +41 -0
  7. package/dist/src/alu/test/alu.test.js +39 -0
  8. package/dist/src/bytes.d.ts +6 -2
  9. package/dist/src/bytes.js +10 -4
  10. package/dist/src/canon.js +44 -0
  11. package/dist/src/geometry.d.ts +19 -1
  12. package/dist/src/geometry.js +125 -141
  13. package/dist/src/meter.d.ts +33 -0
  14. package/dist/src/meter.js +34 -1
  15. package/dist/src/mind/articulation.js +14 -1
  16. package/dist/src/mind/attention.d.ts +12 -0
  17. package/dist/src/mind/attention.js +44 -16
  18. package/dist/src/mind/bridge.js +3 -3
  19. package/dist/src/mind/derivation.d.ts +40 -0
  20. package/dist/src/mind/derivation.js +34 -0
  21. package/dist/src/mind/evidence.d.ts +24 -0
  22. package/dist/src/mind/evidence.js +90 -0
  23. package/dist/src/mind/graph-search.d.ts +89 -15
  24. package/dist/src/mind/graph-search.js +345 -174
  25. package/dist/src/mind/learning.js +1 -1
  26. package/dist/src/mind/mechanisms/cover.d.ts +19 -3
  27. package/dist/src/mind/mechanisms/cover.js +142 -61
  28. package/dist/src/mind/mechanisms/recall.js +10 -3
  29. package/dist/src/mind/mind.d.ts +6 -0
  30. package/dist/src/mind/mind.js +5 -2
  31. package/dist/src/mind/pipeline.d.ts +5 -1
  32. package/dist/src/mind/pipeline.js +220 -90
  33. package/dist/src/mind/primitives.d.ts +25 -5
  34. package/dist/src/mind/primitives.js +107 -44
  35. package/dist/src/mind/reasoning.d.ts +18 -4
  36. package/dist/src/mind/reasoning.js +487 -328
  37. package/dist/src/mind/recognition.js +29 -13
  38. package/dist/src/mind/resonance.js +1 -11
  39. package/dist/src/mind/traverse.d.ts +45 -5
  40. package/dist/src/mind/traverse.js +285 -8
  41. package/dist/src/mind/types.d.ts +16 -1
  42. package/dist/src/store-sqlite.d.ts +25 -0
  43. package/dist/src/store-sqlite.js +89 -1
  44. package/dist/src/store.d.ts +48 -4
  45. package/dist/src/store.js +86 -6
  46. package/docs/INDEX.md +20 -19
  47. package/docs/INVARIANTS.md +17 -16
  48. package/docs/architecture/bounded-reads.md +1 -1
  49. package/docs/architecture/caches.md +5 -4
  50. package/docs/architecture/closure.md +45 -5
  51. package/docs/architecture/cost-model.md +16 -0
  52. package/docs/architecture/evidence.md +113 -0
  53. package/docs/architecture/exact-vs-approximate.md +10 -9
  54. package/docs/architecture/factored-machinery.md +14 -13
  55. package/docs/architecture/fold-contract.md +51 -1
  56. package/docs/architecture/mechanism-market.md +21 -0
  57. package/docs/architecture/memoization.md +3 -3
  58. package/docs/architecture/meter.md +2 -1
  59. package/docs/architecture/saturation.md +12 -0
  60. package/docs/architecture/store.md +25 -2
  61. package/docs/failures/tempting-but-wrong.md +13 -2
  62. package/docs/harness/gates.md +12 -10
  63. package/docs/mechanisms/cover.md +23 -6
  64. package/jsr.json +1 -1
  65. package/package.json +1 -1
  66. package/src/alu/README.md +10 -2
  67. package/src/alu/src/index.ts +1 -0
  68. package/src/alu/src/parser.ts +6 -6
  69. package/src/alu/src/resonance.ts +42 -0
  70. package/src/alu/test/alu.test.ts +40 -0
  71. package/src/bytes.ts +13 -3
  72. package/src/canon.ts +40 -0
  73. package/src/geometry.ts +183 -154
  74. package/src/meter.ts +34 -1
  75. package/src/mind/articulation.ts +14 -2
  76. package/src/mind/attention.ts +47 -25
  77. package/src/mind/bridge.ts +3 -3
  78. package/src/mind/derivation.ts +77 -0
  79. package/src/mind/evidence.ts +107 -0
  80. package/src/mind/graph-search.ts +449 -221
  81. package/src/mind/learning.ts +1 -7
  82. package/src/mind/match.ts +1 -2
  83. package/src/mind/mechanisms/cast.ts +1 -2
  84. package/src/mind/mechanisms/cover.ts +207 -87
  85. package/src/mind/mechanisms/extraction.ts +1 -2
  86. package/src/mind/mechanisms/prefix-completion.ts +1 -1
  87. package/src/mind/mechanisms/recall.ts +17 -5
  88. package/src/mind/mechanisms/reference.ts +1 -1
  89. package/src/mind/mind.ts +9 -30
  90. package/src/mind/pipeline.ts +263 -104
  91. package/src/mind/primitives.ts +119 -43
  92. package/src/mind/reasoning.ts +611 -419
  93. package/src/mind/recognition.ts +24 -9
  94. package/src/mind/resonance.ts +2 -16
  95. package/src/mind/trace.ts +1 -1
  96. package/src/mind/traverse.ts +321 -8
  97. package/src/mind/types.ts +15 -11
  98. package/src/store-sqlite.ts +92 -1
  99. package/src/store.ts +113 -7
  100. package/test/105-derive-through-reports-its-refusal.test.mjs +8 -5
  101. package/test/106-the-join-fires.test.mjs +21 -0
  102. package/test/111-the-cover-assembly-is-counted.test.mjs +8 -5
  103. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +18 -12
  104. package/test/136-the-two-named-limits.test.mjs +3 -2
  105. package/test/137-the-law-lives-once-and-below.test.mjs +21 -0
  106. package/test/148-exact-shortcuts-agree.test.mjs +188 -0
  107. package/test/149-the-closure-engine.test.mjs +138 -0
  108. package/test/150-the-join-is-output-sensitive.test.mjs +66 -0
  109. package/test/151-the-cover-pays-for-what-it-reaches.test.mjs +142 -0
  110. package/test/152-the-read-side-names-as-the-write-side.test.mjs +146 -0
  111. package/test/153-a-cheaper-bound-is-looked-at-first.test.mjs +155 -0
  112. package/test/154-the-question-names-the-step.test.mjs +281 -0
  113. package/test/24-generalization.test.mjs +32 -0
  114. package/test/36-bloom.test.mjs +53 -0
  115. package/test/37-cluster-dispersion-fusion.test.mjs +75 -0
  116. package/test/48-recognise-turn-connective.test.mjs +3 -2
  117. package/test/55-cost-meter.test.mjs +4 -4
  118. package/test/90-connector-read-cap.test.mjs +7 -7
@@ -8,34 +8,20 @@ import { Sema } from "../sema.js";
8
8
  import {
9
9
  bytesToTree,
10
10
  contentFoldIncremental,
11
+ contentIdentity,
11
12
  Grid,
12
13
  gridToTree,
13
14
  hilbertBytes,
14
15
  stackGrids,
15
16
  } from "../geometry.js";
16
17
  import { canonHash } from "../canon.js";
17
- import { bytesEqual } from "../bytes.js";
18
+ import { bytesEqual, concatBytes, latin1 } from "../bytes.js";
18
19
  import { ALL } from "./types.js";
19
20
  import type { Input, MindContext } from "./types.js";
20
21
  import type { ContentFold } from "../geometry.js";
21
22
 
22
23
  // ── Address: bytes → node ──────────────────────────────────────────────
23
24
 
24
- /** The content key of a byte span — one latin1 char per byte, an exact,
25
- * collision-free encoding. Spans on the perception path are query-scale
26
- * (windows, regions, candidate spans), so key construction is far cheaper
27
- * than the river fold it deduplicates. */
28
- export function latin1Key(bytes: Uint8Array): string {
29
- // Batched String.fromCharCode — avoids the O(n²) cost of repeated += on
30
- // potentially-large query spans, and stays well under the ~65536 arg limit.
31
- const n = bytes.length;
32
- let s = "";
33
- for (let i = 0; i < n; i += 4096) {
34
- s += String.fromCharCode(...bytes.subarray(i, Math.min(i + 4096, n)));
35
- }
36
- return s;
37
- }
38
-
39
25
  /** The {@link perceive} memo key: the span's content PLUS the boundary set it
40
26
  * was folded under. The tree is a function of BOTH — the same bytes fold
41
27
  * plainly with no boundaries and into a left-nested stable-prefix shape with
@@ -50,7 +36,7 @@ export function perceiveKey(
50
36
  bytes: Uint8Array,
51
37
  boundaries?: readonly number[],
52
38
  ): string {
53
- const k = latin1Key(bytes);
39
+ const k = latin1(bytes);
54
40
  return boundaries === undefined || boundaries.length === 0
55
41
  ? k
56
42
  : k + "\u0000" + boundaries.join(",");
@@ -154,7 +140,7 @@ export function perceiveDeposit(
154
140
  .filter((L) => L >= 2 && L < bytes.length)
155
141
  .sort((a, b) => b - a);
156
142
  for (const L of lens) {
157
- const hit = ctx._depositTrees.get(latin1Key(bytes.subarray(0, L)));
143
+ const hit = ctx._depositTrees.get(latin1(bytes.subarray(0, L)));
158
144
  if (hit !== undefined) {
159
145
  prev = hit.content;
160
146
  break;
@@ -173,7 +159,7 @@ export function perceiveDeposit(
173
159
  ctx._depositLens.clear();
174
160
  ctx._depositTrees.clear();
175
161
  }
176
- ctx._depositTrees.set(latin1Key(bytes), { content: folded.fold });
162
+ ctx._depositTrees.set(latin1(bytes), { content: folded.fold });
177
163
  ctx._depositLens.add(bytes.length);
178
164
  }
179
165
  return folded.tree;
@@ -252,12 +238,10 @@ export function foldTree(
252
238
  return { end, node };
253
239
  }
254
240
  let pos = start;
255
- let known = true;
256
- const kids: number[] = [];
241
+ const kids: Array<number | null> = [];
257
242
  for (const k of n.kids) {
258
243
  const r = foldTree(ctx, k, pos, visit);
259
- if (r.node === null) known = false;
260
- else if (known) kids.push(r.node);
244
+ kids.push(r.node);
261
245
  pos = r.end;
262
246
  }
263
247
  // Same store-probe elision as the leaf case: a cached entry already names
@@ -265,11 +249,10 @@ export function foldTree(
265
249
  // id need not be re-derived. Using it also keeps a warm walk's ids
266
250
  // bit-identical to a cold walk's rather than re-deriving them from children
267
251
  // that may themselves have come from cache.
268
- const node = cached !== undefined
269
- ? cached.id
270
- : known
271
- ? ctx.store.findBranch(kids)
272
- : null;
252
+ const named = cached !== undefined
253
+ ? { id: cached.id, byBytes: false }
254
+ : branchNaming(ctx, kids, treeBytes(n));
255
+ const node = named.id;
273
256
  visit?.(n, start, pos, node);
274
257
  if (node !== null && ctx._resolvedSubtrees) {
275
258
  ctx._resolvedSubtrees.set(n, { id: node, len: pos - start });
@@ -277,15 +260,113 @@ export function foldTree(
277
260
  return { end: pos, node };
278
261
  }
279
262
 
263
+ /** A perceived subtree's bytes, its leaves in order. */
264
+ function treeBytes(n: Sema): Uint8Array {
265
+ const parts: Uint8Array[] = [];
266
+ const walk = (x: Sema): void => {
267
+ if (x.kids === null) parts.push(x.leaf ?? new Uint8Array(0));
268
+ else for (const k of x.kids) walk(k);
269
+ };
270
+ walk(n);
271
+ return concatBytes(parts);
272
+ }
273
+
274
+ /** The EXACT content-addressed node of a byte stream — `foldTree(perceive)`,
275
+ * read for identity alone.
276
+ *
277
+ * A fold names a branch only when every child is named, so identity needs the
278
+ * fold's SHAPE and the store's answer per node, never its vectors:
279
+ * {@link contentIdentity} walks the same shape (geometry.ts — one grouping
280
+ * rule, two algebras) and asks the store bottom-up, building no D-dimensional
281
+ * gist and leaving nothing in the perception memo. Each node is named the
282
+ * way the store's write side names it ({@link branchNaming}). `test/148` pins
283
+ * the agreement with the full fold over random and corpus spans. */
284
+ export function exactNode(ctx: MindContext, bytes: Uint8Array): number | null {
285
+ return exactNaming(ctx, bytes).id;
286
+ }
287
+
288
+ /** {@link exactNode}, with whether the span's own name was found only through
289
+ * its BYTES — its children named no branch, and the flat node over the same
290
+ * bytes did ({@link branchNaming}). That is where the exact lookup used to
291
+ * MISS, so it is where {@link resolve} still asks the canonical class: the
292
+ * class may hold the learnt member that leads somewhere, which a flat index
293
+ * entry need not (measured: `tonight` named an edge-less window and
294
+ * pre-empted the case-folded `Tonight` whose edge a composition stood on).
295
+ * Recognition's probes reach it through `resolve`; asking it again for the
296
+ * perceived tree's own byte-named groups changed none of 116 real queries and
297
+ * no test, so it is not asked there. */
298
+ export function exactNaming(
299
+ ctx: MindContext,
300
+ bytes: Uint8Array,
301
+ ): { id: number | null; byBytes: boolean } {
302
+ if (bytes.length === 0) {
303
+ return { id: foldTree(ctx, perceive(ctx, bytes), 0).node, byBytes: false };
304
+ }
305
+ if (ctx.meter) ctx.meter.identityBytes += bytes.length;
306
+ let byBytes = false;
307
+ const id = contentIdentity(
308
+ ctx.space,
309
+ ctx.alphabet,
310
+ bytes,
311
+ (from, to) =>
312
+ to - from === 1
313
+ ? ctx.store.findLeaf(bytes.subarray(from, to))
314
+ : flatNode(ctx, bytes.subarray(from, to)),
315
+ (kids, from, to) => {
316
+ const named = branchNaming(ctx, kids, bytes.subarray(from, to));
317
+ if (from === 0 && to === bytes.length) byBytes = named.byBytes;
318
+ return named.id;
319
+ },
320
+ );
321
+ return { id, byBytes };
322
+ }
323
+
324
+ /** The flat node over a span's single-byte atoms — the node every deposit
325
+ * interns for its whole input and for each canonical window (learning.ts
326
+ * `deposit`, `indexSubSpans`). The store's negative filter refuses most
327
+ * misses without a lookup. */
328
+ function flatNode(ctx: MindContext, span: Uint8Array): number | null {
329
+ const store = ctx.store;
330
+ return store.findFlatBranch
331
+ ? store.findFlatBranch(span)
332
+ : store.findBranch(Array.from(span, (b) => -(b + 1)));
333
+ }
334
+
335
+ /** THE READ SIDE NAMES A BRANCH EXACTLY AS THE WRITE SIDE DID. `intern`
336
+ * (store.ts) names a branch by its children; when they name none, it looks up
337
+ * the flat node over the same bytes and REUSES it (step 1b, "same bytes, same
338
+ * node") — so a deposit whose fold grouped `ver` + `!` was stored with the
339
+ * window `ver!` as that child. Reading by the children alone could never
340
+ * name such a deposit again: measured on the 31.7M-node store, 8 of 80 stored
341
+ * dialogue turns asked verbatim resolved to nothing (a 25-byte turn, a final
342
+ * `?` or `!`, …) and fell to the composition path. Same order as the write
343
+ * side: the children first, the bytes when they name nothing — and an unnamed
344
+ * child does not settle it, since the write side minted that child and still
345
+ * reached step 1b. */
346
+ function branchNaming(
347
+ ctx: MindContext,
348
+ kids: ReadonlyArray<number | null>,
349
+ span: Uint8Array,
350
+ ): { id: number | null; byBytes: boolean } {
351
+ if (kids.every((k) => k !== null)) {
352
+ const id = ctx.store.findBranch(kids as number[]);
353
+ if (id !== null) return { id, byBytes: false };
354
+ }
355
+ if (kids.length < 2) return { id: null, byBytes: false };
356
+ const id = flatNode(ctx, span);
357
+ if (id !== null && ctx.meter) ctx.meter.flatBranchNames++;
358
+ return { id, byBytes: id !== null };
359
+ }
360
+
280
361
  /** The canonical node id of a byte span: perceive it in isolation — the way
281
362
  * training did — and recover its root bottom-up. Returns null if any part is
282
363
  * unknown. */
283
364
  export function resolve(ctx: MindContext, bytes: Uint8Array): number | null {
284
365
  if (bytes.length === 0) return null;
285
366
  if (ctx.meter) ctx.meter.resolves++;
286
- const exact = foldTree(ctx, perceive(ctx, bytes), 0).node;
287
- if (exact !== null) return exact;
288
- return canonResolve(ctx, bytes);
367
+ const { id: exact, byBytes } = exactNaming(ctx, bytes);
368
+ if (exact !== null && !byBytes) return exact;
369
+ return canonResolve(ctx, bytes) ?? exact;
289
370
  }
290
371
 
291
372
  /** Equivalence-class resolution: when the exact content-addressed lookup
@@ -305,7 +386,7 @@ export function canonResolve(
305
386
  if (canon === null || !store.canonFind) return null;
306
387
  if (bytes.length < 2) return null;
307
388
  const memo = ctx.canonMemo;
308
- const memoKey = memo ? latin1Key(bytes) : "";
389
+ const memoKey = memo ? latin1(bytes) : "";
309
390
  if (memo) {
310
391
  const hit = memo.get(memoKey);
311
392
  if (hit !== undefined) return hit;
@@ -320,7 +401,7 @@ export function canonResolve(
320
401
  // skips identity rows) — the exact content-addressed lookup of the
321
402
  // canonical bytes finds it directly.
322
403
  if (key.length !== bytes.length || !bytesEqual(key, bytes)) {
323
- const direct = foldTree(ctx, perceive(ctx, key), 0).node;
404
+ const direct = exactNode(ctx, key);
324
405
  if (direct !== null) return set(direct);
325
406
  }
326
407
  if (ctx.meter) ctx.meter.canonLookups++;
@@ -336,17 +417,12 @@ export function canonResolve(
336
417
  // resolved for these bytes is their FOLD — the deposit-shaped node that
337
418
  // carries the edges and halos. Re-folding the candidate's bytes lands
338
419
  // on exactly the node the canonical-case query would have found.
339
- const folded = foldTree(ctx, perceive(ctx, bytesOf), 0).node;
420
+ const folded = exactNode(ctx, bytesOf);
340
421
  const use = folded ?? id;
341
- // THE ADMISSION PREDICATE, by its own pair of probes: `traverse.ts`'s
342
- // `leadsSomewhere` is edge-or-halo, and `hasHalo` is the one that carries
343
- // the mass bar (`mass >= minHaloMass`). Asking `haloMass(use) > 0` instead
344
- // is the same answer only while `minHaloMass <= 1` (its default): raise the
345
- // bar and this site would rank a node as leading on evidence the law
346
- // refuses. Calling `leadsSomewhere` here is not possible — `traverse.ts`
347
- // imports THIS file, so it would be a cycle — which is why the pair is
348
- // spelled out rather than named.
349
- const leads = store.hasNext(use) || store.hasHalo(use);
422
+ // THE ADMISSION PREDICATE, asked of the store that owns it (edge or halo,
423
+ // the halo tier carrying the mass bar). Asking `haloMass(use) > 0` instead
424
+ // would agree only while `minHaloMass <= 1`.
425
+ const leads = store.leadsSomewhere(use);
350
426
  if (
351
427
  best === null || (leads && !bestLeads) ||
352
428
  (leads === bestLeads && use < best)