@hviana/sema 0.7.3 → 0.7.6

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 (46) hide show
  1. package/AGENTS.md +95 -843
  2. package/README.md +11 -11
  3. package/dist/src/mind/graph-search.d.ts +32 -22
  4. package/dist/src/mind/graph-search.js +97 -54
  5. package/dist/src/mind/mind.js +14 -0
  6. package/dist/src/store-sqlite.js +17 -0
  7. package/dist/src/store.d.ts +18 -0
  8. package/dist/src/store.js +10 -0
  9. package/docs/INDEX.md +71 -0
  10. package/docs/INVARIANTS.md +19 -0
  11. package/docs/architecture/bounded-reads.md +85 -0
  12. package/docs/architecture/caches.md +89 -0
  13. package/docs/architecture/commonality.md +45 -0
  14. package/docs/architecture/cost-model.md +71 -0
  15. package/docs/architecture/determinism.md +73 -0
  16. package/docs/architecture/exact-vs-approximate.md +47 -0
  17. package/docs/architecture/factored-machinery.md +28 -0
  18. package/docs/architecture/fold-contract.md +87 -0
  19. package/docs/architecture/halo-sketch.md +99 -0
  20. package/docs/architecture/match-project.md +62 -0
  21. package/docs/architecture/mechanism-market.md +95 -0
  22. package/docs/architecture/memoization.md +96 -0
  23. package/docs/architecture/meter.md +55 -0
  24. package/docs/architecture/saturation.md +92 -0
  25. package/docs/architecture/store.md +79 -0
  26. package/docs/architecture/thresholds.md +79 -0
  27. package/docs/failures/tempting-but-wrong.md +144 -0
  28. package/docs/harness/gates.md +56 -0
  29. package/docs/mechanisms/alu.md +75 -0
  30. package/docs/mechanisms/cast.md +75 -0
  31. package/docs/mechanisms/confluence.md +36 -0
  32. package/docs/mechanisms/cover.md +54 -0
  33. package/docs/mechanisms/extraction.md +53 -0
  34. package/docs/mechanisms/prefix-completion.md +54 -0
  35. package/docs/mechanisms/recall.md +69 -0
  36. package/docs/mechanisms/reference.md +58 -0
  37. package/jsr.json +1 -1
  38. package/package.json +1 -1
  39. package/src/mind/graph-search.ts +101 -55
  40. package/src/mind/mind.ts +14 -0
  41. package/src/store-sqlite.ts +19 -0
  42. package/src/store.ts +22 -0
  43. package/test/89-completion-recursion.test.mjs +30 -10
  44. package/test/97-store-seed.test.mjs +105 -0
  45. package/test/98-completion-chaining.test.mjs +140 -0
  46. package/HOW_IT_WORKS.md +0 -5836
package/README.md CHANGED
@@ -41,7 +41,7 @@ No weights. No gradients. No training loop. No neural network. No GPU.
41
41
  > Vector Symbolic Architecture (Plate 1995; Kanerva 2009) over a
42
42
  > content-addressable memory, with inference by weighted automated deduction
43
43
  > (Knuth 1977; Felzenszwalb & McAllester 2007). Each term is grounded in
44
- > [HOW_IT_WORKS.md](HOW_IT_WORKS.md).
44
+ > [docs/INDEX.md](docs/INDEX.md).
45
45
 
46
46
  ---
47
47
 
@@ -314,16 +314,16 @@ start talking — no install, no runtime, no API key.
314
314
 
315
315
  ## ✦ Learn more
316
316
 
317
- | Document | What's inside |
318
- | :----------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- |
319
- | 📘 **[HOW_IT_WORKS.md](HOW_IT_WORKS.md)** | The full theory: vector symbolic architectures, the Merkle DAG, distributional halos, weighted deduction — concepts, diagrams, and extensive pseudocode. |
320
- | 🛠️ **[AGENTS.md](AGENTS.md)** | The development manual: repo layout, build/test, internals, invariants, and recipes for extending the system. |
321
- | 🎓 **[CITATION.cff](CITATION.cff)** | How to cite Sema in academic work. |
322
- | ⚖️ **[LICENSE.md](LICENSE.md)** | PolyForm Noncommercial License 1.0.0. |
323
- | 📚 **[DATASETS.md](DATASETS.md)** | Training corpora: provenance, per-corpus attribution, and how a trained memory file is licensed. |
324
- | 💼 **[COMMERCIAL-LICENSE.md](COMMERCIAL-LICENSE.md)** | Commercial licensing terms and contact. |
325
- | 🤗 **[Trained examples](https://huggingface.co/buckets/hviana/sema-trained-v1)** | Pre-trained memory files you can download and use directly. |
326
- | 💿 **[Binary examples](https://huggingface.co/buckets/hviana/sema-binary-examples)** | Ready-to-run web chat apps for Windows, Mac, and Linux — one file, no install. |
317
+ | Document | What's inside |
318
+ | :----------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
319
+ | 📘 **[docs/INDEX.md](docs/INDEX.md)** | Architecture docs: single-system laws (docs/architecture/), mechanisms, invariants, and harness gates — the entry point for theory and implementation. |
320
+ | 🛠️ **[AGENTS.md](AGENTS.md)** | The development manual: repo layout, build/test, internals, invariants, and recipes for extending the system. |
321
+ | 🎓 **[CITATION.cff](CITATION.cff)** | How to cite Sema in academic work. |
322
+ | ⚖️ **[LICENSE.md](LICENSE.md)** | PolyForm Noncommercial License 1.0.0. |
323
+ | 📚 **[DATASETS.md](DATASETS.md)** | Training corpora: provenance, per-corpus attribution, and how a trained memory file is licensed. |
324
+ | 💼 **[COMMERCIAL-LICENSE.md](COMMERCIAL-LICENSE.md)** | Commercial licensing terms and contact. |
325
+ | 🤗 **[Trained examples](https://huggingface.co/buckets/hviana/sema-trained-v1)** | Pre-trained memory files you can download and use directly. |
326
+ | 💿 **[Binary examples](https://huggingface.co/buckets/hviana/sema-binary-examples)** | Ready-to-run web chat apps for Windows, Mac, and Linux — one file, no install. |
327
327
 
328
328
  ---
329
329
 
@@ -270,37 +270,47 @@ export declare class GraphSearch {
270
270
  * and recompose into a deeper learnt form (→ FINAL). This is why a single
271
271
  * edge-target needs no bespoke logic — it routes back through {@link solve}.
272
272
  *
273
- * Re-recognition (not the node's tree children) is what surfaces the learnt
274
- * parts: content-defined chunking may cut "p1 p2" as "p1 p"|"2", so only
275
- * recognising the bytes recovers p1 and p2 as the forms the graph knows.
273
+ * The produced bytes are decomposed by the machinery that owns their shape:
274
+ * the span's LEAVES and SPLITS drive the split rule, which resolves each half
275
+ * through findLeaf — so a part that straddles a content-defined cut is still
276
+ * recovered even though the node's tree children need not align with the
277
+ * learnt parts (the fold cuts "p1 p2" as "p1 p"|"2"). Recognised SITES are
278
+ * filtered to the node's own kids: a produced form is completed out of what
279
+ * it was built from, never by re-recognising arbitrary forms inside it. That
280
+ * filter is load-bearing — a 37-byte dialogue sentence carries seven hub
281
+ * openers, and re-covering them chained through the corpus's whole
282
+ * continuation population: 2.5 GB and OOM for `respond("hi.")`.
276
283
  *
277
284
  * The recovered answer is accepted only when it MOVED and names a LEARNT node
278
285
  * ({@link resolve}) — the graph itself gates against re-expanding a contained
279
- * form ("ice is cold" ⊅→ "ice is cold is cold").
286
+ * form ("ice is cold" ⊅→ "ice is cold is cold"). An ACCEPTED completion is
287
+ * then re-covered in turn, so a chain runs as deep as the graph licenses; a
288
+ * rejected one ends its branch, so no work is spent past it.
280
289
  *
281
- * Termination is STRUCTURAL: a produced node is re-covered once, never inside
282
- * another re-cover (see the guard below), so one cover pays at most one
283
- * nested {@link solve} per distinct produced node it actually reaches, and
284
- * {@link recompleteMemo} collapses a repeat to nothing.
285
- *
286
- * This comment used to argue termination from "distinct node ids are finite
287
- * and each finished completion is memoised". That is a bound of N — the one
288
- * AGENTS §2.8 forbids — and it was load-bearing, not pedantic: nested, the
289
- * recursion reached depth 331 and 9.1 GB on an 18.9M-node store for a 2-byte
290
- * query and did not terminate, which is what killed a 5 h training run at its
291
- * checkpoint recall. Guard: test/89-completion-recursion.test.mjs. */
290
+ * Termination and cost are STRUCTURAL: {@link recompleteOpen} is the chain's
291
+ * stack (membership is the cycle guard), {@link recompleteMemo} re-covers each
292
+ * node at most once, only accepted completions recurse, and every level
293
+ * deepens only the top derivation — so a cover pays for the chain it finds,
294
+ * not for how densely the corpus interconnects the forms it passes through.
295
+ * Guard: test/89-completion-recursion.test.mjs. */
292
296
  private recompleteNode;
293
297
  /** Per-cover memo of each produced node's completion (so the many terminal
294
298
  * outs of a long query re-cover each distinct node at most once); reset at the
295
299
  * top of {@link cover}. */
296
300
  private recompleteMemo;
297
- /** The node currently being re-completed — the recursion stack, and so also
298
- * the nesting depth. {@link recompleteNode} refuses to start while it is
299
- * non-empty (one re-cover per produced node, never one inside another), which
300
- * is what keeps a query's cost proportional to its answer rather than to the
301
- * corpus; it therefore holds at most one id. A Set, not a flag, because it
302
- * states WHICH node is open — the invariant a reader needs to check the
303
- * guard, and what makes the old cycle-guard reading still hold. */
301
+ /** The derivation sink of the TOP cover, threaded into every nested
302
+ * completion so a produced form's own recompositions are reported in the
303
+ * same trace instead of vanishing after the first layer. Undefined when
304
+ * nothing is inspecting, so an uninspected response pays nothing. */
305
+ private derivationSink?;
306
+ /** The chain of nodes currently being re-completed — the recursion STACK.
307
+ * MEMBERSHIP is the cycle guard ({@link recompleteNode} refuses a node
308
+ * already open on this chain), which is what lets a completion recurse as
309
+ * deep as the graph licenses while the work stays the answer's: the
310
+ * recursion only advances through an ACCEPTED completion, every produced
311
+ * form is decomposed into its own kids, and the memo re-covers each node at
312
+ * most once per cover. A Set, not a flag, because it states WHICH node is
313
+ * open — the invariant a reader needs to check the guard. */
304
314
  private recompleteOpen;
305
315
  /** out(i,j,bytes,…): index it for the binary rules, then offer splicing a
306
316
  * learnt connector (the in-search bridge), splitting (at a sub-leaf form
@@ -230,12 +230,26 @@ export class GraphSearch {
230
230
  // through (completion is cover, recursively — see {@link recompleteNode}).
231
231
  this.recompleteOpen.clear();
232
232
  this.recompleteMemo = new Map();
233
- return this.solve(queryLen, {
233
+ // The top cover's derivation sink is threaded into every nested completion
234
+ // so the recompositions a produced form needs are reported in the same
235
+ // trace instead of vanishing after the first layer.
236
+ this.derivationSink = onDerivation;
237
+ const solved = this.solve(queryLen, {
234
238
  sites,
235
239
  leaves,
236
240
  splits,
237
241
  starts,
238
242
  }, conceptTarget, substitutions, connectors, computedResults, onDerivation);
243
+ // Deepening runs HERE, once, on the derivation the top cover CHOSE — never
244
+ // inside the nested solve a completion runs. Nesting the deepening is what
245
+ // made per-query cost track how densely the corpus interconnects the forms
246
+ // passed through: every re-cover fanned out into the whole corpus's
247
+ // continuations instead of following the chain the answer itself licensed.
248
+ // With deepening only at the top, `recompleteNode` walks the accepted chain
249
+ // one link at a time (its own memo and stack), so the work is the answer's.
250
+ return solved === null
251
+ ? null
252
+ : { segs: this.deepen(solved.segs), cost: solved.cost };
239
253
  }
240
254
  /** Build the deduction system for one span and return its lightest cover's
241
255
  * chosen spans — the SINGLE routine the query and every produced composite
@@ -270,7 +284,7 @@ export class GraphSearch {
270
284
  onDerivation(readDerivation(derivation, substitutions !== undefined));
271
285
  }
272
286
  return derivation
273
- ? { segs: this.deepen(readCover(derivation)), cost: derivation.cost }
287
+ ? { segs: readCover(derivation), cost: derivation.cost }
274
288
  : null;
275
289
  }
276
290
  /** Re-cover the CHOSEN fixpoint spans, in place.
@@ -722,55 +736,51 @@ export class GraphSearch {
722
736
  * and recompose into a deeper learnt form (→ FINAL). This is why a single
723
737
  * edge-target needs no bespoke logic — it routes back through {@link solve}.
724
738
  *
725
- * Re-recognition (not the node's tree children) is what surfaces the learnt
726
- * parts: content-defined chunking may cut "p1 p2" as "p1 p"|"2", so only
727
- * recognising the bytes recovers p1 and p2 as the forms the graph knows.
739
+ * The produced bytes are decomposed by the machinery that owns their shape:
740
+ * the span's LEAVES and SPLITS drive the split rule, which resolves each half
741
+ * through findLeaf — so a part that straddles a content-defined cut is still
742
+ * recovered even though the node's tree children need not align with the
743
+ * learnt parts (the fold cuts "p1 p2" as "p1 p"|"2"). Recognised SITES are
744
+ * filtered to the node's own kids: a produced form is completed out of what
745
+ * it was built from, never by re-recognising arbitrary forms inside it. That
746
+ * filter is load-bearing — a 37-byte dialogue sentence carries seven hub
747
+ * openers, and re-covering them chained through the corpus's whole
748
+ * continuation population: 2.5 GB and OOM for `respond("hi.")`.
728
749
  *
729
750
  * The recovered answer is accepted only when it MOVED and names a LEARNT node
730
751
  * ({@link resolve}) — the graph itself gates against re-expanding a contained
731
- * form ("ice is cold" ⊅→ "ice is cold is cold").
752
+ * form ("ice is cold" ⊅→ "ice is cold is cold"). An ACCEPTED completion is
753
+ * then re-covered in turn, so a chain runs as deep as the graph licenses; a
754
+ * rejected one ends its branch, so no work is spent past it.
732
755
  *
733
- * Termination is STRUCTURAL: a produced node is re-covered once, never inside
734
- * another re-cover (see the guard below), so one cover pays at most one
735
- * nested {@link solve} per distinct produced node it actually reaches, and
736
- * {@link recompleteMemo} collapses a repeat to nothing.
737
- *
738
- * This comment used to argue termination from "distinct node ids are finite
739
- * and each finished completion is memoised". That is a bound of N — the one
740
- * AGENTS §2.8 forbids — and it was load-bearing, not pedantic: nested, the
741
- * recursion reached depth 331 and 9.1 GB on an 18.9M-node store for a 2-byte
742
- * query and did not terminate, which is what killed a 5 h training run at its
743
- * checkpoint recall. Guard: test/89-completion-recursion.test.mjs. */
756
+ * Termination and cost are STRUCTURAL: {@link recompleteOpen} is the chain's
757
+ * stack (membership is the cycle guard), {@link recompleteMemo} re-covers each
758
+ * node at most once, only accepted completions recurse, and every level
759
+ * deepens only the top derivation — so a cover pays for the chain it finds,
760
+ * not for how densely the corpus interconnects the forms it passes through.
761
+ * Guard: test/89-completion-recursion.test.mjs. */
744
762
  recompleteNode(node) {
745
763
  if (!this.host.recogniseSpan)
746
764
  return null;
747
765
  const memo = this.recompleteMemo;
748
766
  if (memo.has(node))
749
767
  return memo.get(node) ?? null;
750
- // ONE re-cover per produced node — never a re-cover inside a re-cover.
751
- //
752
768
  // Re-covering is how a PRODUCED node's bytes enter the search at all: the
753
- // cover machinery otherwise only ever sees the QUERY's spans. That is
754
- // needed once. The alternation of decomposition and recomposition that
755
- // follows — parts rewriting several times, siblings fusing, a recomposition
756
- // feeding another — is the main search's own fuse/`rcmp` work, not this
757
- // recursion's: 15-decomposition-gap §9–§12 all pass with this method
758
- // disabled outright, and only §6 (the produced composite "p1 p2", whose
759
- // bytes nothing else brings in) needs it.
769
+ // cover machinery otherwise only ever sees the QUERY's spans. The recursion
770
+ // is allowed to nest — a chain IS nested completions — but it is bounded so
771
+ // the work stays the ANSWER's (AGENTS §2.8): the stack below is the cycle
772
+ // guard, only ACCEPTED completions recurse, and the nested solve decomposes
773
+ // the form by its own shape instead of re-recognising the corpus's hub forms
774
+ // inside it.
760
775
  //
761
- // Nesting it was the defect. Each level is a full {@link solve} with its
762
- // own agenda and chart, exploring from a node the answer never asked about,
763
- // so per-query cost tracked how densely the corpus interconnects the forms
764
- // passed through — the growth AGENTS §2.8 forbids. Measured on an
765
- // 18.9M-node store: depth 331 and 9.1 GB for a 2-byte query, not
766
- // terminating; and on the guard corpus every one of 125 nested re-covers
767
- // was REJECTED by the resolve() gate below, expanding a 70-byte node into a
768
- // 374-byte concatenation that names nothing. All of it was waste.
769
- //
770
- // `recompleteOpen` is that stack, so a non-empty stack means we are already
771
- // inside one. This subsumes the old cycle guard: a node cannot recurse
772
- // back into itself when nothing recurses at all.
773
- if (this.recompleteOpen.size > 0)
776
+ // `recompleteOpen` IS the stack of the chain being built, so MEMBERSHIP is
777
+ // the cycle guard: a node already open on this chain cannot re-enter it.
778
+ // Testing the NODE — not the stack's size — is what lets a completion
779
+ // recurse as deep as the graph licenses, exactly the intrinsic convergence
780
+ // {@link solve}'s contract states. Work stays the answer's because the
781
+ // recursion only ever advances through an ACCEPTED completion (below) and
782
+ // {@link cover} deepens only the top derivation.
783
+ if (this.recompleteOpen.has(node))
774
784
  return null;
775
785
  // A leaf or single-child node has no parts to recompose; skip before the
776
786
  // costly recognition so a plain terminal answer pays nothing.
@@ -782,16 +792,43 @@ export class GraphSearch {
782
792
  const bytes = this.store.bytesPrefix(node, ALL);
783
793
  this.recompleteOpen.add(node);
784
794
  try {
785
- // Completion is cover: re-cover the produced bytes through the SAME solve
786
- // routine, recognising them afresh. No concepts/connectors (those need the
787
- // caller's async pre-resolution) — the recursion explores edges and fusion,
788
- // which is what a deeper rewrite chain is made of.
789
- const solved = this.solve(bytes.length, this.host.recogniseSpan(bytes), new Map());
795
+ // Completion is cover, but the produced form is decomposed by its own
796
+ // shape. The LEAVES and SPLITS are kept whole, so the split rule still
797
+ // recovers a part that straddles a content-defined cut (the fold cuts
798
+ // "p1 p2" as "p1 p"|"2", and findLeaf still resolves p1 and p2). The
799
+ // recognised SITES are filtered to the node's own kids, because
800
+ // re-recognising arbitrary forms inside the bytes is what let a hub-heavy
801
+ // utterance explode: a 37-byte dialogue sentence carries seven hub
802
+ // openers, and re-covering them chained through the corpus's whole
803
+ // continuation population (2.5 GB and OOM for `"hi."`). No
804
+ // concepts/connectors either (those need the caller's async
805
+ // pre-resolution) — the recursion follows edges and fusion, which is what
806
+ // a deeper rewrite chain is made of.
807
+ const rec = this.host.recogniseSpan(bytes);
808
+ const kids = new Set(nrec.kids);
809
+ const solved = this.solve(bytes.length, {
810
+ sites: rec.sites.filter((s) => kids.has(s.payload)),
811
+ leaves: rec.leaves,
812
+ splits: rec.splits,
813
+ starts: rec.starts,
814
+ }, new Map(), undefined, undefined, undefined, this.derivationSink);
790
815
  const answer = solved && concatBytes(solved.segs.map((s) => s.bytes));
791
- const out = (answer !== null && !bytesEqual(answer, bytes) &&
792
- this.host.resolve(answer) !== null)
793
- ? answer
816
+ // ACCEPT, then CONTINUE THE CHAIN — but only along an accepted
817
+ // completion. A re-cover whose result is not itself a learnt node (the
818
+ // 70→374-byte concatenations that name nothing) ends its branch here
819
+ // instead of recursing into work the answer never asked for, and an
820
+ // accepted one names a node that is re-covered in turn. That is what
821
+ // keeps a deep chain possible while the work stays proportional to the
822
+ // chain rather than to the corpus's interconnections.
823
+ const composed = answer !== null && !bytesEqual(answer, bytes)
824
+ ? this.host.resolve(answer)
794
825
  : null;
826
+ if (composed === null) {
827
+ memo.set(node, null);
828
+ return null;
829
+ }
830
+ const deeper = this.recompleteNode(composed);
831
+ const out = deeper ?? answer;
795
832
  memo.set(node, out);
796
833
  return out;
797
834
  }
@@ -803,13 +840,19 @@ export class GraphSearch {
803
840
  * outs of a long query re-cover each distinct node at most once); reset at the
804
841
  * top of {@link cover}. */
805
842
  recompleteMemo = new Map();
806
- /** The node currently being re-completed — the recursion stack, and so also
807
- * the nesting depth. {@link recompleteNode} refuses to start while it is
808
- * non-empty (one re-cover per produced node, never one inside another), which
809
- * is what keeps a query's cost proportional to its answer rather than to the
810
- * corpus; it therefore holds at most one id. A Set, not a flag, because it
811
- * states WHICH node is open — the invariant a reader needs to check the
812
- * guard, and what makes the old cycle-guard reading still hold. */
843
+ /** The derivation sink of the TOP cover, threaded into every nested
844
+ * completion so a produced form's own recompositions are reported in the
845
+ * same trace instead of vanishing after the first layer. Undefined when
846
+ * nothing is inspecting, so an uninspected response pays nothing. */
847
+ derivationSink;
848
+ /** The chain of nodes currently being re-completed — the recursion STACK.
849
+ * MEMBERSHIP is the cycle guard ({@link recompleteNode} refuses a node
850
+ * already open on this chain), which is what lets a completion recurse as
851
+ * deep as the graph licenses while the work stays the answer's: the
852
+ * recursion only advances through an ACCEPTED completion, every produced
853
+ * form is decomposed into its own kids, and the memo re-covers each node at
854
+ * most once per cover. A Set, not a flag, because it states WHICH node is
855
+ * open — the invariant a reader needs to check the guard. */
813
856
  recompleteOpen = new Set();
814
857
  /** out(i,j,bytes,…): index it for the binary rules, then offer splicing a
815
858
  * learnt connector (the in-search bridge), splitting (at a sub-leaf form
@@ -143,10 +143,24 @@ export class Mind {
143
143
  const { store: optsStore, mechanisms: userMechs, mechanismFactories: userFacts, canon: optsCanon, profile: optsProfile, ...rest } = (optsOrCfg ?? {});
144
144
  this._canonOpt = optsCanon ?? null;
145
145
  this._profile = optsProfile === true;
146
+ // `explicitSeed` is read BEFORE resolveConfig folds the default in, so
147
+ // the store can be consulted only when the caller did not choose.
148
+ const explicitSeed = rest.seed;
146
149
  this.cfg = resolveConfig(rest);
147
150
  this.store = optsStore ?? new SQliteStore({
148
151
  maxGroup: this.cfg.geometry.maxGroup,
149
152
  });
153
+ // THE ARTIFACT'S SEED GOVERNS. `train.seed` is recovered by the store at
154
+ // open, exactly like `train.D` and `geometry.maxGroup`. The seed feeds
155
+ // `makeKeyring`, `Space.rand` and the `Alphabet` below, so folding a
156
+ // query under config.ts's default (42) against a store trained with
157
+ // another seed (e.g. 7) lands in a DIFFERENT vector space than the one
158
+ // the artifact's nodes were folded into: recognition and resonance then
159
+ // read the wrong space and every answer degrades silently. An explicit
160
+ // caller seed still wins — this only replaces the unconfigured default.
161
+ if (explicitSeed === undefined && this.store.trainSeed !== null) {
162
+ this.cfg.seed = this.store.trainSeed;
163
+ }
150
164
  userMechanisms = userMechs ?? [];
151
165
  userFactories = userFacts ?? [];
152
166
  }
@@ -360,6 +360,23 @@ export class SQliteStore extends AbstractStore {
360
360
  this._maxGroup = g;
361
361
  }
362
362
  }
363
+ // Recover the TRAINING seed exactly as D and maxGroup are recovered. The
364
+ // seed seeds the alphabet and the seat keyring (mind.ts), so a Mind that
365
+ // folds a query under any other seed lands in a different vector space
366
+ // than the one this artifact's nodes were folded into — recognition,
367
+ // resonance and every mechanism downstream then read the wrong space. The
368
+ // trainer persists `train.seed` and refuses to resume against a store
369
+ // trained with a different one (example/train_base/main.ts), so the value
370
+ // is authoritative for this artifact. Absent on a store that was never
371
+ // trained, where the caller's configured seed stands.
372
+ {
373
+ const row = this.sqlite.prepare("SELECT val FROM meta WHERE key = 'train.seed'").get();
374
+ if (row) {
375
+ const s = Number(row.val);
376
+ if (Number.isInteger(s) && s >= 0)
377
+ this._trainSeed = s;
378
+ }
379
+ }
363
380
  // Persist maxGroup to meta when opening a FRESH store (no rows yet) so
364
381
  // indexSubtree always sees the training-time value even when the store is
365
382
  // accessed without a Mind / full snapshot.
@@ -114,6 +114,16 @@ export declare class BoundedMap<K, V> {
114
114
  }
115
115
  export interface Store {
116
116
  readonly D: number;
117
+ /** The seed the artifact was TRAINED with, recovered from the store's own
118
+ * `train.seed` metadata, or null for a store that was never trained.
119
+ *
120
+ * This is not decoration: the seed feeds the alphabet and the seat keyring
121
+ * (see the Mind constructor), so folding a query under any other seed lands
122
+ * in a different vector space than the one the artifact's nodes were folded
123
+ * into. A Mind opening a trained store MUST adopt this seed unless the
124
+ * caller explicitly overrides it — the same discipline that recovers
125
+ * `train.D` and `geometry.maxGroup` from the metadata. */
126
+ readonly trainSeed: number | null;
117
127
  /** The work accumulator for the inference call in flight, or null. The
118
128
  * Mind attaches one per profiled response and detaches it after (see
119
129
  * src/meter.ts). A store MUST only ever write to it — no read may reach
@@ -475,6 +485,10 @@ export declare abstract class AbstractStore implements Store {
475
485
  protected efFor(clusterCount: number): number;
476
486
  protected _D: number;
477
487
  protected _maxGroup: number;
488
+ /** `train.seed` recovered by the backend at open, or null when the store was
489
+ * never trained. A backend that omits it simply reports null, which leaves
490
+ * the caller's configured seed in force. */
491
+ protected _trainSeed: number | null;
478
492
  protected readonly minHaloMass: number;
479
493
  protected readonly efSearch: number;
480
494
  protected readonly overfetch: number;
@@ -563,6 +577,10 @@ export declare abstract class AbstractStore implements Store {
563
577
  protected _edgeSrcCount: number;
564
578
  constructor(config: StoreConfig, D: number, maxGroup: number);
565
579
  get D(): number;
580
+ /** The seed the artifact was trained with, recovered from `train.seed` at
581
+ * open. Null for a store that was never trained. See
582
+ * {@link Store.trainSeed} for why this must govern inference. */
583
+ get trainSeed(): number | null;
566
584
  /** Await the async initialisation performed by the concrete constructor. */
567
585
  protected _ensureReady(): Promise<void>;
568
586
  has(id: NodeId): boolean;
package/dist/src/store.js CHANGED
@@ -434,6 +434,10 @@ export class AbstractStore {
434
434
  // ── Config ─────────────────────────────────────────────────────────────
435
435
  _D;
436
436
  _maxGroup;
437
+ /** `train.seed` recovered by the backend at open, or null when the store was
438
+ * never trained. A backend that omits it simply reports null, which leaves
439
+ * the caller's configured seed in force. */
440
+ _trainSeed = null;
437
441
  minHaloMass;
438
442
  efSearch;
439
443
  overfetch;
@@ -552,6 +556,12 @@ export class AbstractStore {
552
556
  get D() {
553
557
  return this._D;
554
558
  }
559
+ /** The seed the artifact was trained with, recovered from `train.seed` at
560
+ * open. Null for a store that was never trained. See
561
+ * {@link Store.trainSeed} for why this must govern inference. */
562
+ get trainSeed() {
563
+ return this._trainSeed;
564
+ }
555
565
  /** Await the async initialisation performed by the concrete constructor. */
556
566
  async _ensureReady() {
557
567
  if (!this._ready)
package/docs/INDEX.md ADDED
@@ -0,0 +1,71 @@
1
+ # Sema Documentation Index
2
+
3
+ Sema is a single system stated three ways: the law lives in `docs/architecture/`
4
+ (what holds), the prescription in `AGENTS.md` bootloader (what to do and where),
5
+ and the proof in `test/` (pins that fail when the law is broken).
6
+
7
+ ## Routing — what to read for each task
8
+
9
+ | Task | Read | Why |
10
+ | --------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
11
+ | Add a mechanism | `docs/architecture/mechanism-market.md` + `docs/mechanisms/*.md` | Market contract: decoupled, declared competence, visible budget, evidence travels |
12
+ | Add a threshold | `docs/architecture/thresholds.md` | All cutoffs are formulas over D/W/N in `geometry.ts`; `config.ts` holds only budgets |
13
+ | Debug an answer | `docs/architecture/cost-model.md` + `src/meter.ts` | One cost ladder (`MICRO`/`STEP`/`CONCEPT`/`PASS`) decides every grounding choice |
14
+ | Understand the fold | `docs/architecture/fold-contract.md` | Deposit and inference must compute the same tree; boundaries are not turn metadata |
15
+ | Add a store backend | `docs/architecture/store.md` + `docs/architecture/bounded-reads.md` | `AbstractStore` owns domain logic; backends are thin wrappers with capped reads |
16
+ | Add an ALU operation | `src/alu/README.md` | One `registry.derive` per op composing existing ops; no new `derive` needed |
17
+ | Add a matcher or projection | `docs/architecture/match-project.md` | Mechanisms are `(matcher, direction, gate)` configs over the shared `match.ts` family |
18
+ | Add a deduction rule | `docs/architecture/cost-model.md` + `docs/architecture/determinism.md` | Place cost on the ladder, keep heuristic admissible, extend `classifyMove` |
19
+ | Change vector search | `docs/architecture/exact-vs-approximate.md` + `docs/architecture/bounded-reads.md` | Scores propose, bytes dispose; ANN is bounded by `hubBound` |
20
+ | Profile or bound work | `docs/architecture/meter.md` + `docs/architecture/bounded-reads.md` | `meter.ts` is write-only; counters are product, phases are hints |
21
+
22
+ ## Architecture laws (13)
23
+
24
+ | Law | File | Summary | Pins |
25
+ | --- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------- |
26
+ | 1 | `docs/architecture/determinism.md` | No `Math.random`/`Date.now` in behaviour; seed-derived randomness; corpus-determined tie-breaks | `test/20` |
27
+ | 2 | `docs/architecture/thresholds.md` | Every decision cutoff derived in `geometry.ts` over D/W/N; no tunable knobs | `test/40`, `test/64` |
28
+ | 3 | `docs/architecture/exact-vs-approximate.md` | Vector scores rank only; identity via content-addressed lookup; five graded ladders | `test/51` |
29
+ | 4 | `docs/architecture/cost-model.md` | Single ladder `MICRO`/`STEP`/`CONCEPT`/`PASS`; weight `moves + PASS·unaccounted`; `STEP`-grade compare | `test/04`, `test/55` |
30
+ | 5 | `docs/architecture/match-project.md` | Shared `match.ts` family (`locate`/`alignGraded`/`frameSlots`/`project`); voicing gates belong to consumers | `test/24`, `test/76` |
31
+ | 6 | `docs/architecture/mechanism-market.md` | `PipelineMechanism` (`floor`/`run`/`parse`); admissible-floor pruning and investment discipline | `test/01`, `test/04` |
32
+ | 7 | `docs/architecture/commonality.md` | Two populations: corpus-global (`reachOf`+`dominates`) vs weave-local (`depth[]`) | `test/17`, `test/34` |
33
+ | 8 | `docs/architecture/bounded-reads.md` | No per-query read grows with N; `hubBound=√N` enforced at store via LIMIT/probe/prefix caps | `test/77`, `test/90` |
34
+ | 9 | `docs/architecture/store.md` | `AbstractStore` owns dedup/indexing/batch; `store-sqlite.ts` is thin wrappers; canon index optional | `test/08` |
35
+ | 10 | `docs/architecture/fold-contract.md` | `perceiveDeposit` and `perceive` agree; `contentLevels` is single boundary rule; no W/offset dependence | `test/59`, `test/63` |
36
+ | 11 | `docs/architecture/memoization.md` | `Precomputed` is per-response lazy cache (promise-cached async); `beginResponse`/`endResponse` lifecycle | `test/42` |
37
+ | 12 | `docs/architecture/saturation.md` | Every walk names a deciding saturation beside its cap; cap is safety net, not decision | `test/27`, `test/16` |
38
+ | 13 | `docs/architecture/meter.md` | `meter.ts` is write-only work accounting; counts are deterministic, phases nest | `test/55` |
39
+
40
+ ## Mechanisms (8)
41
+
42
+ | Mechanism | File | Role |
43
+ | ----------------- | -------------------------------------- | --------------------------------------------------------- |
44
+ | cover | `docs/mechanisms/cover.md` | Exact/computed-span covering via `GraphSearch` |
45
+ | cast | `docs/mechanisms/cast.md` | Weave-local analogy via `depth[]` frame gate |
46
+ | confluence | `docs/mechanisms/confluence.md` | Corpus-global filler/scaffolding gate over climb |
47
+ | extraction | `docs/mechanisms/extraction.md` | Located-frame read-out with anchored span accounting |
48
+ | reference | `docs/mechanisms/reference.md` | Slot-bound voicing of asker-supplied referents |
49
+ | recall | `docs/mechanisms/recall.md` | Nearest stored form; echo tier via substitution bridge |
50
+ | prefix-completion | `docs/mechanisms/prefix-completion.md` | Literal prefix of exactly one trained form |
51
+ | alu | `docs/mechanisms/alu.md` | Authoritative computed spans (`parse` → `aluToMechanism`) |
52
+
53
+ ## Supporting docs
54
+
55
+ | Doc | Role |
56
+ | ----------------------------------------- | ------------------------------------------------------------------------------- |
57
+ | `docs/architecture/caches.md` | Every acceleration is a `BoundedMap`; miss re-derives; budgets in `StoreConfig` |
58
+ | `docs/architecture/halo-sketch.md` | Halo & sketch — distributional memory, quantization, bottom-k profiles |
59
+ | `docs/architecture/factored-machinery.md` | Single-definition contracts table — one owner per shared symbol |
60
+
61
+ ## Cross-cutting
62
+
63
+ - `docs/INVARIANTS.md` — the five invariants (determinism, derived thresholds,
64
+ exact-decides, one cost currency, bounded reads) with file-level routing.
65
+ - `docs/failures/tempting-but-wrong.md` — refuted simplifications that passed
66
+ review but failed pins (e.g. reordering ladders, flattening attention
67
+ asymmetries, one-cone-exhausted stop).
68
+ - `docs/harness/gates.md` — how `AGENTS.md` recipes,
69
+ `bench/profile-inference.mjs`, and `test/*.test.mjs` enforce the laws.
70
+ - `docs/architecture/` — full per-law derivation (each file states why the law
71
+ is this way; no separate HOW).
@@ -0,0 +1,19 @@
1
+ # INVARIANTS — Laws, Proofs, Derivations
2
+
3
+ > Law in `docs/architecture/*.md`, proof in `test/*.test.mjs`.
4
+
5
+ | # | Law | Where defined (src symbol) | Pins (test/N) | Doc (docs/architecture/*.md) |
6
+ | -- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ---------------------------- |
7
+ | 1 | Determinism | `src/config.ts:seed` `src/alphabet.ts:Alphabet` `src/mind/traverse.ts:guidedFirst` | `test/20` `test/42` | `determinism.md` |
8
+ | 2 | Derived thresholds | `src/geometry.ts:mergeThreshold,identityBar,reachThreshold,significanceBar,consensusFloor,dominates` | `test/64` `test/40` | `thresholds.md` |
9
+ | 3 | Exact decides / approximate proposes | `src/mind/primitives.ts:resolve` `src/mind/match.ts:locate,alignGraded` `src/mind/resonance.ts:bridge` | `test/51` | `exact-vs-approximate.md` |
10
+ | 4 | One cost currency | `src/mind/graph-search.ts:MICRO,STEP,CONCEPT,PASS` `src/derive:lightestDerivation` (min,+) `src/mind/attention.ts:poolVotes` (+,+) | `test/55` `test/04` | `cost-model.md` |
11
+ | 5 | Bounded reads | `src/store.ts:AbstractStore:nextFirst,parentsFirst,containersSlice,hasNext,bytesPrefix` `src/mind/traverse.ts:hubBound,hubCap` | `test/90` `test/14` | `bounded-reads.md` |
12
+ | 6 | Fold contract | `src/geometry.ts:contentLevels` `src/mind/canonical.ts:canonicalWindows,chainReach` `src/canon.ts:canonicalizer` | `test/59` `test/63` | `fold-contract.md` |
13
+ | 7 | Mechanism market | `src/mind/pipeline-mechanism.ts:PipelineMechanism,Precomputed` `src/mind/pipeline.ts:think,worthRunning` | `test/01` `test/04` | `mechanism-market.md` |
14
+ | 8 | Two commonality measures | `src/mind/traverse.ts:reachOf,dominates,corpusN` (global) `src/mind/match.ts:depth[],MIN_WEAVE` (weave-local) | `test/17` `test/34` | `commonality.md` |
15
+ | 9 | Memoization idempotence | `src/mind/pipeline-mechanism.ts:Precomputed` `src/mind/mind.ts:beginResponse,endResponse,_resolvedSubtrees` | `test/42` | `memoization.md` |
16
+ | 10 | Caches as budgets | `src/store.ts:BoundedMap` `src/config.ts:StoreConfig:bytesCacheMax,recCacheBytes,haloCacheBytes` | `test/96` `test/91` | `caches.md` |
17
+ | 11 | Honest degradation | `src/mind/pipeline.ts:weight=moves+PASS*unaccounted` `src/store.ts:BoundedMap:miss→re-derive` | `test/28` `test/84` | `store.md`+`caches.md` |
18
+ | 12 | Meter contracts | `src/meter.ts:Meter,PhaseCost,time` `src/mind/pipeline-mechanism.ts:Precomputed.shared` | `test/55` | `meter.md` |
19
+ | 13 | Saturation | `src/mind/traverse.ts:edgeAncestors:SaturationReason` `src/mind/junction.ts:junctionContainersFrom` `src/mind/resonance.ts:pivotInto` | `test/27` `test/16` | `saturation.md` |