@hviana/sema 0.1.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.
- package/AGENTS.md +470 -0
- package/AUTHORS.md +12 -0
- package/COMMERCIAL-LICENSE.md +19 -0
- package/CONTRIBUTING.md +15 -0
- package/HOW_IT_WORKS.md +4210 -0
- package/LICENSE.md +117 -0
- package/README.md +290 -0
- package/TRADEMARKS.md +19 -0
- package/example/demo.ts +46 -0
- package/example/train_base.ts +2675 -0
- package/index.html +893 -0
- package/package.json +25 -0
- package/src/alphabet.ts +34 -0
- package/src/alu/README.md +332 -0
- package/src/alu/src/alu.ts +541 -0
- package/src/alu/src/expr.ts +339 -0
- package/src/alu/src/index.ts +115 -0
- package/src/alu/src/kernel-arith.ts +377 -0
- package/src/alu/src/kernel-bits.ts +199 -0
- package/src/alu/src/kernel-logic.ts +102 -0
- package/src/alu/src/kernel-nd.ts +235 -0
- package/src/alu/src/kernel-numeric.ts +466 -0
- package/src/alu/src/operation.ts +344 -0
- package/src/alu/src/parser.ts +574 -0
- package/src/alu/src/resonance.ts +161 -0
- package/src/alu/src/text.ts +83 -0
- package/src/alu/src/value.ts +327 -0
- package/src/alu/test/alu.test.ts +1004 -0
- package/src/bytes.ts +62 -0
- package/src/config.ts +227 -0
- package/src/derive/README.md +295 -0
- package/src/derive/src/deduction.ts +263 -0
- package/src/derive/src/index.ts +24 -0
- package/src/derive/src/priority-queue.ts +70 -0
- package/src/derive/src/rewrite.ts +127 -0
- package/src/derive/src/trie.ts +242 -0
- package/src/derive/test/derive.test.ts +137 -0
- package/src/extension.ts +42 -0
- package/src/geometry.ts +511 -0
- package/src/index.ts +38 -0
- package/src/ingest-cache.ts +224 -0
- package/src/mind/articulation.ts +137 -0
- package/src/mind/attention.ts +1061 -0
- package/src/mind/canonical.ts +107 -0
- package/src/mind/graph-search.ts +1100 -0
- package/src/mind/index.ts +18 -0
- package/src/mind/junction.ts +347 -0
- package/src/mind/learning.ts +246 -0
- package/src/mind/match.ts +507 -0
- package/src/mind/mechanisms/alu.ts +33 -0
- package/src/mind/mechanisms/cast.ts +568 -0
- package/src/mind/mechanisms/confluence.ts +268 -0
- package/src/mind/mechanisms/cover.ts +248 -0
- package/src/mind/mechanisms/extraction.ts +422 -0
- package/src/mind/mechanisms/recall.ts +241 -0
- package/src/mind/mind.ts +483 -0
- package/src/mind/pipeline-mechanism.ts +326 -0
- package/src/mind/pipeline.ts +300 -0
- package/src/mind/primitives.ts +201 -0
- package/src/mind/rationale.ts +275 -0
- package/src/mind/reasoning.ts +198 -0
- package/src/mind/recognition.ts +234 -0
- package/src/mind/resonance.ts +0 -0
- package/src/mind/trace.ts +108 -0
- package/src/mind/traverse.ts +556 -0
- package/src/mind/types.ts +281 -0
- package/src/rabitq-hnsw/README.md +303 -0
- package/src/rabitq-hnsw/src/database.ts +492 -0
- package/src/rabitq-hnsw/src/heap.ts +90 -0
- package/src/rabitq-hnsw/src/hnsw.ts +514 -0
- package/src/rabitq-hnsw/src/index.ts +15 -0
- package/src/rabitq-hnsw/src/prng.ts +39 -0
- package/src/rabitq-hnsw/src/rabitq.ts +308 -0
- package/src/rabitq-hnsw/src/store.ts +994 -0
- package/src/rabitq-hnsw/test/hnsw.test.ts +1213 -0
- package/src/store-sqlite.ts +928 -0
- package/src/store.ts +2148 -0
- package/src/vec.ts +124 -0
- package/test/00-extract.test.mjs +151 -0
- package/test/01-floor.test.mjs +20 -0
- package/test/02-roundtrip.test.mjs +83 -0
- package/test/03-recall.test.mjs +98 -0
- package/test/04-think.test.mjs +627 -0
- package/test/05-concepts.test.mjs +84 -0
- package/test/08-storage.test.mjs +948 -0
- package/test/09-edges.test.mjs +65 -0
- package/test/11-universality.test.mjs +180 -0
- package/test/13-conversation.test.mjs +228 -0
- package/test/14-scaling.test.mjs +503 -0
- package/test/15-decomposition-gap.test.mjs +0 -0
- package/test/16-bridge.test.mjs +250 -0
- package/test/17-intelligence.test.mjs +240 -0
- package/test/18-alu.test.mjs +209 -0
- package/test/19-nd.test.mjs +254 -0
- package/test/20-stability.test.mjs +489 -0
- package/test/21-partial.test.mjs +158 -0
- package/test/22-multihop.test.mjs +130 -0
- package/test/23-rationale.test.mjs +316 -0
- package/test/24-generalization.test.mjs +416 -0
- package/test/25-embedding.test.mjs +75 -0
- package/test/26-cooperative.test.mjs +89 -0
- package/test/27-saturation-drop.test.mjs +637 -0
- package/test/28-unknowable.test.mjs +88 -0
- package/test/29-counterfactual.test.mjs +476 -0
- package/test/30-conflict-resolution.test.mjs +166 -0
- package/test/31-audit.test.mjs +288 -0
- package/test/32-confluence.test.mjs +173 -0
- package/test/33-multi-candidate.test.mjs +222 -0
- package/test/34-cross-region.test.mjs +252 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// 30-conflict-resolution.test.mjs — distributional-evidence disambiguation
|
|
2
|
+
// for conflicting continuations (chooseNext).
|
|
3
|
+
//
|
|
4
|
+
// When a context node has MULTIPLE learnt continuations (e.g. the same question
|
|
5
|
+
// was answered differently across training samples), chooseNext resolves the
|
|
6
|
+
// conflict by comparing each candidate's distributional evidence: the number of
|
|
7
|
+
// distinct contexts that predict it (prevOf count). This is the structural
|
|
8
|
+
// manifestation of the candidate's halo — each prevOf entry corresponds to a
|
|
9
|
+
// context whose signature was poured into the candidate's halo via pourHalo
|
|
10
|
+
// during ingestPair.
|
|
11
|
+
//
|
|
12
|
+
// A candidate predicted by more distinct contexts carries stronger support and
|
|
13
|
+
// wins regardless of insertion order. When evidence is equal, first-inserted
|
|
14
|
+
// wins (backward compatible — insertion order is the correct default for equal
|
|
15
|
+
// support). The gist-cosine comparison is NOT consulted here because for short
|
|
16
|
+
// answer candidates it is dominated by accidental byte-pattern correlations.
|
|
17
|
+
//
|
|
18
|
+
// These assertions pin the behaviour so it cannot silently regress:
|
|
19
|
+
// • a single continuation still works (the common case — no overhead);
|
|
20
|
+
// • equal evidence keeps first-inserted (backward compatible);
|
|
21
|
+
// • MORE distributional evidence overrides insertion order;
|
|
22
|
+
// • the tiebreak is deterministic and seed-stable.
|
|
23
|
+
|
|
24
|
+
import { test } from "node:test";
|
|
25
|
+
import assert from "node:assert/strict";
|
|
26
|
+
import { Mind } from "../dist/src/index.js";
|
|
27
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
28
|
+
|
|
29
|
+
// ── §1 Single continuation (the common case — must not regress) ─────────────
|
|
30
|
+
|
|
31
|
+
test("single continuation: unchanged", async () => {
|
|
32
|
+
const m = new Mind({ seed: 7 });
|
|
33
|
+
await m.ingest([["fire", "hot"]]);
|
|
34
|
+
assert.equal(await m.respondText("fire"), "hot");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("single continuation with query wrapper", async () => {
|
|
38
|
+
const m = new Mind({ seed: 7 });
|
|
39
|
+
await m.ingest([["ice", "cold"]]);
|
|
40
|
+
assert.equal(await m.respondText("the nature of ice is known"), "cold");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ── §2 Equal evidence — first-inserted wins (backward compatible) ──────────
|
|
44
|
+
|
|
45
|
+
test("equal evidence: first-inserted wins", async () => {
|
|
46
|
+
const m = new Mind({ seed: 7 });
|
|
47
|
+
// Both continuations have exactly ONE prev context each — equal support.
|
|
48
|
+
await m.ingest([["x", "first"]]);
|
|
49
|
+
await m.ingest([["x", "second"]]);
|
|
50
|
+
assert.equal(await m.respondText("x"), "first");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("equal evidence, reversed insertion order", async () => {
|
|
54
|
+
const m = new Mind({ seed: 7 });
|
|
55
|
+
await m.ingest([["x", "second"]]);
|
|
56
|
+
await m.ingest([["x", "first"]]);
|
|
57
|
+
assert.equal(await m.respondText("x"), "second");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ── §3 More distributional evidence overrides insertion order ───────────────
|
|
61
|
+
|
|
62
|
+
test("more corroborated answer wins despite being trained second", async () => {
|
|
63
|
+
const m = new Mind({ seed: 7 });
|
|
64
|
+
// Wrong answer, trained first — 1 context.
|
|
65
|
+
await m.ingest([["a capital do Brasil é", "Rio de Janeiro"]]);
|
|
66
|
+
// Correct answer, trained second — but 4 DISTINCT corroborating contexts.
|
|
67
|
+
await m.ingest([["a capital do Brasil é", "Brasília"]]);
|
|
68
|
+
await m.ingest([["qual a capital do Brasil?", "Brasília"]]);
|
|
69
|
+
await m.ingest([["a capital brasileira é", "Brasília"]]);
|
|
70
|
+
await m.ingest([["Brasil tem como capital", "Brasília"]]);
|
|
71
|
+
|
|
72
|
+
assert.equal(await m.respondText("a capital do Brasil é"), "Brasília");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("more corroborated answer wins with query wrapper", async () => {
|
|
76
|
+
const m = new Mind({ seed: 7 });
|
|
77
|
+
// The query must literally contain a trained context form so recognition
|
|
78
|
+
// can find it — Sema's recognition is content-addressed, not semantic.
|
|
79
|
+
await m.ingest([["speed of light is", "300 000 km/s"]]);
|
|
80
|
+
await m.ingest([["speed of light is", "299 792 458 m/s"]]);
|
|
81
|
+
await m.ingest([["light travels at", "299 792 458 m/s"]]);
|
|
82
|
+
await m.ingest([["c equals", "299 792 458 m/s"]]);
|
|
83
|
+
|
|
84
|
+
// "speed of light is" is a literal substring of this query.
|
|
85
|
+
const r = await m.respondText("the speed of light is?");
|
|
86
|
+
assert.ok(
|
|
87
|
+
r.includes("299 792 458 m/s"),
|
|
88
|
+
`expected answer to contain '299 792 458 m/s', got '${r}'`,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("corroboration from diverse formulations beats single-source wrong answer", async () => {
|
|
93
|
+
const m = new Mind({ seed: 7 });
|
|
94
|
+
// One source claims a wrong chemical symbol.
|
|
95
|
+
await m.ingest([["water is", "Wo"]]);
|
|
96
|
+
// Multiple diverse sources give the correct one.
|
|
97
|
+
await m.ingest([["water is", "H2O"]]);
|
|
98
|
+
await m.ingest([["the chemical formula for water is", "H2O"]]);
|
|
99
|
+
await m.ingest([["water's molecular composition is", "H2O"]]);
|
|
100
|
+
|
|
101
|
+
assert.equal(await m.respondText("water is"), "H2O");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ── §4 Three-way conflict — most corroborated wins ─────────────────────────
|
|
105
|
+
|
|
106
|
+
test("three conflicting continuations: most evidenced wins", async () => {
|
|
107
|
+
const m = new Mind({ seed: 7 });
|
|
108
|
+
await m.ingest([["answer is", "alpha"]]); // 1 context
|
|
109
|
+
await m.ingest([["answer is", "beta"]]); // 1 context
|
|
110
|
+
await m.ingest([["answer is", "gamma"]]); // 1 context
|
|
111
|
+
await m.ingest([["the answer is", "beta"]]); // +1 for beta
|
|
112
|
+
await m.ingest([["correct answer is", "beta"]]); // +1 for beta
|
|
113
|
+
await m.ingest([["desired answer is", "beta"]]); // +1 for beta
|
|
114
|
+
|
|
115
|
+
// Beta: 4 prev contexts. Alpha: 1. Gamma: 1. Beta wins.
|
|
116
|
+
assert.equal(await m.respondText("answer is"), "beta");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ── §5 Single shared context, diverging answers ────────────────────────────
|
|
120
|
+
|
|
121
|
+
test("shared prefix with diverging continuations resolved by evidence", async () => {
|
|
122
|
+
const m = new Mind({ seed: 7 });
|
|
123
|
+
|
|
124
|
+
// "Paris is the capital of" has two continuations. The wrong one (Germany)
|
|
125
|
+
// slips in first with a single source. The correct one (France) is
|
|
126
|
+
// corroborated by multiple formulations that ALL predict "France".
|
|
127
|
+
await m.ingest([["Paris is the capital of", "Germany"]]); // wrong, 1 source
|
|
128
|
+
await m.ingest([["Paris is the capital of", "France"]]); // correct
|
|
129
|
+
await m.ingest([["the capital of this country is", "France"]]); // corroboration
|
|
130
|
+
await m.ingest([["which country has Paris? It is", "France"]]); // corroboration
|
|
131
|
+
|
|
132
|
+
assert.equal(await m.respondText("Paris is the capital of"), "France");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ── §6 Seed stability ──────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
test("same result with different seeds", async () => {
|
|
138
|
+
for (const seed of [7, 42, 123]) {
|
|
139
|
+
const m = new Mind({ seed });
|
|
140
|
+
await m.ingest([["color is", "red"]]);
|
|
141
|
+
await m.ingest([["color is", "blue"]]);
|
|
142
|
+
await m.ingest([["favorite color is", "blue"]]);
|
|
143
|
+
await m.ingest([["preferred color is", "blue"]]);
|
|
144
|
+
|
|
145
|
+
assert.equal(await m.respondText("color is"), "blue");
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ── §7 Multi-hop chain with a conflicting intermediate node ────────────────
|
|
150
|
+
|
|
151
|
+
test("multi-hop chain with resolved conflict at intermediate node", async () => {
|
|
152
|
+
const m = new Mind({ seed: 7 });
|
|
153
|
+
|
|
154
|
+
// Wrong chain: A → wrong_intermediate
|
|
155
|
+
await m.ingest([["start", "wrong middle"]]);
|
|
156
|
+
// Correct chain: A → correct_intermediate → final_answer
|
|
157
|
+
await m.ingest([["start", "correct middle"]]);
|
|
158
|
+
await m.ingest([["correct middle", "final answer"]]);
|
|
159
|
+
// Additional corroboration for the correct intermediate
|
|
160
|
+
await m.ingest([["begin here", "correct middle"]]);
|
|
161
|
+
await m.ingest([["the starting point is", "correct middle"]]);
|
|
162
|
+
|
|
163
|
+
// Should follow the more corroborated intermediate to the final answer.
|
|
164
|
+
const r = await m.respondText("start");
|
|
165
|
+
assert.equal(r, "final answer");
|
|
166
|
+
});
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// 31-audit.test.mjs — flip points pinned by the inference-path audit.
|
|
2
|
+
//
|
|
3
|
+
// Each section freezes a decision boundary the audit found unguarded:
|
|
4
|
+
// A. Response provenance (honesty read-out, incl. the recall-echo marker)
|
|
5
|
+
// B. express() confidence floor (no fabrication from unrelated vectors)
|
|
6
|
+
// C. Perfect-self-match reverse recall with MULTIPLE predecessors
|
|
7
|
+
// D. The reason() echo gate (a query that is itself a learnt continuation)
|
|
8
|
+
// E. consensusFloor — one derived formula, shared by its two consumers
|
|
9
|
+
//
|
|
10
|
+
// The assertions describe BEHAVIOUR only.
|
|
11
|
+
|
|
12
|
+
import { test } from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { consensusFloor, Mind } from "../dist/src/index.js";
|
|
15
|
+
|
|
16
|
+
const newMind = () => new Mind({ seed: 7 });
|
|
17
|
+
const text = (r) => new TextDecoder().decode(r.bytes.filter((b) => b !== 0));
|
|
18
|
+
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
20
|
+
// Section A — provenance
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
23
|
+
test("A1: a grounded fact carries a non-echo provenance", async () => {
|
|
24
|
+
const m = newMind();
|
|
25
|
+
await m.ingest([
|
|
26
|
+
["what is ice?", "ice is frozen water"],
|
|
27
|
+
["what is fire?", "fire is hot plasma"],
|
|
28
|
+
]);
|
|
29
|
+
const r = await m.respond("what is ice?");
|
|
30
|
+
assert.equal(text(r), "ice is frozen water");
|
|
31
|
+
assert.ok(
|
|
32
|
+
["cast", "join", "cover", "extract", "recall"].includes(r.provenance),
|
|
33
|
+
`grounded answer must not be an echo (got ${r.provenance})`,
|
|
34
|
+
);
|
|
35
|
+
await m.store.close();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("A2: silence carries no provenance", async () => {
|
|
39
|
+
const m = newMind();
|
|
40
|
+
await m.ingest([["what is a cat?", "a cat is a small feline"]]);
|
|
41
|
+
const r = await m.respond("explain quantum chromodynamics");
|
|
42
|
+
assert.equal(r.v, null);
|
|
43
|
+
assert.equal(r.provenance, undefined);
|
|
44
|
+
await m.store.close();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("A3: every answer's provenance is from the closed set", async () => {
|
|
48
|
+
const m = newMind();
|
|
49
|
+
await m.ingest([
|
|
50
|
+
["ice", "cold"],
|
|
51
|
+
["fire", "hot"],
|
|
52
|
+
["what is ice?", "ice is frozen water"],
|
|
53
|
+
]);
|
|
54
|
+
const allowed = new Set([
|
|
55
|
+
"cast",
|
|
56
|
+
"join",
|
|
57
|
+
"cover",
|
|
58
|
+
"extract",
|
|
59
|
+
"recall",
|
|
60
|
+
"recall-echo",
|
|
61
|
+
]);
|
|
62
|
+
for (const q of ["ice", "what is ice?", "ice fire", "icy things"]) {
|
|
63
|
+
const r = await m.respond(q);
|
|
64
|
+
if (r.v !== null) {
|
|
65
|
+
assert.ok(allowed.has(r.provenance), `${q} → ${r.provenance}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
await m.store.close();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
72
|
+
// Section B — express() confidence floor
|
|
73
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
74
|
+
|
|
75
|
+
test("B1: express of a related embedding returns bytes; of an unrelated vector, silence", async () => {
|
|
76
|
+
const m = newMind();
|
|
77
|
+
await m.ingest([
|
|
78
|
+
["what is ice?", "ice is frozen water"],
|
|
79
|
+
["what is fire?", "fire is hot plasma"],
|
|
80
|
+
]);
|
|
81
|
+
const emb = await m.embedding("what is ice?");
|
|
82
|
+
assert.notEqual(emb, null);
|
|
83
|
+
const related = await m.express(emb);
|
|
84
|
+
assert.ok(related.length > 0, "a related vector must still express");
|
|
85
|
+
|
|
86
|
+
// A pseudo-random direction is (whp) far below the reach threshold of
|
|
87
|
+
// every stored gist — expressing it would fabricate an answer.
|
|
88
|
+
const rand = new Float32Array(emb.length);
|
|
89
|
+
for (let i = 0; i < rand.length; i++) {
|
|
90
|
+
rand[i] = (Math.sin(i * 12.9898) * 43758.5453) % 1;
|
|
91
|
+
}
|
|
92
|
+
const unrelated = await m.express(rand);
|
|
93
|
+
assert.equal(unrelated.length, 0, "an unrelated vector must express nothing");
|
|
94
|
+
await m.store.close();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
98
|
+
// Section C — perfect self-match with multiple predecessors
|
|
99
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
100
|
+
|
|
101
|
+
test("C1: a shared answer asked verbatim reverse-recalls ONE of its contexts, not itself", async () => {
|
|
102
|
+
const m = newMind();
|
|
103
|
+
await m.ingest([
|
|
104
|
+
["what makes ice special?", "it stays frozen"],
|
|
105
|
+
["what makes glass special?", "it stays frozen"],
|
|
106
|
+
["what is fire?", "fire is hot plasma"],
|
|
107
|
+
]);
|
|
108
|
+
const out = await m.respondText("it stays frozen");
|
|
109
|
+
assert.ok(
|
|
110
|
+
out === "what makes ice special?" || out === "what makes glass special?",
|
|
111
|
+
`expected a predecessor context, got ${JSON.stringify(out)}`,
|
|
112
|
+
);
|
|
113
|
+
await m.store.close();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
117
|
+
// Section D — the reason() echo gate
|
|
118
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
119
|
+
|
|
120
|
+
test("D1: a query that is itself a learnt continuation still answers deterministically", async () => {
|
|
121
|
+
// "beta bridge" is the continuation of "alpha follows"; asking it back must
|
|
122
|
+
// not echo the conversation — it grounds to its own chain fixpoint, the
|
|
123
|
+
// same answer the chain head reaches.
|
|
124
|
+
const m = newMind();
|
|
125
|
+
await m.ingest([
|
|
126
|
+
["alpha follows", "beta bridge"],
|
|
127
|
+
["beta bridge", "gamma end"],
|
|
128
|
+
["gamma end", "delta done"],
|
|
129
|
+
]);
|
|
130
|
+
assert.equal(await m.respondText("beta bridge"), "delta done");
|
|
131
|
+
assert.equal(await m.respondText("alpha follows"), "delta done");
|
|
132
|
+
await m.store.close();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
136
|
+
// Section E — consensusFloor: one derived formula
|
|
137
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
138
|
+
|
|
139
|
+
test("E1: consensusFloor is ln(N) + 1/2", () => {
|
|
140
|
+
for (const N of [2, 10, 1000, 1e6]) {
|
|
141
|
+
assert.equal(consensusFloor(N), Math.log(N) + 1 / 2);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
146
|
+
// Section G — pass-2 flip points
|
|
147
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
148
|
+
|
|
149
|
+
test("G1: answerRunsInContext decomposes a sparse subsequence and rejects a non-subsequence", async () => {
|
|
150
|
+
const { answerRunsInContext } = await import(
|
|
151
|
+
"../dist/src/mind/mechanisms/extraction.js"
|
|
152
|
+
);
|
|
153
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
154
|
+
const runs = answerRunsInContext(
|
|
155
|
+
null,
|
|
156
|
+
enc("the red fox jumps high"),
|
|
157
|
+
enc("red high"),
|
|
158
|
+
);
|
|
159
|
+
assert.deepEqual(runs, [
|
|
160
|
+
{ start: 4, end: 8, ansLen: 4 },
|
|
161
|
+
{ start: 18, end: 22, ansLen: 4 },
|
|
162
|
+
]);
|
|
163
|
+
assert.equal(answerRunsInContext(null, enc("abc"), enc("xyz")), null);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("G2: detectSaturated — a saturated PARENT region cannot swallow an unsaturated chunk", async () => {
|
|
167
|
+
const { detectSaturated } = await import("../dist/src/mind/attention.js");
|
|
168
|
+
const ctx = { space: { maxGroup: 4 } };
|
|
169
|
+
// collectRegions emits post-order: the parent (0-8) arrives AFTER its two
|
|
170
|
+
// chunks and shares chunk 1's start. With chunk 2 (4-8) unsaturated, the
|
|
171
|
+
// saturated parent + saturated chunk 3 must NOT fuse into one interval
|
|
172
|
+
// spanning 0-12 over the unsaturated middle.
|
|
173
|
+
const regions = [
|
|
174
|
+
{ start: 0, end: 4, chunk: true },
|
|
175
|
+
{ start: 4, end: 8, chunk: true },
|
|
176
|
+
{ start: 0, end: 8, chunk: false }, // post-order parent
|
|
177
|
+
{ start: 8, end: 12, chunk: true },
|
|
178
|
+
];
|
|
179
|
+
const sat = detectSaturated(ctx, regions, [true, false, true, true]);
|
|
180
|
+
assert.deepEqual(sat.intervals, [
|
|
181
|
+
{ start: 0, end: 4 },
|
|
182
|
+
{ start: 8, end: 12 },
|
|
183
|
+
]);
|
|
184
|
+
assert.equal(sat.leadingEnd, 4);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("G3: poured halo mass breaks equal-diversity ties (repetition is evidence)", async () => {
|
|
188
|
+
// Both continuations have ONE distinct predecessor context; the second is
|
|
189
|
+
// reinforced across three episodes. Insertion order must not decide —
|
|
190
|
+
// the poured mass must.
|
|
191
|
+
const m = newMind();
|
|
192
|
+
await m.ingest([["the sky is", "blue today"]]);
|
|
193
|
+
for (let i = 0; i < 3; i++) await m.ingest([["the sky is", "grey often"]]);
|
|
194
|
+
assert.equal(await m.respondText("the sky is"), "grey often");
|
|
195
|
+
await m.store.close();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
199
|
+
// Section H — pass-3 flip points
|
|
200
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
201
|
+
|
|
202
|
+
test("H1: an ALU kernel decline is observable, not silent", async () => {
|
|
203
|
+
const { Alu } = await import("../dist/src/alu/src/index.js");
|
|
204
|
+
const alu = new Alu();
|
|
205
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
206
|
+
const v = alu.recogniseValue(enc("tall"));
|
|
207
|
+
assert.equal(alu.applyCaught, 0);
|
|
208
|
+
// A symbol fed to arithmetic: the rule declines (null) — and the decline
|
|
209
|
+
// is counted with the error retained, so a genuine kernel bug can never
|
|
210
|
+
// hide behind the same catch invisibly.
|
|
211
|
+
assert.equal(alu.apply("add", [v, v]), null);
|
|
212
|
+
assert.equal(alu.applyCaught, 1);
|
|
213
|
+
assert.ok(alu.lastApplyError !== null);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("H2: the ALU meaning-memo does not change answers across repeat responds", async () => {
|
|
217
|
+
const m = newMind();
|
|
218
|
+
await m.ingest([
|
|
219
|
+
["1+2", "3"],
|
|
220
|
+
["where do penguins live?", "penguins live in antarctica"],
|
|
221
|
+
]);
|
|
222
|
+
// Arithmetic by word-operator (exercises meaningOf), literal infix, and a
|
|
223
|
+
// plain-English query — each asked twice; the memoised second pass must
|
|
224
|
+
// answer identically.
|
|
225
|
+
for (const q of ["sum 3 4", "2+2 3+3", "where do penguins live?"]) {
|
|
226
|
+
const first = await m.respondText(q);
|
|
227
|
+
assert.equal(await m.respondText(q), first, q);
|
|
228
|
+
}
|
|
229
|
+
assert.equal(await m.respondText("sum 3 4"), "7");
|
|
230
|
+
await m.store.close();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
234
|
+
// Section I — pass-4 flip points
|
|
235
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
236
|
+
|
|
237
|
+
test("I1: containsSpan rejects a sparse subsequence that isSpanShaped accepts", async () => {
|
|
238
|
+
const { containsSpan, isSpanShaped } = await import(
|
|
239
|
+
"../dist/src/mind/mechanisms/extraction.js"
|
|
240
|
+
);
|
|
241
|
+
const m = newMind();
|
|
242
|
+
await m.ingest([["what is ice?", "ice is frozen water"]]);
|
|
243
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
244
|
+
// "cold" is a gap-tolerant subsequence of this sentence (c…o…l…d in order)
|
|
245
|
+
// but NOT a contiguous run nor a recognised subtree: the fusion gate
|
|
246
|
+
// (containsSpan) must reject it while extraction's multi-piece reading
|
|
247
|
+
// (isSpanShaped) still accepts it.
|
|
248
|
+
const query = enc("since october, less daylight");
|
|
249
|
+
const short = enc("cold");
|
|
250
|
+
assert.equal(isSpanShaped(m, query, short), true);
|
|
251
|
+
assert.equal(containsSpan(m, query, short), false);
|
|
252
|
+
// A contiguous run passes both.
|
|
253
|
+
assert.equal(containsSpan(m, enc("very cold night"), short), true);
|
|
254
|
+
await m.store.close();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("I2: a read of a dangling node id is empty, safe, and COUNTED", async () => {
|
|
258
|
+
const m = newMind();
|
|
259
|
+
await m.ingest([["a b", "c d"]]);
|
|
260
|
+
assert.equal(m.store.danglingReads, 0);
|
|
261
|
+
const out = await m.express(99_999_999);
|
|
262
|
+
assert.equal(out.length, 0);
|
|
263
|
+
assert.equal(m.store.danglingReads, 1);
|
|
264
|
+
await m.store.close();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
268
|
+
// Section F — rationale still traces a full respond (instrumentation path)
|
|
269
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
270
|
+
|
|
271
|
+
test("F1: inspectRationale sees the provenance-bearing pipeline end to end", async () => {
|
|
272
|
+
const m = newMind();
|
|
273
|
+
await m.ingest([
|
|
274
|
+
["what is ice?", "ice is frozen water"],
|
|
275
|
+
["what is fire?", "fire is hot plasma"],
|
|
276
|
+
]);
|
|
277
|
+
const steps = [];
|
|
278
|
+
const r = await m.respond("what is ice?", (s) => steps.push(s));
|
|
279
|
+
assert.ok(r.provenance !== undefined);
|
|
280
|
+
const names = new Set(steps.map((s) => s.mechanism ?? s.name));
|
|
281
|
+
assert.ok(steps.length > 0, "rationale stream must not be empty");
|
|
282
|
+
assert.ok(
|
|
283
|
+
[...names].some((n) => String(n).includes("respond")) ||
|
|
284
|
+
steps.some((s) => JSON.stringify(s).includes("respond")),
|
|
285
|
+
"the respond mechanism must appear in the rationale",
|
|
286
|
+
);
|
|
287
|
+
await m.store.close();
|
|
288
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// 32-confluence.test.mjs — the Confluence Join: conjunctive queries.
|
|
2
|
+
//
|
|
3
|
+
// THE CLASS: answers that are stored in NO single fact and exist only as the
|
|
4
|
+
// INTERSECTION of independent evidence streams. "Which material is
|
|
5
|
+
// translucent and featherlight?" — each property reaches its own exemplars;
|
|
6
|
+
// the entity satisfying BOTH lives exactly where the streams meet. A path-
|
|
7
|
+
// following mechanism cannot answer this (any one path grounds one
|
|
8
|
+
// constraint), and fusion answers it WRONG (one fact per constraint, from
|
|
9
|
+
// different entities). Confluence intersects the aligned exemplars across
|
|
10
|
+
// constraints and returns the discriminative content they share, gated by
|
|
11
|
+
// the same structural IDF the attention climb derives — so it can only ever
|
|
12
|
+
// name content that byte-literally exists in two independently learnt facts:
|
|
13
|
+
// it cannot fabricate.
|
|
14
|
+
//
|
|
15
|
+
// Every filler in these corpora is ≥ maxGroup bytes (the literal-alignment
|
|
16
|
+
// quantum), the same constraint CAST's weave detection lives under.
|
|
17
|
+
|
|
18
|
+
import { test } from "node:test";
|
|
19
|
+
import assert from "node:assert/strict";
|
|
20
|
+
import { Mind } from "../dist/src/index.js";
|
|
21
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
22
|
+
|
|
23
|
+
const mk = (seed = 7) =>
|
|
24
|
+
new Mind({ seed, store: new SQliteStore({ path: ":memory:" }) });
|
|
25
|
+
const ask = async (m, q) =>
|
|
26
|
+
(await m.respondText(q)).replace(/\s+/g, " ").trim();
|
|
27
|
+
|
|
28
|
+
// A small materials corpus: each entity has two properties; every property
|
|
29
|
+
// is shared with a distractor entity, so NO single constraint identifies
|
|
30
|
+
// anything — only the intersection does.
|
|
31
|
+
const materials = [
|
|
32
|
+
["Porcelain is translucent", "translucent"],
|
|
33
|
+
["Porcelain is featherlight", "featherlight"],
|
|
34
|
+
["Aluminium is featherlight", "featherlight"],
|
|
35
|
+
["Aluminium is waterproof", "waterproof"],
|
|
36
|
+
["Cast iron is waterproof", "waterproof"],
|
|
37
|
+
["Cast iron is translucent", "translucent"],
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
41
|
+
// Section A — two-constraint entity resolution
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
test("A1 — the entity satisfying BOTH constraints is found, not a one-constraint distractor", async () => {
|
|
45
|
+
const m = mk();
|
|
46
|
+
await m.ingest(materials);
|
|
47
|
+
|
|
48
|
+
// translucent ∩ featherlight = Porcelain (Aluminium is featherlight but
|
|
49
|
+
// not translucent; Cast iron is translucent but not featherlight).
|
|
50
|
+
const got = await ask(m, "Which material is translucent and featherlight?");
|
|
51
|
+
assert.ok(
|
|
52
|
+
/Porcelain/i.test(got),
|
|
53
|
+
`expected the intersection entity Porcelain, got "${got}"`,
|
|
54
|
+
);
|
|
55
|
+
assert.ok(
|
|
56
|
+
!/Aluminium|Cast iron/i.test(got),
|
|
57
|
+
`a one-constraint distractor leaked into the answer: "${got}"`,
|
|
58
|
+
);
|
|
59
|
+
await m.store.close();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("A2 — each pairing resolves to ITS intersection (the join is not a lucky top anchor)", async () => {
|
|
63
|
+
const m = mk();
|
|
64
|
+
await m.ingest(materials);
|
|
65
|
+
|
|
66
|
+
const cases = [
|
|
67
|
+
[/featherlight.*waterproof|waterproof.*featherlight/, "Aluminium"],
|
|
68
|
+
[/waterproof.*translucent|translucent.*waterproof/, "Cast iron"],
|
|
69
|
+
];
|
|
70
|
+
const got1 = await ask(m, "Which material is featherlight and waterproof?");
|
|
71
|
+
assert.ok(/Aluminium/i.test(got1), `expected Aluminium, got "${got1}"`);
|
|
72
|
+
assert.ok(!/Porcelain/i.test(got1), `distractor leaked: "${got1}"`);
|
|
73
|
+
|
|
74
|
+
const got2 = await ask(m, "Which material is waterproof and translucent?");
|
|
75
|
+
assert.ok(/Cast iron/i.test(got2), `expected Cast iron, got "${got2}"`);
|
|
76
|
+
assert.ok(!/Aluminium/i.test(got2), `distractor leaked: "${got2}"`);
|
|
77
|
+
await m.store.close();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("A3 — constraint order does not change the intersection", async () => {
|
|
81
|
+
const m = mk();
|
|
82
|
+
await m.ingest(materials);
|
|
83
|
+
|
|
84
|
+
const got = await ask(m, "Which material is featherlight and translucent?");
|
|
85
|
+
assert.ok(
|
|
86
|
+
/Porcelain/i.test(got),
|
|
87
|
+
`expected Porcelain regardless of constraint order, got "${got}"`,
|
|
88
|
+
);
|
|
89
|
+
await m.store.close();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
93
|
+
// Section B — honesty: an empty intersection must not fabricate
|
|
94
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
95
|
+
|
|
96
|
+
test("B1 — no entity satisfies both: the join must not invent a pairing", async () => {
|
|
97
|
+
const m = mk();
|
|
98
|
+
await m.ingest([
|
|
99
|
+
["Porcelain is translucent", "translucent"],
|
|
100
|
+
["Aluminium is waterproof", "waterproof"],
|
|
101
|
+
["Obsidian is razor sharp", "razor sharp"],
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
// Nothing is both translucent and waterproof — the intersection is empty.
|
|
105
|
+
// Whatever fallback answers, it must not ASSERT the false conjunction.
|
|
106
|
+
const got = await ask(m, "Which material is translucent and waterproof?");
|
|
107
|
+
assert.ok(
|
|
108
|
+
!/Porcelain is waterproof|Aluminium is translucent/i.test(got),
|
|
109
|
+
`a fabricated conjunction was asserted: "${got}"`,
|
|
110
|
+
);
|
|
111
|
+
await m.store.close();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
115
|
+
// Section C — cross-domain: the same meet works on relational facts
|
|
116
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
117
|
+
|
|
118
|
+
test("C1 — who did BOTH things: conjunctive resolution over biographical facts", async () => {
|
|
119
|
+
const m = mk();
|
|
120
|
+
await m.ingest([
|
|
121
|
+
["Leonardo painted the Mona Lisa", "the Mona Lisa"],
|
|
122
|
+
["Leonardo designed flying machines", "flying machines"],
|
|
123
|
+
["Raphael painted the School of Athens", "the School of Athens"],
|
|
124
|
+
["Brunelleschi designed the great dome", "the great dome"],
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
const got = await ask(
|
|
128
|
+
m,
|
|
129
|
+
"Who painted the Mona Lisa and designed flying machines?",
|
|
130
|
+
);
|
|
131
|
+
assert.ok(/Leonardo/i.test(got), `expected Leonardo, got "${got}"`);
|
|
132
|
+
assert.ok(
|
|
133
|
+
!/Raphael|Brunelleschi/i.test(got),
|
|
134
|
+
`a one-constraint distractor leaked: "${got}"`,
|
|
135
|
+
);
|
|
136
|
+
await m.store.close();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
140
|
+
// Section D — non-interference: single-constraint queries keep their path
|
|
141
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
142
|
+
|
|
143
|
+
test("D1 — a single-constraint query still answers through the ordinary pipeline", async () => {
|
|
144
|
+
const m = mk();
|
|
145
|
+
await m.ingest(materials);
|
|
146
|
+
|
|
147
|
+
const got = await ask(m, "Porcelain is translucent");
|
|
148
|
+
assert.ok(
|
|
149
|
+
got.length > 0 && !/Aluminium|Cast iron/i.test(got),
|
|
150
|
+
`single-fact query degraded: "${got}"`,
|
|
151
|
+
);
|
|
152
|
+
await m.store.close();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
156
|
+
// Section E — seed independence (approximate resonance must not decide)
|
|
157
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
158
|
+
|
|
159
|
+
test("E1 — the intersection is seed-independent (it is exact, not resonant)", async () => {
|
|
160
|
+
for (const seed of [1, 7, 42, 99]) {
|
|
161
|
+
const m = mk(seed);
|
|
162
|
+
await m.ingest(materials);
|
|
163
|
+
const got = await ask(
|
|
164
|
+
m,
|
|
165
|
+
"Which material is translucent and featherlight?",
|
|
166
|
+
);
|
|
167
|
+
assert.ok(
|
|
168
|
+
/Porcelain/i.test(got) && !/Aluminium|Cast iron/i.test(got),
|
|
169
|
+
`seed ${seed}: expected Porcelain, got "${got}"`,
|
|
170
|
+
);
|
|
171
|
+
await m.store.close();
|
|
172
|
+
}
|
|
173
|
+
});
|