@hviana/sema 0.7.3 → 0.7.5
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.
- package/AGENTS.md +95 -843
- package/README.md +11 -11
- package/dist/src/mind/mind.js +14 -0
- package/dist/src/store-sqlite.js +17 -0
- package/dist/src/store.d.ts +18 -0
- package/dist/src/store.js +10 -0
- package/docs/INDEX.md +71 -0
- package/docs/INVARIANTS.md +19 -0
- package/docs/architecture/bounded-reads.md +85 -0
- package/docs/architecture/caches.md +89 -0
- package/docs/architecture/commonality.md +45 -0
- package/docs/architecture/cost-model.md +71 -0
- package/docs/architecture/determinism.md +73 -0
- package/docs/architecture/exact-vs-approximate.md +47 -0
- package/docs/architecture/factored-machinery.md +28 -0
- package/docs/architecture/fold-contract.md +87 -0
- package/docs/architecture/halo-sketch.md +99 -0
- package/docs/architecture/match-project.md +62 -0
- package/docs/architecture/mechanism-market.md +95 -0
- package/docs/architecture/memoization.md +96 -0
- package/docs/architecture/meter.md +55 -0
- package/docs/architecture/saturation.md +92 -0
- package/docs/architecture/store.md +79 -0
- package/docs/architecture/thresholds.md +79 -0
- package/docs/failures/tempting-but-wrong.md +144 -0
- package/docs/harness/gates.md +56 -0
- package/docs/mechanisms/alu.md +75 -0
- package/docs/mechanisms/cast.md +75 -0
- package/docs/mechanisms/confluence.md +36 -0
- package/docs/mechanisms/cover.md +54 -0
- package/docs/mechanisms/extraction.md +53 -0
- package/docs/mechanisms/prefix-completion.md +54 -0
- package/docs/mechanisms/recall.md +69 -0
- package/docs/mechanisms/reference.md +58 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/mind/mind.ts +14 -0
- package/src/store-sqlite.ts +19 -0
- package/src/store.ts +22 -0
- package/test/89-completion-recursion.test.mjs +30 -10
- package/test/97-store-seed.test.mjs +105 -0
- package/HOW_IT_WORKS.md +0 -5836
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Cover — Lightest Derivation over the Query
|
|
2
|
+
|
|
3
|
+
Cover is graph search. Its axioms are the query's own decomposition and its goal
|
|
4
|
+
is the cheapest cover of the query's bytes. It runs first so a computed-backed
|
|
5
|
+
cover becomes a near-zero-cost incumbent.
|
|
6
|
+
|
|
7
|
+
## Matcher — `locate` / recognition sites (`src/mind/recognition.ts`)
|
|
8
|
+
|
|
9
|
+
Sites are spans of the query that name a node already in the store. Cover
|
|
10
|
+
consumes them directly; any site whose bytes overlap a computed span is masked
|
|
11
|
+
(computation always wins).
|
|
12
|
+
|
|
13
|
+
## Projection — edges + conceptHop (`src/mind/match.ts`, `src/mind/graph-search.ts`)
|
|
14
|
+
|
|
15
|
+
- `formRules` follow continuation edges (`GraphSearch.formRules`): each hop
|
|
16
|
+
costs `STEP` (1). Forks across all continuations up to the hub bound;
|
|
17
|
+
disambiguation is distributional, not heuristic.
|
|
18
|
+
- Edge-less forms may hop via a halo sibling (`conceptHop` / `resolveConcepts`
|
|
19
|
+
in `src/mind/mechanisms/cover.ts`) at `CONCEPT` (10), borrowing a synonym's
|
|
20
|
+
continuation.
|
|
21
|
+
|
|
22
|
+
## Gate — `leadsSomewhere` (`src/mind/traverse.ts`)
|
|
23
|
+
|
|
24
|
+
A site participates only if it leads somewhere: it bears an edge (`hasNext`) or
|
|
25
|
+
a halo (`hasHalo`). Forms that lead nowhere contribute nothing to any derivation
|
|
26
|
+
and are filtered during recognition.
|
|
27
|
+
|
|
28
|
+
## Cost (`src/mind/graph-search.ts`)
|
|
29
|
+
|
|
30
|
+
| Symbol | Value | Rule |
|
|
31
|
+
| --------- | ----------- | ----------------------------------------------- |
|
|
32
|
+
| `STEP` | 1 | per edge hop |
|
|
33
|
+
| `CONCEPT` | 10 | abandoning a chain / synonym hop |
|
|
34
|
+
| `PASS` | 1000 / byte | each unaccounted byte |
|
|
35
|
+
| `MICRO` | 1e-3 | per-byte A* heuristic (`h = (len-right)*MICRO`) |
|
|
36
|
+
|
|
37
|
+
Mechanism weight is `moves + PASS * unaccounted_bytes`; comparison is at `STEP`
|
|
38
|
+
grade, then by `scaffolding` bytes, then list order.
|
|
39
|
+
|
|
40
|
+
## Pre-resolution (`src/mind/mechanisms/cover.ts`)
|
|
41
|
+
|
|
42
|
+
`resolveConcepts` and `resolveConnectors` pre-resolve the async maps the
|
|
43
|
+
synchronous search cannot gather: concept targets and learnt connectors, keyed
|
|
44
|
+
by node pair. Bridges (`bridge`) splice connectors between rewrites.
|
|
45
|
+
|
|
46
|
+
## Provenance
|
|
47
|
+
|
|
48
|
+
`cover` for the query's own cover; `join` when fusing fragments into a deeper
|
|
49
|
+
learned form.
|
|
50
|
+
|
|
51
|
+
## Pins
|
|
52
|
+
|
|
53
|
+
- `test/09-edges.test.mjs` — edge following and hop semantics
|
|
54
|
+
- `test/19-nd.test.mjs` — form rules and multi-hop chains
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Extraction — Skill-Framed Span Read-out
|
|
2
|
+
|
|
3
|
+
Extraction transfers a learnt span-in-context skill to an unseen query. A skill
|
|
4
|
+
exemplar is a stored fact whose answer is a span of its context (or of its
|
|
5
|
+
pieces); extraction locates the exemplar's framing bytes in the query and reads
|
|
6
|
+
what sits between them.
|
|
7
|
+
|
|
8
|
+
## Matcher — `skillExemplar` / `isSpanShaped` / `containsSpan` (`src/mind/match.ts`)
|
|
9
|
+
|
|
10
|
+
An exemplar is span-shaped when its answer is an in-order embedding of its
|
|
11
|
+
context. `isSpanShaped` is the open reading (sparse subsequence, any gaps) used
|
|
12
|
+
to accept candidates; `answerRunsInContext` is the strong reading (greedy
|
|
13
|
+
longest contiguous runs) used to decompose the answer for projection. Candidates
|
|
14
|
+
are ranked anchors from `climbAttentionAll` (`Precomputed.spanShapedOf`), tried
|
|
15
|
+
in order up to `pre.k`; sub-quantum (`< W = maxGroup`) or unanchored results are
|
|
16
|
+
skipped.
|
|
17
|
+
|
|
18
|
+
## Projection — read between located frames (`src/mind/mechanisms/extraction.ts`)
|
|
19
|
+
|
|
20
|
+
`answerRunsInContext` splits the exemplar answer into pieces within its context.
|
|
21
|
+
For each piece, the `W`-bounded pre-frame (and post-frame or next-piece
|
|
22
|
+
pre-frame) is `locate`d in the query at recognition sites. Located frames define
|
|
23
|
+
`start`/`end`; the bytes `query[start:end]` are read out. Multi-piece skills
|
|
24
|
+
concatenate reads in order.
|
|
25
|
+
|
|
26
|
+
## Gate — both borders located to account (`src/mind/mechanisms/extraction.ts`)
|
|
27
|
+
|
|
28
|
+
Frames are evidence only when `locate` succeeds. An unanchored read (no frame
|
|
29
|
+
located, `accounted === []`) is discarded — not an extraction. An open-ended
|
|
30
|
+
read (only one border located) stays unaccounted.
|
|
31
|
+
|
|
32
|
+
## Cost (`src/mind/graph-search.ts`)
|
|
33
|
+
|
|
34
|
+
Mechanism weight is `moves + PASS * unaccounted_bytes` with
|
|
35
|
+
`moves = CONCEPT + STEP * accounted.length`. Comparison is at `STEP` grade, then
|
|
36
|
+
scaffolding, then list order.
|
|
37
|
+
|
|
38
|
+
## Selective accounting
|
|
39
|
+
|
|
40
|
+
Frames are always accounted when located. The read span between them is
|
|
41
|
+
accounted only when bounded on both sides (`preBounded && postBounded`);
|
|
42
|
+
otherwise it is PASS-priced like uncovered bytes, so a correct bounded
|
|
43
|
+
extraction can outweigh an echoing juxtaposition.
|
|
44
|
+
|
|
45
|
+
## Provenance
|
|
46
|
+
|
|
47
|
+
`extract` (single-piece) or synthesised multi-piece read.
|
|
48
|
+
|
|
49
|
+
## Pins
|
|
50
|
+
|
|
51
|
+
- `test/00-extract.test.mjs` — skill transfer across values and relations
|
|
52
|
+
- `test/68-extraction-unanchored.test.mjs` — unanchored gate (empty accounted is
|
|
53
|
+
silence)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Prefix Completion — Literal Opening of One Trained Form
|
|
2
|
+
|
|
3
|
+
The query is a proper prefix of exactly one trained form. That form is voiced
|
|
4
|
+
whole; nothing is invented.
|
|
5
|
+
|
|
6
|
+
## Gate — exactly one opener
|
|
7
|
+
|
|
8
|
+
A candidate is a form whose bytes literally open with the query (every query
|
|
9
|
+
byte matched in order from offset zero). Grounding requires **exactly one
|
|
10
|
+
distinct continuation** over the candidate set:
|
|
11
|
+
|
|
12
|
+
- **Candidates** = `formsOpenedBy` (content-addressed window index) ∪ memoised
|
|
13
|
+
`resonance` top-k — evaluated as one union so an exact-index ambiguity cannot
|
|
14
|
+
be hidden by the approximate tier.
|
|
15
|
+
- **Distinctness** is by continuation bytes, not form id.
|
|
16
|
+
- **Zero or ≥2 distinct continuations → refuse** (the prefix trap).
|
|
17
|
+
|
|
18
|
+
## Cost (`src/mind/graph-search.ts`)
|
|
19
|
+
|
|
20
|
+
| Symbol | Value | Rule |
|
|
21
|
+
| ------ | ----- | ------------------------------------------------- |
|
|
22
|
+
| `STEP` | 1 | maximal claim: every query byte literally matched |
|
|
23
|
+
|
|
24
|
+
`floor` returns `STEP`; `run` returns one candidate with `moves = STEP`,
|
|
25
|
+
`accounted = [[0, query.length]]`, `bytes = form`.
|
|
26
|
+
|
|
27
|
+
## `complete` flag
|
|
28
|
+
|
|
29
|
+
The result carries `complete` semantics: the grounded bytes are a trained form
|
|
30
|
+
reached by identity. Post-grounding (`reason` → `fuse`) must not extend them.
|
|
31
|
+
|
|
32
|
+
## Guards (from `src/mind/mechanisms/prefix-completion.ts`)
|
|
33
|
+
|
|
34
|
+
1. **Unreadable veto** — a saturating `bytesPrefix` read is a standing
|
|
35
|
+
disagreement; if any candidate saturates, none is licensed.
|
|
36
|
+
2. **One grouping window** — continuation must be ≥ `W` (`maxGroup`);
|
|
37
|
+
sub-quantum tails are unvoiceable and also count as disagreement.
|
|
38
|
+
3. **Uniqueness** — as above.
|
|
39
|
+
|
|
40
|
+
Structural pre-check (`floor`): `query.length * W < query.length + W` → `null`
|
|
41
|
+
(no room for a perceivable continuation).
|
|
42
|
+
|
|
43
|
+
## Where it runs
|
|
44
|
+
|
|
45
|
+
Last grounding mechanism in `defaultMechanisms` (`mind/pipeline.ts`); registered
|
|
46
|
+
after recall so an exact self-match (`IDENTITY`) wins ties. Shares
|
|
47
|
+
`formsOpenedBy` (`mind/traverse.ts`) and `Precomputed.resonance()`.
|
|
48
|
+
|
|
49
|
+
## Pins
|
|
50
|
+
|
|
51
|
+
- `test/70-prefix-completion.test.mjs` — literal prefix, ambiguity, sub-quantum,
|
|
52
|
+
saturating-read veto, determinism.
|
|
53
|
+
- `test/72-prefix-candidate-supply.test.mjs` — `formsOpenedBy` ∪ `resonance`
|
|
54
|
+
union supply; resonance alone cannot rank a proper prefix.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Recall — Nearest Stored Form
|
|
2
|
+
|
|
3
|
+
Recall resonates the whole query's gist against the content index and grounds
|
|
4
|
+
the nearest learned form. Resonance proposes; bytes decide.
|
|
5
|
+
|
|
6
|
+
## Gist and budget
|
|
7
|
+
|
|
8
|
+
Query gist is `pre.guide`; the single top-k read is `pre.resonance()` shared
|
|
9
|
+
across the response. `Precomputed.k = 2·recallQueryK` tiers 0b/1/2/3 through it;
|
|
10
|
+
the last tier re-folds bytes.
|
|
11
|
+
|
|
12
|
+
## Tiers (degrading)
|
|
13
|
+
|
|
14
|
+
| Tier | Name | Gate | Action |
|
|
15
|
+
| ---- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| 0 | Exact identity | `pre.queryResolved` exists | Reverse-recall (`reverseContext`) to best-resonating predecessor at `STEP` |
|
|
17
|
+
| 0b | RC8 argument binding | One maximal `≥2W` edge-source constituent, no substantial `≥2W` form outside it | `follow` its continuation; refuses if result is a subspan of the query |
|
|
18
|
+
| 1 | Clean resonance | `score ≥ identityBar(D,W,len)` per hit | `project` hit; restating hits (bytes or `canon`-equivalent) only via reverse-recall |
|
|
19
|
+
| 2 | Scaffolding-dominated | `score ≥ significanceBar(D)` and consensus-climb anchor clears `consensusFloor(N)` or `dominates(breadth,1) && peak>ln2`, and query is not all-scaffolding | `project` anchor at `CONCEPT`; refuses if continuation voices the anchor's displaced filler or is a query subspan |
|
|
20
|
+
| 3 | Last resort + bridge | Query-relative fraction `max(0,cos−sig)·√(lenG/lenQ) ≥ reachThreshold(W)` | Best grounded `project` hit at `STEP` |
|
|
21
|
+
|
|
22
|
+
W = `maxGroup` (river window); bars from `src/geometry.ts`.
|
|
23
|
+
|
|
24
|
+
## Echo — the refusing tail
|
|
25
|
+
|
|
26
|
+
If no tier grounded, the exact cosine of the top hit is re-folded (`gistOf` on
|
|
27
|
+
its bytes). Decision uses that exact value in the same query-relative,
|
|
28
|
+
chance-corrected fraction — never the RaBitQ estimate. Below `reach` → silence;
|
|
29
|
+
restating → silence; otherwise the hit's own bytes are returned as an ungrounded
|
|
30
|
+
echo.
|
|
31
|
+
|
|
32
|
+
## Provenance
|
|
33
|
+
|
|
34
|
+
Grounded answers carry `recall`; the echo carries `recall-echo` (`echoed: true`
|
|
35
|
+
on `RecallResult`). Consumers distinguish a continuation through learned edges
|
|
36
|
+
from a near-identity echo.
|
|
37
|
+
|
|
38
|
+
## Substitution bridge — refusal-path only (`src/mind/bridge.ts`)
|
|
39
|
+
|
|
40
|
+
Runs only after every gist tier failed, reusing the same top-k proposals (the
|
|
41
|
+
bridge's cap is `2·recallQueryK`; every proposal is byte-verified). A candidate
|
|
42
|
+
context is byte-aligned around the rarest stored W-windows. A mismatch becomes a
|
|
43
|
+
corroborated substitution only when its query span is corpus-attested (every
|
|
44
|
+
W-window stored, one reused ≥2 containers), its geometry clears
|
|
45
|
+
`conceptThreshold(D)` or its halo clears `significanceBar(D)`, its frame is
|
|
46
|
+
unanimous, and the raw gap is length-balanced. Coverage must dominate the query
|
|
47
|
+
and no dismissed gap may hide known content (`dismissedKnownContent` gate). Cost
|
|
48
|
+
is `CONCEPT` per substitution plus `STEP`; accounted spans include matched and
|
|
49
|
+
substituted ranges (so a 28/29-byte paraphrase is not charged `PASS` per
|
|
50
|
+
substituted byte — observed double-charge that let `cast` outbid the bridge).
|
|
51
|
+
Zero-substitution identity bridges carry `complete: true` (the whole read-out);
|
|
52
|
+
substituted bridges do not.
|
|
53
|
+
|
|
54
|
+
Scaffolding-only queries abstain: when every stored window that could anchor is
|
|
55
|
+
saturated (corpus-global scaffolding, `allWindowsAreScaffolding`), the bridge
|
|
56
|
+
returns nothing — a single substituted word cannot carry the semantic load.
|
|
57
|
+
|
|
58
|
+
## Cost
|
|
59
|
+
|
|
60
|
+
Tiers 0/1/3 price `STEP` (one hop); tier 2 and the bridge price `CONCEPT` per
|
|
61
|
+
substitution/scaffold step. Mechanism `floor` is `STEP`; weight is
|
|
62
|
+
`moves + PASS·unaccounted`.
|
|
63
|
+
|
|
64
|
+
## Pins
|
|
65
|
+
|
|
66
|
+
- `test/03-recall.test.mjs` — exact identity and reverse-recall
|
|
67
|
+
- `test/16-bridge.test.mjs` — corroborated substitutions
|
|
68
|
+
- `test/73-scaffolding-only-bridge-abstains.test.mjs` — scaffolding-only queries
|
|
69
|
+
stay silent
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Reference — Voicing a Slot with the Asker's Bytes
|
|
2
|
+
|
|
3
|
+
Reference voices a slot of a learned frame with the bytes the asker supplied in
|
|
4
|
+
that position — asserting _position_, not equivalence. The bytes are the
|
|
5
|
+
asker's, so voicing them cannot fabricate corpus knowledge; the fabricable claim
|
|
6
|
+
is the _relation_ about them, which the licence withholds.
|
|
7
|
+
|
|
8
|
+
## Matcher — `frameSlots` inventory
|
|
9
|
+
|
|
10
|
+
The shared matcher is `frameSlots` (`src/mind/match.ts`) via
|
|
11
|
+
`Precomputed.frames()`.
|
|
12
|
+
|
|
13
|
+
- Seeded at origin `(0,0)`, `alignAround` finds common runs (seed `W`) then
|
|
14
|
+
sweeps both directions; each gap is contracted by `contractGap` to its varying
|
|
15
|
+
core (shared prefix/suffix stripped) and tagged
|
|
16
|
+
`substitution | insertion | deletion`.
|
|
17
|
+
- `frameSlots` **reports, never judges**: every gap (any kind, any size), sorted
|
|
18
|
+
by `qs`, plus `covered` (shared bytes) and `matched` spans. No gate is applied
|
|
19
|
+
there.
|
|
20
|
+
|
|
21
|
+
## Gate — reference elects and licences; matcher does not
|
|
22
|
+
|
|
23
|
+
All voicing gates belong to the **consumer**
|
|
24
|
+
(`src/mind/mechanisms/reference.ts`), not the matcher. A shared layer that
|
|
25
|
+
refused on their behalf would be reference-shaped and hide most real pairings.
|
|
26
|
+
|
|
27
|
+
- **Election:** `electFrame` groups inventory by full slot signature
|
|
28
|
+
(`qs:qe,...`) and keeps the modal group — one frame, not one slot.
|
|
29
|
+
- **Carriage licence:** `carriesFillers` —
|
|
30
|
+
`substituteAll(contA, fillersA→fillersB) == contB` byte-exact, all slots
|
|
31
|
+
simultaneously (longest needle first). Constant continuations pass vacuously;
|
|
32
|
+
filler-dependent content is refused.
|
|
33
|
+
- **Four voicing gates** (in `voiceable` + caller):
|
|
34
|
+
1. frame `dominates` query (`covered > |query|/2`);
|
|
35
|
+
2. every slot reaches one window `W` on _both_ sides;
|
|
36
|
+
3. no insertion/deletion (substitutions only);
|
|
37
|
+
4. fillers pairwise distinct. Additional: referents pairwise distinct and no
|
|
38
|
+
slot inside `answeredSpans`.
|
|
39
|
+
|
|
40
|
+
Matched frame + every slot is `accounted`; `complete: true`.
|
|
41
|
+
|
|
42
|
+
## Cost
|
|
43
|
+
|
|
44
|
+
`moves = STEP·slots + STEP` (one binding per slot + one edge follow). Not
|
|
45
|
+
`CONCEPT` — byte identity, not halo. `scaffolding` is never reported; a referent
|
|
46
|
+
is explained, not carried for lack of explanation.
|
|
47
|
+
|
|
48
|
+
`floor` is `STEP+STEP`, investment-disciplined before touching `frames()`.
|
|
49
|
+
|
|
50
|
+
## Provenance
|
|
51
|
+
|
|
52
|
+
`provenance: "reference"` with trace steps `bindReferent` / `referenceLicence`.
|
|
53
|
+
|
|
54
|
+
## Pins
|
|
55
|
+
|
|
56
|
+
- `test/76-reference-binding` — inventory vs gate split, carried/absorbed/
|
|
57
|
+
refused, multi-slot licence, `complete` and answered-span guard.
|
|
58
|
+
- `test/76-type-level-company` — type-level halo company underpinning.
|
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/mind/mind.ts
CHANGED
|
@@ -398,10 +398,24 @@ export class Mind implements MindContext {
|
|
|
398
398
|
} = (optsOrCfg ?? {}) as MindOptions;
|
|
399
399
|
this._canonOpt = optsCanon ?? null;
|
|
400
400
|
this._profile = optsProfile === true;
|
|
401
|
+
// `explicitSeed` is read BEFORE resolveConfig folds the default in, so
|
|
402
|
+
// the store can be consulted only when the caller did not choose.
|
|
403
|
+
const explicitSeed = (rest as Partial<MindConfig>).seed;
|
|
401
404
|
this.cfg = resolveConfig(rest as Partial<MindConfig>);
|
|
402
405
|
this.store = optsStore ?? new SQliteStore({
|
|
403
406
|
maxGroup: this.cfg.geometry.maxGroup,
|
|
404
407
|
});
|
|
408
|
+
// THE ARTIFACT'S SEED GOVERNS. `train.seed` is recovered by the store at
|
|
409
|
+
// open, exactly like `train.D` and `geometry.maxGroup`. The seed feeds
|
|
410
|
+
// `makeKeyring`, `Space.rand` and the `Alphabet` below, so folding a
|
|
411
|
+
// query under config.ts's default (42) against a store trained with
|
|
412
|
+
// another seed (e.g. 7) lands in a DIFFERENT vector space than the one
|
|
413
|
+
// the artifact's nodes were folded into: recognition and resonance then
|
|
414
|
+
// read the wrong space and every answer degrades silently. An explicit
|
|
415
|
+
// caller seed still wins — this only replaces the unconfigured default.
|
|
416
|
+
if (explicitSeed === undefined && this.store.trainSeed !== null) {
|
|
417
|
+
this.cfg.seed = this.store.trainSeed;
|
|
418
|
+
}
|
|
405
419
|
userMechanisms = userMechs ?? [];
|
|
406
420
|
userFactories = userFacts ?? [];
|
|
407
421
|
}
|
package/src/store-sqlite.ts
CHANGED
|
@@ -401,6 +401,25 @@ export class SQliteStore extends AbstractStore implements Store {
|
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
+
// Recover the TRAINING seed exactly as D and maxGroup are recovered. The
|
|
405
|
+
// seed seeds the alphabet and the seat keyring (mind.ts), so a Mind that
|
|
406
|
+
// folds a query under any other seed lands in a different vector space
|
|
407
|
+
// than the one this artifact's nodes were folded into — recognition,
|
|
408
|
+
// resonance and every mechanism downstream then read the wrong space. The
|
|
409
|
+
// trainer persists `train.seed` and refuses to resume against a store
|
|
410
|
+
// trained with a different one (example/train_base/main.ts), so the value
|
|
411
|
+
// is authoritative for this artifact. Absent on a store that was never
|
|
412
|
+
// trained, where the caller's configured seed stands.
|
|
413
|
+
{
|
|
414
|
+
const row = this.sqlite.prepare(
|
|
415
|
+
"SELECT val FROM meta WHERE key = 'train.seed'",
|
|
416
|
+
).get() as { val: string } | undefined;
|
|
417
|
+
if (row) {
|
|
418
|
+
const s = Number(row.val);
|
|
419
|
+
if (Number.isInteger(s) && s >= 0) this._trainSeed = s;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
404
423
|
// Persist maxGroup to meta when opening a FRESH store (no rows yet) so
|
|
405
424
|
// indexSubtree always sees the training-time value even when the store is
|
|
406
425
|
// accessed without a Mind / full snapshot.
|
package/src/store.ts
CHANGED
|
@@ -285,6 +285,17 @@ export class BoundedMap<K, V> {
|
|
|
285
285
|
export interface Store {
|
|
286
286
|
readonly D: number;
|
|
287
287
|
|
|
288
|
+
/** The seed the artifact was TRAINED with, recovered from the store's own
|
|
289
|
+
* `train.seed` metadata, or null for a store that was never trained.
|
|
290
|
+
*
|
|
291
|
+
* This is not decoration: the seed feeds the alphabet and the seat keyring
|
|
292
|
+
* (see the Mind constructor), so folding a query under any other seed lands
|
|
293
|
+
* in a different vector space than the one the artifact's nodes were folded
|
|
294
|
+
* into. A Mind opening a trained store MUST adopt this seed unless the
|
|
295
|
+
* caller explicitly overrides it — the same discipline that recovers
|
|
296
|
+
* `train.D` and `geometry.maxGroup` from the metadata. */
|
|
297
|
+
readonly trainSeed: number | null;
|
|
298
|
+
|
|
288
299
|
/** The work accumulator for the inference call in flight, or null. The
|
|
289
300
|
* Mind attaches one per profiled response and detaches it after (see
|
|
290
301
|
* src/meter.ts). A store MUST only ever write to it — no read may reach
|
|
@@ -917,6 +928,10 @@ export abstract class AbstractStore implements Store {
|
|
|
917
928
|
|
|
918
929
|
protected _D: number;
|
|
919
930
|
protected _maxGroup: number;
|
|
931
|
+
/** `train.seed` recovered by the backend at open, or null when the store was
|
|
932
|
+
* never trained. A backend that omits it simply reports null, which leaves
|
|
933
|
+
* the caller's configured seed in force. */
|
|
934
|
+
protected _trainSeed: number | null = null;
|
|
920
935
|
protected readonly minHaloMass: number;
|
|
921
936
|
protected readonly efSearch: number;
|
|
922
937
|
protected readonly overfetch: number;
|
|
@@ -1091,6 +1106,13 @@ export abstract class AbstractStore implements Store {
|
|
|
1091
1106
|
return this._D;
|
|
1092
1107
|
}
|
|
1093
1108
|
|
|
1109
|
+
/** The seed the artifact was trained with, recovered from `train.seed` at
|
|
1110
|
+
* open. Null for a store that was never trained. See
|
|
1111
|
+
* {@link Store.trainSeed} for why this must govern inference. */
|
|
1112
|
+
get trainSeed(): number | null {
|
|
1113
|
+
return this._trainSeed;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1094
1116
|
/** Await the async initialisation performed by the concrete constructor. */
|
|
1095
1117
|
protected async _ensureReady(): Promise<void> {
|
|
1096
1118
|
if (!this._ready) throw new Error("Store: not open");
|
|
@@ -77,15 +77,35 @@ const mix = (x) => {
|
|
|
77
77
|
return (x ^ (x >>> 15)) >>> 0;
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
/** Four-word windows of the repo's own
|
|
81
|
-
*
|
|
82
|
-
*
|
|
80
|
+
/** Four-word windows of the repo's own prose, taken from the CODE's comments
|
|
81
|
+
* under src (TypeScript) and test (the suites) — never from documentation. A
|
|
82
|
+
* test that reads docs is coupled to every documentation edit: deleting one
|
|
83
|
+
* root file once took its corpus below the non-vacuity guard and broke every
|
|
84
|
+
* release. Code comments are prose too, and the code is always here.
|
|
85
|
+
*
|
|
86
|
+
* Comment markers, code fences, inline code and link targets are stripped so
|
|
87
|
+
* what is left is language, which is where the fragment overlap lives. */
|
|
83
88
|
function fragments() {
|
|
84
89
|
const out = [];
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
const files = [];
|
|
91
|
+
const walk = (dir, exts) => {
|
|
92
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
93
|
+
if (
|
|
94
|
+
e.name.startsWith(".") || e.name === "node_modules" || e.name === "dist"
|
|
95
|
+
) continue;
|
|
96
|
+
const p = join(dir, e.name);
|
|
97
|
+
if (e.isDirectory()) walk(p, exts);
|
|
98
|
+
else if (exts.some((x) => e.name.endsWith(x))) files.push(p);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
walk(join(REPO, "src"), [".ts"]);
|
|
102
|
+
walk(join(REPO, "test"), [".mjs"]);
|
|
103
|
+
for (const f of files.sort()) {
|
|
104
|
+
const text = readFileSync(f, "utf8");
|
|
105
|
+
let t = "";
|
|
106
|
+
for (const m of text.matchAll(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g)) {
|
|
107
|
+
t += " " + m[0];
|
|
108
|
+
}
|
|
89
109
|
t = t.toLowerCase().replace(/[^a-z ]+/g, " ").replace(/\s+/g, " ");
|
|
90
110
|
const w = t.split(" ").filter(Boolean);
|
|
91
111
|
for (let i = 0; i + 4 < w.length; i += 2) {
|
|
@@ -140,9 +160,9 @@ const SIZES = [750, 1000, 1500];
|
|
|
140
160
|
test("completion recursion: per-query work does not grow with the corpus", async () => {
|
|
141
161
|
assert.ok(
|
|
142
162
|
FRAG.length > 4000,
|
|
143
|
-
`only ${FRAG.length} prose fragments found in
|
|
144
|
-
`draws its corpus from
|
|
145
|
-
`it can no longer exercise the completion recursion at all`,
|
|
163
|
+
`only ${FRAG.length} prose fragments found in the source comments — this ` +
|
|
164
|
+
`test draws its corpus from src/**/*.ts and test/**/*.mjs; with the ` +
|
|
165
|
+
`comments gone it can no longer exercise the completion recursion at all`,
|
|
146
166
|
);
|
|
147
167
|
|
|
148
168
|
const searches = [], pops = [], answers = [], secs = [];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// 97-store-seed.test.mjs — a trained store's OWN seed governs the Mind that
|
|
2
|
+
// opens it.
|
|
3
|
+
//
|
|
4
|
+
// `train.seed` is persisted by the trainer (example/train_base/main.ts) and the
|
|
5
|
+
// trainer refuses to resume a store under a different seed, so the value is
|
|
6
|
+
// authoritative for the artifact. The seed feeds `makeKeyring`, `Space.rand`
|
|
7
|
+
// and the `Alphabet` in the Mind constructor: folding a query under any other
|
|
8
|
+
// seed lands in a DIFFERENT vector space than the one the artifact's nodes were
|
|
9
|
+
// folded into, so recognition and resonance read the wrong space and every
|
|
10
|
+
// answer degrades silently.
|
|
11
|
+
//
|
|
12
|
+
// The store recovers `train.D` and `geometry.maxGroup` from its own metadata at
|
|
13
|
+
// open; `train.seed` must be recovered the same way, and a Mind that did not
|
|
14
|
+
// receive an explicit seed must adopt it. An explicit caller seed still wins.
|
|
15
|
+
|
|
16
|
+
import { test } from "node:test";
|
|
17
|
+
import assert from "node:assert/strict";
|
|
18
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { DEFAULT_CONFIG, Mind } from "../dist/src/index.js";
|
|
22
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
23
|
+
|
|
24
|
+
/** A store that was never trained carries no seed and leaves the caller's
|
|
25
|
+
* configured default in force. */
|
|
26
|
+
test("an untrained store reports no trainSeed and keeps the default seed", async () => {
|
|
27
|
+
const store = new SQliteStore({ path: ":memory:", D: 256 });
|
|
28
|
+
const mind = new Mind({ store });
|
|
29
|
+
assert.equal(store.trainSeed, null);
|
|
30
|
+
assert.equal(mind.cfg.seed, DEFAULT_CONFIG.seed);
|
|
31
|
+
await store.close();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** The artifact's seed is recovered at open and adopted by a Mind that was not
|
|
35
|
+
* given one; an explicit seed still overrides it. */
|
|
36
|
+
test("a trained store's seed is recovered and adopted unless overridden", async () => {
|
|
37
|
+
const dir = mkdtempSync(join(tmpdir(), "sema-seed-"));
|
|
38
|
+
const stem = join(dir, "trained");
|
|
39
|
+
const TRAIN_SEED = 7;
|
|
40
|
+
|
|
41
|
+
// Build the artifact: ingest under an explicit seed, then persist the seed
|
|
42
|
+
// exactly as the trainer does.
|
|
43
|
+
{
|
|
44
|
+
const store = new SQliteStore({ path: stem, D: 256 });
|
|
45
|
+
const mind = new Mind({ seed: TRAIN_SEED, store });
|
|
46
|
+
await mind.ingest([["the sky is blue", "blue"]]);
|
|
47
|
+
await store.setMeta("train.seed", String(TRAIN_SEED));
|
|
48
|
+
store.commit();
|
|
49
|
+
await store.close();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Reopen WITHOUT a seed: the store's own seed must stand.
|
|
53
|
+
{
|
|
54
|
+
const store = new SQliteStore({ path: stem, D: 256 });
|
|
55
|
+
assert.equal(store.trainSeed, TRAIN_SEED);
|
|
56
|
+
const adopted = new Mind({ store });
|
|
57
|
+
assert.equal(adopted.cfg.seed, TRAIN_SEED);
|
|
58
|
+
await store.close();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Reopen WITH an explicit seed: the caller wins over the artifact.
|
|
62
|
+
{
|
|
63
|
+
const store = new SQliteStore({ path: stem, D: 256 });
|
|
64
|
+
const explicit = new Mind({ seed: 3, store });
|
|
65
|
+
assert.equal(explicit.cfg.seed, 3);
|
|
66
|
+
await store.close();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
rmSync(dir, { recursive: true, force: true });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/** The adopted seed is the one the answer is computed under: a store ingested
|
|
73
|
+
* under seed 7 answers a query identically when reopened without a seed and
|
|
74
|
+
* when reopened with seed 7 passed explicitly. */
|
|
75
|
+
test("adopting the artifact seed reproduces the artifact's answer", async () => {
|
|
76
|
+
const dir = mkdtempSync(join(tmpdir(), "sema-seed-"));
|
|
77
|
+
const stem = join(dir, "trained");
|
|
78
|
+
const TRAIN_SEED = 7;
|
|
79
|
+
const QUESTION = "the sky is blue";
|
|
80
|
+
|
|
81
|
+
let artifactAnswer;
|
|
82
|
+
{
|
|
83
|
+
const store = new SQliteStore({ path: stem, D: 256 });
|
|
84
|
+
const mind = new Mind({ seed: TRAIN_SEED, store });
|
|
85
|
+
await mind.ingest([
|
|
86
|
+
["the sky is blue", "blue"],
|
|
87
|
+
["the grass is green", "green"],
|
|
88
|
+
]);
|
|
89
|
+
artifactAnswer = (await mind.respondText(QUESTION)).trim();
|
|
90
|
+
await store.setMeta("train.seed", String(TRAIN_SEED));
|
|
91
|
+
store.commit();
|
|
92
|
+
await store.close();
|
|
93
|
+
}
|
|
94
|
+
assert.equal(artifactAnswer, "blue");
|
|
95
|
+
|
|
96
|
+
{
|
|
97
|
+
const store = new SQliteStore({ path: stem, D: 256 });
|
|
98
|
+
const adopted = new Mind({ store });
|
|
99
|
+
assert.equal(adopted.cfg.seed, TRAIN_SEED);
|
|
100
|
+
assert.equal((await adopted.respondText(QUESTION)).trim(), artifactAnswer);
|
|
101
|
+
await store.close();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
rmSync(dir, { recursive: true, force: true });
|
|
105
|
+
});
|