@hviana/sema 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +7 -7
- package/dist/src/alu/src/index.d.ts +1 -1
- package/dist/src/alu/src/index.js +1 -1
- package/dist/src/alu/src/parser.js +2 -6
- package/dist/src/alu/src/resonance.d.ts +13 -0
- package/dist/src/alu/src/resonance.js +41 -0
- package/dist/src/alu/test/alu.test.js +39 -0
- package/dist/src/bytes.d.ts +6 -2
- package/dist/src/bytes.js +10 -4
- package/dist/src/canon.js +44 -0
- package/dist/src/geometry.d.ts +19 -1
- package/dist/src/geometry.js +125 -141
- package/dist/src/meter.d.ts +33 -0
- package/dist/src/meter.js +34 -1
- package/dist/src/mind/articulation.js +14 -1
- package/dist/src/mind/attention.d.ts +12 -0
- package/dist/src/mind/attention.js +44 -16
- package/dist/src/mind/bridge.js +3 -3
- package/dist/src/mind/derivation.d.ts +40 -0
- package/dist/src/mind/derivation.js +34 -0
- package/dist/src/mind/evidence.d.ts +24 -0
- package/dist/src/mind/evidence.js +90 -0
- package/dist/src/mind/graph-search.d.ts +89 -15
- package/dist/src/mind/graph-search.js +345 -174
- package/dist/src/mind/learning.js +1 -1
- package/dist/src/mind/mechanisms/cover.d.ts +19 -3
- package/dist/src/mind/mechanisms/cover.js +142 -61
- package/dist/src/mind/mechanisms/recall.js +10 -3
- package/dist/src/mind/mind.d.ts +6 -0
- package/dist/src/mind/mind.js +5 -2
- package/dist/src/mind/pipeline.d.ts +5 -1
- package/dist/src/mind/pipeline.js +220 -90
- package/dist/src/mind/primitives.d.ts +25 -5
- package/dist/src/mind/primitives.js +107 -44
- package/dist/src/mind/reasoning.d.ts +18 -4
- package/dist/src/mind/reasoning.js +487 -328
- package/dist/src/mind/recognition.js +29 -13
- package/dist/src/mind/resonance.js +1 -11
- package/dist/src/mind/traverse.d.ts +45 -5
- package/dist/src/mind/traverse.js +285 -8
- package/dist/src/mind/types.d.ts +16 -1
- package/dist/src/store-sqlite.d.ts +25 -0
- package/dist/src/store-sqlite.js +89 -1
- package/dist/src/store.d.ts +48 -4
- package/dist/src/store.js +86 -6
- package/docs/INDEX.md +20 -19
- package/docs/INVARIANTS.md +17 -16
- package/docs/architecture/bounded-reads.md +1 -1
- package/docs/architecture/caches.md +5 -4
- package/docs/architecture/closure.md +45 -5
- package/docs/architecture/cost-model.md +16 -0
- package/docs/architecture/evidence.md +113 -0
- package/docs/architecture/exact-vs-approximate.md +10 -9
- package/docs/architecture/factored-machinery.md +14 -13
- package/docs/architecture/fold-contract.md +51 -1
- package/docs/architecture/mechanism-market.md +21 -0
- package/docs/architecture/memoization.md +3 -3
- package/docs/architecture/meter.md +2 -1
- package/docs/architecture/saturation.md +12 -0
- package/docs/architecture/store.md +25 -2
- package/docs/failures/tempting-but-wrong.md +13 -2
- package/docs/harness/gates.md +12 -10
- package/docs/mechanisms/cover.md +23 -6
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alu/README.md +10 -2
- package/src/alu/src/index.ts +1 -0
- package/src/alu/src/parser.ts +6 -6
- package/src/alu/src/resonance.ts +42 -0
- package/src/alu/test/alu.test.ts +40 -0
- package/src/bytes.ts +13 -3
- package/src/canon.ts +40 -0
- package/src/geometry.ts +183 -154
- package/src/meter.ts +34 -1
- package/src/mind/articulation.ts +14 -2
- package/src/mind/attention.ts +47 -25
- package/src/mind/bridge.ts +3 -3
- package/src/mind/derivation.ts +77 -0
- package/src/mind/evidence.ts +107 -0
- package/src/mind/graph-search.ts +449 -221
- package/src/mind/learning.ts +1 -7
- package/src/mind/match.ts +1 -2
- package/src/mind/mechanisms/cast.ts +1 -2
- package/src/mind/mechanisms/cover.ts +207 -87
- package/src/mind/mechanisms/extraction.ts +1 -2
- package/src/mind/mechanisms/prefix-completion.ts +1 -1
- package/src/mind/mechanisms/recall.ts +17 -5
- package/src/mind/mechanisms/reference.ts +1 -1
- package/src/mind/mind.ts +9 -30
- package/src/mind/pipeline.ts +263 -104
- package/src/mind/primitives.ts +119 -43
- package/src/mind/reasoning.ts +611 -419
- package/src/mind/recognition.ts +24 -9
- package/src/mind/resonance.ts +2 -16
- package/src/mind/trace.ts +1 -1
- package/src/mind/traverse.ts +321 -8
- package/src/mind/types.ts +15 -11
- package/src/store-sqlite.ts +92 -1
- package/src/store.ts +113 -7
- package/test/105-derive-through-reports-its-refusal.test.mjs +8 -5
- package/test/106-the-join-fires.test.mjs +21 -0
- package/test/111-the-cover-assembly-is-counted.test.mjs +8 -5
- package/test/128-the-leads-somewhere-pair-agrees.test.mjs +18 -12
- package/test/136-the-two-named-limits.test.mjs +3 -2
- package/test/137-the-law-lives-once-and-below.test.mjs +21 -0
- package/test/148-exact-shortcuts-agree.test.mjs +188 -0
- package/test/149-the-closure-engine.test.mjs +138 -0
- package/test/150-the-join-is-output-sensitive.test.mjs +66 -0
- package/test/151-the-cover-pays-for-what-it-reaches.test.mjs +142 -0
- package/test/152-the-read-side-names-as-the-write-side.test.mjs +146 -0
- package/test/153-a-cheaper-bound-is-looked-at-first.test.mjs +155 -0
- package/test/154-the-question-names-the-step.test.mjs +281 -0
- package/test/24-generalization.test.mjs +32 -0
- package/test/36-bloom.test.mjs +53 -0
- package/test/37-cluster-dispersion-fusion.test.mjs +75 -0
- package/test/48-recognise-turn-connective.test.mjs +3 -2
- package/test/55-cost-meter.test.mjs +4 -4
- package/test/90-connector-read-cap.test.mjs +7 -7
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// 148-exact-shortcuts-agree.test.mjs — two shortcuts that are EXACT, pinned
|
|
2
|
+
// against the definitions they shortcut.
|
|
3
|
+
//
|
|
4
|
+
// 148.1 THE SEGMENT PROBE DECIDES A MISS EXACTLY. `exactNode` (primitives.ts)
|
|
5
|
+
// answers "which stored node are these bytes?" without building the fold
|
|
6
|
+
// when a level-0 content segment is not a stored flat branch: a fold
|
|
7
|
+
// names a branch only when every child is named, and a segment is one
|
|
8
|
+
// flat node over single-byte atoms. So the answer must equal
|
|
9
|
+
// `foldTree(perceive(bytes))` on EVERY span — hits, misses, edges — and
|
|
10
|
+
// this walks every sub-span of real deposits and of noise to show it.
|
|
11
|
+
//
|
|
12
|
+
// 148.3 THE IDENTITY FOLD HAS THE VECTOR FOLD'S SHAPE. `exactNode` names a
|
|
13
|
+
// stream through `contentIdentity` (geometry.ts), which groups the same
|
|
14
|
+
// way the vector fold does but reads an item's gist only where the shape
|
|
15
|
+
// does — the eight coordinates `itemKey` hashes inside an over-long row —
|
|
16
|
+
// and computes those lazily. Pinned with an interning namer, so every
|
|
17
|
+
// node is named and the two folds must agree on EVERY node of the tree,
|
|
18
|
+
// over streams long and repetitive enough to force over-long rows (low
|
|
19
|
+
// entropy keeps the cut levels flat, so the split falls to `itemKey`).
|
|
20
|
+
//
|
|
21
|
+
// 148.2 THE ASCII CANON IS THE UNICODE CANON. `textCanon` takes a byte loop
|
|
22
|
+
// for ASCII input instead of decode → NFKC → lowercase → regex → encode.
|
|
23
|
+
// NFKC is the identity on ASCII and the regex's ASCII whitespace is
|
|
24
|
+
// TAB..CR and SPACE, so the two must agree byte for byte; the reference
|
|
25
|
+
// here is the general reading, spelled out, over random ASCII drawn from
|
|
26
|
+
// an alphabet that is mostly whitespace and case.
|
|
27
|
+
|
|
28
|
+
import { test } from "node:test";
|
|
29
|
+
import assert from "node:assert/strict";
|
|
30
|
+
import { Mind } from "../dist/src/index.js";
|
|
31
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
32
|
+
import { exactNode, foldTree, perceive } from "../dist/src/mind/primitives.js";
|
|
33
|
+
import { textCanon } from "../dist/src/canon.js";
|
|
34
|
+
import { bytesToTree, contentIdentity } from "../dist/src/geometry.js";
|
|
35
|
+
|
|
36
|
+
const enc = new TextEncoder();
|
|
37
|
+
const dec = new TextDecoder("utf-8", { fatal: false });
|
|
38
|
+
|
|
39
|
+
/** Deterministic generator — the suite forbids Math.random in fixtures. */
|
|
40
|
+
function lcg(seed) {
|
|
41
|
+
let x = seed >>> 0;
|
|
42
|
+
return () => {
|
|
43
|
+
x = (Math.imul(x, 1664525) + 1013904223) >>> 0;
|
|
44
|
+
return x / 0x100000000;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test("148.1 exactNode agrees with the full fold on every sub-span", async () => {
|
|
49
|
+
const mind = new Mind({
|
|
50
|
+
seed: 3,
|
|
51
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
52
|
+
});
|
|
53
|
+
const facts = [
|
|
54
|
+
["The director of Eva is Gustaf Molander.", "Gustaf Molander"],
|
|
55
|
+
["gustaf molander country", "The country of Gustaf Molander is Sweden."],
|
|
56
|
+
["What is the capital of France", "The capital of France is Paris"],
|
|
57
|
+
["ice", "cold"],
|
|
58
|
+
];
|
|
59
|
+
await mind.ingest(facts);
|
|
60
|
+
|
|
61
|
+
const rand = lcg(148);
|
|
62
|
+
const noise = Array.from(
|
|
63
|
+
{ length: 6 },
|
|
64
|
+
() =>
|
|
65
|
+
Array.from(
|
|
66
|
+
{ length: 24 },
|
|
67
|
+
() => "abcdefghij ETAOIN.,"[Math.floor(rand() * 19)],
|
|
68
|
+
).join(""),
|
|
69
|
+
);
|
|
70
|
+
const streams = [...facts.flat(), ...noise].map((t) => enc.encode(t));
|
|
71
|
+
|
|
72
|
+
let hits = 0, misses = 0;
|
|
73
|
+
for (const bytes of streams) {
|
|
74
|
+
for (let a = 0; a < bytes.length; a++) {
|
|
75
|
+
for (let b = a + 1; b <= bytes.length; b++) {
|
|
76
|
+
const span = bytes.subarray(a, b);
|
|
77
|
+
const full = foldTree(mind, perceive(mind, span), 0).node;
|
|
78
|
+
assert.equal(
|
|
79
|
+
exactNode(mind, span),
|
|
80
|
+
full,
|
|
81
|
+
`span "${
|
|
82
|
+
dec.decode(span)
|
|
83
|
+
}": the segment probe disagrees with the fold`,
|
|
84
|
+
);
|
|
85
|
+
if (full === null) misses++;
|
|
86
|
+
else hits++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Non-vacuity: both outcomes must be exercised, or agreement proves nothing.
|
|
91
|
+
assert.ok(hits > 50, `too few hits to pin the agreement (${hits})`);
|
|
92
|
+
assert.ok(misses > 1000, `too few misses to pin the agreement (${misses})`);
|
|
93
|
+
await mind.store.close();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("148.2 the ASCII canon equals the Unicode canon, byte for byte", () => {
|
|
97
|
+
const reference = (bytes) =>
|
|
98
|
+
enc.encode(
|
|
99
|
+
dec.decode(bytes).normalize("NFKC").toLowerCase().replace(
|
|
100
|
+
/(\S)\s+(?=\S)/g,
|
|
101
|
+
"$1 ",
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
// Mostly the characters the rule is about: every ASCII whitespace, both
|
|
105
|
+
// cases, punctuation, plus the control bytes JS's \s does NOT include.
|
|
106
|
+
const alphabet = [
|
|
107
|
+
0x20,
|
|
108
|
+
0x09,
|
|
109
|
+
0x0a,
|
|
110
|
+
0x0b,
|
|
111
|
+
0x0c,
|
|
112
|
+
0x0d,
|
|
113
|
+
0x1c,
|
|
114
|
+
0x1f,
|
|
115
|
+
0x00,
|
|
116
|
+
0x7f,
|
|
117
|
+
..."aZ.,'Q9 ".split("").map((c) => c.charCodeAt(0)),
|
|
118
|
+
];
|
|
119
|
+
const rand = lcg(2);
|
|
120
|
+
const cases = [
|
|
121
|
+
"",
|
|
122
|
+
" ",
|
|
123
|
+
" lead",
|
|
124
|
+
"trail ",
|
|
125
|
+
"a b\t\tc\n\nD",
|
|
126
|
+
"\t\v\f\r",
|
|
127
|
+
"ALL CAPS",
|
|
128
|
+
].map((t) => enc.encode(t));
|
|
129
|
+
for (let i = 0; i < 4000; i++) {
|
|
130
|
+
const n = Math.floor(rand() * 14);
|
|
131
|
+
cases.push(
|
|
132
|
+
Uint8Array.from(
|
|
133
|
+
{ length: n },
|
|
134
|
+
() => alphabet[Math.floor(rand() * alphabet.length)],
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
for (const c of cases) {
|
|
139
|
+
assert.deepEqual(
|
|
140
|
+
[...textCanon(c)],
|
|
141
|
+
[...reference(c)],
|
|
142
|
+
`canon of ${JSON.stringify(dec.decode(c))} diverged`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("148.3 the identity fold groups exactly as the vector fold", () => {
|
|
148
|
+
const mind = new Mind({
|
|
149
|
+
seed: 5,
|
|
150
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
151
|
+
});
|
|
152
|
+
const ids = new Map();
|
|
153
|
+
const intern = (key) => {
|
|
154
|
+
let id = ids.get(key);
|
|
155
|
+
if (id === undefined) ids.set(key, id = ids.size);
|
|
156
|
+
return id;
|
|
157
|
+
};
|
|
158
|
+
const atoms = (bytes) => Array.from(bytes, (b) => -(b + 1)).join(",");
|
|
159
|
+
/** foldTree's naming, over the vector fold's tree. */
|
|
160
|
+
const name = (n) =>
|
|
161
|
+
n.kids === null ? -(n.leaf[0] + 1) : intern(n.kids.map(name).join(","));
|
|
162
|
+
const rand = lcg(1483);
|
|
163
|
+
const alphabets = ["ab", "aab", "abc ", "0123456789", "the quick brown fox "];
|
|
164
|
+
let streams = 0;
|
|
165
|
+
for (const alpha of alphabets) {
|
|
166
|
+
for (let t = 0; t < 40; t++) {
|
|
167
|
+
const n = 2 + Math.floor(rand() * 1500);
|
|
168
|
+
const bytes = Uint8Array.from(
|
|
169
|
+
{ length: n },
|
|
170
|
+
() => alpha.charCodeAt(Math.floor(rand() * alpha.length)),
|
|
171
|
+
);
|
|
172
|
+
const viaVectors = name(bytesToTree(mind.space, mind.alphabet, bytes));
|
|
173
|
+
const viaIdentity = contentIdentity(
|
|
174
|
+
mind.space,
|
|
175
|
+
mind.alphabet,
|
|
176
|
+
bytes,
|
|
177
|
+
(from, to) =>
|
|
178
|
+
to - from === 1
|
|
179
|
+
? -(bytes[from] + 1)
|
|
180
|
+
: intern(atoms(bytes.subarray(from, to))),
|
|
181
|
+
(kids) => intern(kids.join(",")),
|
|
182
|
+
);
|
|
183
|
+
assert.equal(viaIdentity, viaVectors, `${alpha} stream of ${n} bytes`);
|
|
184
|
+
streams++;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
assert.equal(streams, 200);
|
|
188
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// 149-the-closure-engine.test.mjs — one closure, over layers.
|
|
2
|
+
//
|
|
3
|
+
// `closeOver` (derivation.ts) closes a derivation under the law, layer by layer:
|
|
4
|
+
// each layer OFFERS, the law admits, and the state a layer reaches is the state
|
|
5
|
+
// the next one is offered against. The pipeline's post-grounding stage is that
|
|
6
|
+
// engine with two layers (the walk, the fusion), so the gates it used to spell
|
|
7
|
+
// by hand are now the law's or the layer's. Pinned:
|
|
8
|
+
//
|
|
9
|
+
// 149.1 ¬FIXED is the engine's, applied before any layer pays: a fixed state
|
|
10
|
+
// enters no layer — no offer is asked, nothing is wrapped.
|
|
11
|
+
// 149.2 layers are PHASES, in order, never revisited: the second layer is
|
|
12
|
+
// offered the first one's final state, and the first is not asked again.
|
|
13
|
+
// 149.3 a layer that does not ENGAGE is not entered at all.
|
|
14
|
+
// 149.4 the law's refusal ends THAT layer's walk, not the closure.
|
|
15
|
+
// 149.5 the pipeline asks the engine, and sequences no layer by hand.
|
|
16
|
+
|
|
17
|
+
import { test } from "node:test";
|
|
18
|
+
import assert from "node:assert/strict";
|
|
19
|
+
import { readFile } from "node:fs/promises";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { closeOver } from "../dist/src/mind/derivation.js";
|
|
23
|
+
|
|
24
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const enc = new TextEncoder();
|
|
26
|
+
const QUERY = enc.encode("ABCDEFGHIJKL");
|
|
27
|
+
const W = 4;
|
|
28
|
+
const state = (extra = {}) => ({
|
|
29
|
+
product: new Uint8Array(0),
|
|
30
|
+
accounted: [],
|
|
31
|
+
remainder: [[0, 12]],
|
|
32
|
+
cost: 0,
|
|
33
|
+
...extra,
|
|
34
|
+
});
|
|
35
|
+
/** A layer that offers the given products, one per ask, then nothing. */
|
|
36
|
+
const layer = (name, products, log, extra = {}) => {
|
|
37
|
+
let i = 0;
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
offer: async (d) => {
|
|
41
|
+
log.push(`${name}:ask:${new TextDecoder().decode(d.product)}`);
|
|
42
|
+
if (i >= products.length) return null;
|
|
43
|
+
return {
|
|
44
|
+
product: enc.encode(products[i++]),
|
|
45
|
+
contains: true,
|
|
46
|
+
reaches: true,
|
|
47
|
+
cost: 1,
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
...extra,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
test("149.1 a fixed state enters no layer", async () => {
|
|
55
|
+
const log = [];
|
|
56
|
+
const entered = [];
|
|
57
|
+
const out = await closeOver(
|
|
58
|
+
state({ fixed: true }),
|
|
59
|
+
QUERY,
|
|
60
|
+
W,
|
|
61
|
+
[layer("a", ["x"], log)],
|
|
62
|
+
async (name, walk) => {
|
|
63
|
+
entered.push(name);
|
|
64
|
+
return walk();
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
assert.deepEqual(log, [], "no offer may be asked of a fixed state");
|
|
68
|
+
assert.deepEqual(entered, [], "no layer may be entered for a fixed state");
|
|
69
|
+
assert.equal(out.fixed, true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("149.2 layers are phases, in order, never revisited", async () => {
|
|
73
|
+
const log = [];
|
|
74
|
+
const out = await closeOver(state(), QUERY, W, [
|
|
75
|
+
layer("a", ["a1", "a2"], log),
|
|
76
|
+
layer("b", ["b1"], log),
|
|
77
|
+
]);
|
|
78
|
+
assert.deepEqual(log, [
|
|
79
|
+
"a:ask:",
|
|
80
|
+
"a:ask:a1",
|
|
81
|
+
"a:ask:a2",
|
|
82
|
+
"b:ask:a2",
|
|
83
|
+
"b:ask:b1",
|
|
84
|
+
]);
|
|
85
|
+
assert.equal(new TextDecoder().decode(out.product), "b1");
|
|
86
|
+
assert.equal(out.cost, 3, "every admitted step is priced");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("149.3 a layer that does not engage is not entered", async () => {
|
|
90
|
+
const log = [];
|
|
91
|
+
const entered = [];
|
|
92
|
+
await closeOver(
|
|
93
|
+
state(),
|
|
94
|
+
QUERY,
|
|
95
|
+
W,
|
|
96
|
+
[layer("a", ["a1"], log, { engages: () => false }), layer("b", [], log)],
|
|
97
|
+
async (name, walk) => {
|
|
98
|
+
entered.push(name);
|
|
99
|
+
return walk();
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
assert.deepEqual(entered, ["b"]);
|
|
103
|
+
assert.deepEqual(log, ["b:ask:"]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("149.4 the law's refusal ends that layer, not the closure", async () => {
|
|
107
|
+
const log = [];
|
|
108
|
+
const refused = [];
|
|
109
|
+
const ends = [];
|
|
110
|
+
// `a` offers a step that neither carries nor reaches: the law refuses it.
|
|
111
|
+
const a = {
|
|
112
|
+
name: "a",
|
|
113
|
+
offer: async () => ({ product: enc.encode("zz"), contains: true, cost: 1 }),
|
|
114
|
+
onRefused: () => refused.push("a"),
|
|
115
|
+
onEnd: (from, to) => ends.push(["a", from === to]),
|
|
116
|
+
};
|
|
117
|
+
const out = await closeOver(state(), QUERY, W, [a, layer("b", ["b1"], log)]);
|
|
118
|
+
assert.deepEqual(refused, ["a"]);
|
|
119
|
+
assert.deepEqual(ends, [["a", true]], "a refused layer ends where it began");
|
|
120
|
+
assert.equal(new TextDecoder().decode(out.product), "b1");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("149.5 the pipeline asks the engine and sequences no layer by hand", async () => {
|
|
124
|
+
const src = await readFile(
|
|
125
|
+
join(here, "..", "src", "mind", "pipeline.ts"),
|
|
126
|
+
"utf8",
|
|
127
|
+
);
|
|
128
|
+
const code = src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
129
|
+
assert.match(code, /closeOver\(/, "the post-grounding stage is the engine");
|
|
130
|
+
assert.ok(
|
|
131
|
+
!/\breason\(|\bfuseAttention\(/.test(code),
|
|
132
|
+
"the pipeline calls a layer's walk directly again — offer it to the engine",
|
|
133
|
+
);
|
|
134
|
+
assert.ok(
|
|
135
|
+
!/decided\.complete\s*\?/.test(code),
|
|
136
|
+
"a complete grounding is the law's FIXED clause, applied by the engine",
|
|
137
|
+
);
|
|
138
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// 150-the-join-is-output-sensitive.test.mjs — the join is priced for the facts
|
|
2
|
+
// a derivation STANDS ON, never for every fact the exploration REACHES.
|
|
3
|
+
//
|
|
4
|
+
// THE TRAP. `deriveThrough` used to fire for every finalized out with a node.
|
|
5
|
+
// A chain hop forks across every continuation (trap 13: the offer is the
|
|
6
|
+
// corpus's own structure), so a form whose continuation is a hub reaches every
|
|
7
|
+
// one of that hub's fixpoints — and each paid an entity scan, and each join it
|
|
8
|
+
// fired was a new fact to scan. Measured on the 31.7M-node store: a 46-byte
|
|
9
|
+
// dialogue query reached 2,000+ fixpoints in a minute and passed 10 GB. It is
|
|
10
|
+
// the trap `recompleteNode` records, and the cure is `deepen`'s: the solve runs
|
|
11
|
+
// to its lightest derivation, licenses the facts THAT derivation stood on, and
|
|
12
|
+
// re-runs with their joins as ordinary rules, until no licensed fact joins.
|
|
13
|
+
//
|
|
14
|
+
// Pinned:
|
|
15
|
+
// 150.1 the facts priced do not grow with the hub's degree — the same query
|
|
16
|
+
// over a hub of 8 continuations and of 32 prices the same facts;
|
|
17
|
+
// 150.2 the answer is unchanged by the degree (the derivation is the same).
|
|
18
|
+
|
|
19
|
+
import { test } from "node:test";
|
|
20
|
+
import assert from "node:assert/strict";
|
|
21
|
+
import { Mind } from "../dist/src/index.js";
|
|
22
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
23
|
+
|
|
24
|
+
/** "hello" → "hi there" (one first hop), then "hi there" forks across `n`
|
|
25
|
+
* distinct replies — each a fixpoint fact the search reaches. The filler
|
|
26
|
+
* raises the corpus scale N so the hop's READ cap (√N) admits all `n`: on a
|
|
27
|
+
* tiny store √N is 1–2 and no hub can be read at all. Measured before the
|
|
28
|
+
* license: 35 facts scanned at n = 8, 59 at n = 32. */
|
|
29
|
+
async function hub(n) {
|
|
30
|
+
const mind = new Mind({
|
|
31
|
+
seed: 7,
|
|
32
|
+
profile: true,
|
|
33
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
34
|
+
});
|
|
35
|
+
const pairs = [["hello", "hi there"]];
|
|
36
|
+
for (let k = 0; k < 1200; k++) {
|
|
37
|
+
pairs.push([`filler question ${k}`, `filler answer ${k}`]);
|
|
38
|
+
}
|
|
39
|
+
for (let k = 0; k < n; k++) {
|
|
40
|
+
pairs.push(["hi there", `reply number ${k} talks about topic ${k}.`]);
|
|
41
|
+
}
|
|
42
|
+
await mind.ingest(pairs);
|
|
43
|
+
return mind;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test("150.1 the join prices the derivation's facts, not the hub's", async () => {
|
|
47
|
+
const priced = [];
|
|
48
|
+
const answers = [];
|
|
49
|
+
for (const n of [8, 32]) {
|
|
50
|
+
const mind = await hub(n);
|
|
51
|
+
// The tail is bytes nothing learnt, so the cover is expensive and the
|
|
52
|
+
// search reaches every fixpoint of the hub before it settles.
|
|
53
|
+
answers.push(await mind.respondText("hello zqxv wbjk"));
|
|
54
|
+
const c = mind.lastCost.counters;
|
|
55
|
+
assert.ok(c.joinFacts > 0, "the derivation's facts are priced");
|
|
56
|
+
priced.push(c.joinFacts);
|
|
57
|
+
await mind.store.close();
|
|
58
|
+
}
|
|
59
|
+
assert.equal(
|
|
60
|
+
priced[1],
|
|
61
|
+
priced[0],
|
|
62
|
+
`facts priced grew with the hub's degree: ${priced.join(" → ")}`,
|
|
63
|
+
);
|
|
64
|
+
// 150.2 — the degree changes what the search REACHES, not what it chose.
|
|
65
|
+
assert.equal(answers[1], answers[0]);
|
|
66
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// 151-the-cover-pays-for-what-it-reaches.test.mjs — the cover's expensive
|
|
2
|
+
// premises are resolved where the search REACHES them, and a span's cheapest
|
|
3
|
+
// completion dominates the rest.
|
|
4
|
+
//
|
|
5
|
+
// THE TRAP, four times. Each of these paid for every candidate the search
|
|
6
|
+
// could have used, before (or regardless of whether) it used it:
|
|
7
|
+
// • connectors — every touching pair of recognised sites was bridged before
|
|
8
|
+
// the search ran (measured on the 31.7M-node store: a 262-byte query
|
|
9
|
+
// recognised whole paid 23 bridges, 143,520 junction pops, 7.3 s of 7.9 s);
|
|
10
|
+
// • concept hops — every edge-less site paid a halo lookup, though the hop is
|
|
11
|
+
// priced at CONCEPT and the whole's own edge closes the cover at one STEP;
|
|
12
|
+
// • fusion — the post-grounding fusion climbed for a second point of
|
|
13
|
+
// attention even when primary's evidence spans the whole query, where no
|
|
14
|
+
// root can stand a quantum apart from it (3.7 s for an exact dialogue turn);
|
|
15
|
+
// • alternatives — a greeting's thousands of replies were each a completion of
|
|
16
|
+
// the same span, each fused and canonically resolved with its neighbours
|
|
17
|
+
// (122,096 pops, 5.4 s of an 8.6 s cover, to stand in the end on two hops).
|
|
18
|
+
//
|
|
19
|
+
// Pinned:
|
|
20
|
+
// 151.1 a query recognised whole bridges no connector for its parts;
|
|
21
|
+
// 151.2 an edge-less part's concept hop is not looked up when the whole
|
|
22
|
+
// closes the cover first;
|
|
23
|
+
// 151.3 a grounding standing on the whole query pays no fusion climb;
|
|
24
|
+
// 151.4 the work the cover generates does not grow with a hub's degree — the
|
|
25
|
+
// replies past the first are dominated, and the answer is unchanged.
|
|
26
|
+
|
|
27
|
+
import { test } from "node:test";
|
|
28
|
+
import assert from "node:assert/strict";
|
|
29
|
+
import { Mind } from "../dist/src/index.js";
|
|
30
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
31
|
+
|
|
32
|
+
async function fixture(pairs) {
|
|
33
|
+
const mind = new Mind({
|
|
34
|
+
seed: 7,
|
|
35
|
+
profile: true,
|
|
36
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
37
|
+
});
|
|
38
|
+
await mind.ingest(pairs);
|
|
39
|
+
return mind;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sitesOf = (mind, text) => {
|
|
43
|
+
mind.beginResponse();
|
|
44
|
+
try {
|
|
45
|
+
return mind.recogniseSpan(new TextEncoder().encode(text)).sites.map((
|
|
46
|
+
s,
|
|
47
|
+
) => [s.start, s.end]);
|
|
48
|
+
} finally {
|
|
49
|
+
mind.endResponse();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
test("151.1 a query recognised whole bridges no connector for its parts", async () => {
|
|
54
|
+
// `ab` and `cd` both continue and TOUCH in the query, so they are an offered
|
|
55
|
+
// connector pair; `abcd` is learnt whole and closes the cover at one STEP,
|
|
56
|
+
// before either part's rewrite (a STEP each) leaves the agenda.
|
|
57
|
+
const mind = await fixture([
|
|
58
|
+
["ab", "x"],
|
|
59
|
+
["cd", "y"],
|
|
60
|
+
["abcd", "the whole"],
|
|
61
|
+
]);
|
|
62
|
+
const sites = sitesOf(mind, "abcd");
|
|
63
|
+
assert.ok(
|
|
64
|
+
sites.some(([s, e]) => s === 0 && e === 2) &&
|
|
65
|
+
sites.some(([s, e]) => s === 2 && e === 4),
|
|
66
|
+
`the touching parts must be recognised for the pair to be offered: ${
|
|
67
|
+
JSON.stringify(sites)
|
|
68
|
+
}`,
|
|
69
|
+
);
|
|
70
|
+
assert.equal(await mind.respondText("abcd"), "the whole");
|
|
71
|
+
assert.equal(mind.lastCost.counters.coverBridges ?? 0, 0);
|
|
72
|
+
await mind.store.close();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("151.2 an unreached concept hop is not looked up", async () => {
|
|
76
|
+
// `ab` and `cd` are learnt only as CONTINUATIONS: they lead somewhere (a halo)
|
|
77
|
+
// but have no edge of their own, so each is offered a concept hop.
|
|
78
|
+
const mind = await fixture([
|
|
79
|
+
["abcd", "the whole"],
|
|
80
|
+
["first", "ab"],
|
|
81
|
+
["second", "cd"],
|
|
82
|
+
]);
|
|
83
|
+
const sites = sitesOf(mind, "abcd");
|
|
84
|
+
assert.ok(
|
|
85
|
+
sites.length > 1,
|
|
86
|
+
`the edge-less parts must be recognised: ${JSON.stringify(sites)}`,
|
|
87
|
+
);
|
|
88
|
+
assert.equal(await mind.respondText("abcd"), "the whole");
|
|
89
|
+
assert.equal(mind.lastCost.counters.haloQueries ?? 0, 0);
|
|
90
|
+
await mind.store.close();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("151.3 a grounding standing on the whole query pays no fusion climb", async () => {
|
|
94
|
+
// The answer carries none of the query's bytes, so the derivation stays open
|
|
95
|
+
// and the fusion layer engages — but primary's evidence is the whole query.
|
|
96
|
+
const mind = await fixture([
|
|
97
|
+
["ab", "x"],
|
|
98
|
+
["cd", "y"],
|
|
99
|
+
["abcd", "the whole"],
|
|
100
|
+
]);
|
|
101
|
+
assert.equal(await mind.respondText("abcd"), "the whole");
|
|
102
|
+
assert.equal(mind.lastCost.counters.climbs ?? 0, 0);
|
|
103
|
+
await mind.store.close();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
/** "hello" → "hi there", then "hi there" forks across `n` replies — each a
|
|
107
|
+
* fixpoint completing the same span. The filler raises N so the hop's READ
|
|
108
|
+
* cap (√N) admits all `n`. */
|
|
109
|
+
async function hub(n) {
|
|
110
|
+
const pairs = [["hello", "hi there"]];
|
|
111
|
+
for (let k = 0; k < 1200; k++) {
|
|
112
|
+
pairs.push([`filler question ${k}`, `filler answer ${k}`]);
|
|
113
|
+
}
|
|
114
|
+
for (let k = 0; k < n; k++) {
|
|
115
|
+
pairs.push(["hi there", `reply number ${k} talks about topic ${k}.`]);
|
|
116
|
+
}
|
|
117
|
+
return await fixture(pairs);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
test("151.4 a hub's degree does not grow the work the cover generates", async () => {
|
|
121
|
+
const generated = [];
|
|
122
|
+
const answers = [];
|
|
123
|
+
for (const n of [8, 32]) {
|
|
124
|
+
const mind = await hub(n);
|
|
125
|
+
// The tail is bytes nothing learnt, so the goal is PASS-priced and, before
|
|
126
|
+
// dominance, the search fused every reply with its neighbours.
|
|
127
|
+
answers.push(await mind.respondText("hello zqxv wbjk"));
|
|
128
|
+
const c = mind.lastCost.counters;
|
|
129
|
+
assert.ok(
|
|
130
|
+
c.searchDominated > 0,
|
|
131
|
+
"the replies past the first are dominated",
|
|
132
|
+
);
|
|
133
|
+
generated.push(c.searchPops - c.searchDominated);
|
|
134
|
+
await mind.store.close();
|
|
135
|
+
}
|
|
136
|
+
assert.equal(
|
|
137
|
+
generated[1],
|
|
138
|
+
generated[0],
|
|
139
|
+
`rule-generating pops grew with the hub's degree: ${generated.join(" → ")}`,
|
|
140
|
+
);
|
|
141
|
+
assert.equal(answers[1], answers[0]);
|
|
142
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// 152-the-read-side-names-as-the-write-side.test.mjs — a stored deposit asked
|
|
2
|
+
// verbatim resolves to itself, however the store named its branches.
|
|
3
|
+
//
|
|
4
|
+
// THE DEFECT. `intern` (store.ts) names a branch by its children; when they
|
|
5
|
+
// name none, it looks up the FLAT node over the same bytes and reuses it (step
|
|
6
|
+
// 1b, "same bytes, same node"). Every deposit interns flat nodes — its whole
|
|
7
|
+
// input and each canonical window — so a later deposit whose fold grouped
|
|
8
|
+
// `ver` + `!` was stored with an earlier deposit's window `ver!` as that
|
|
9
|
+
// child. The read side (`exactNode`, `foldTree`) named branches by their
|
|
10
|
+
// children alone, so it could never name such a deposit again: measured on the
|
|
11
|
+
// 31.7M-node store, 7 of 80 dialogue turns asked verbatim resolved to nothing
|
|
12
|
+
// and fell to the composition path (seconds instead of milliseconds).
|
|
13
|
+
//
|
|
14
|
+
// Pinned:
|
|
15
|
+
// 152.1 the deposit resolves to its own context node, which leads to its
|
|
16
|
+
// continuation — through a branch named by its bytes;
|
|
17
|
+
// 152.2 the identity fold and the vector fold name the same node;
|
|
18
|
+
// 152.3 it is answered on the exact path — the cover alone, no consensus
|
|
19
|
+
// climb — not after the whole market (which reached the same reply
|
|
20
|
+
// through recall, 6 mechanism runs and a climb, before the fix).
|
|
21
|
+
// 152.4 a name found ONLY through the bytes (a flat index entry, no learnt
|
|
22
|
+
// structure) is where the exact lookup used to miss, so `resolve`
|
|
23
|
+
// still asks the canonical class there — which holds the learnt
|
|
24
|
+
// member that continues;
|
|
25
|
+
// 152.5 recognition does the same, so a query named only by such an entry
|
|
26
|
+
// is answered through the equivalent form that continues.
|
|
27
|
+
|
|
28
|
+
import { test } from "node:test";
|
|
29
|
+
import assert from "node:assert/strict";
|
|
30
|
+
import { Mind } from "../dist/src/index.js";
|
|
31
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
32
|
+
import {
|
|
33
|
+
exactNaming,
|
|
34
|
+
exactNode,
|
|
35
|
+
foldTree,
|
|
36
|
+
perceive,
|
|
37
|
+
resolve,
|
|
38
|
+
} from "../dist/src/mind/primitives.js";
|
|
39
|
+
import { textCanon } from "../dist/src/canon.js";
|
|
40
|
+
|
|
41
|
+
const QUERY = "Hey, Sarah. Come check out my new river!";
|
|
42
|
+
const REPLY = "Wow, you dug a trench.";
|
|
43
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
44
|
+
const dec = (b) => new TextDecoder().decode(b);
|
|
45
|
+
|
|
46
|
+
async function fixture() {
|
|
47
|
+
const mind = new Mind({
|
|
48
|
+
seed: 7,
|
|
49
|
+
profile: true,
|
|
50
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
51
|
+
});
|
|
52
|
+
// `Clever!` interns the canonical window `ver!` FIRST, so the pair's own
|
|
53
|
+
// fold — which groups `ver` + `!` — is stored with that window as the child.
|
|
54
|
+
await mind.ingest([["Clever!", "a word"], [QUERY, REPLY]]);
|
|
55
|
+
return mind;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
test("152.1 a deposit stored through a flat window resolves to its own context", async () => {
|
|
59
|
+
const mind = await fixture();
|
|
60
|
+
mind.beginResponse();
|
|
61
|
+
try {
|
|
62
|
+
const id = resolve(mind, enc(QUERY));
|
|
63
|
+
assert.notEqual(id, null, "the stored deposit must resolve");
|
|
64
|
+
const next = mind.store.nextFirst(id, 1);
|
|
65
|
+
assert.equal(dec(mind.store.bytes(next[0])), REPLY);
|
|
66
|
+
assert.ok(
|
|
67
|
+
mind.meter.flatBranchNames > 0,
|
|
68
|
+
"the fixture must exercise a branch the store named by its bytes",
|
|
69
|
+
);
|
|
70
|
+
} finally {
|
|
71
|
+
mind.endResponse();
|
|
72
|
+
}
|
|
73
|
+
await mind.store.close();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("152.2 the identity fold and the vector fold name the same node", async () => {
|
|
77
|
+
const mind = await fixture();
|
|
78
|
+
mind.beginResponse();
|
|
79
|
+
try {
|
|
80
|
+
const bytes = enc(QUERY);
|
|
81
|
+
const viaIdentity = exactNode(mind, bytes);
|
|
82
|
+
const viaVectors = foldTree(mind, perceive(mind, bytes), 0).node;
|
|
83
|
+
assert.notEqual(viaIdentity, null);
|
|
84
|
+
assert.equal(viaIdentity, viaVectors);
|
|
85
|
+
} finally {
|
|
86
|
+
mind.endResponse();
|
|
87
|
+
}
|
|
88
|
+
await mind.store.close();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("152.3 the deposit asked verbatim is answered on the exact path", async () => {
|
|
92
|
+
const mind = await fixture();
|
|
93
|
+
assert.equal(await mind.respondText(QUERY), REPLY);
|
|
94
|
+
const c = mind.lastCost.counters;
|
|
95
|
+
assert.equal(c.mechanismRuns, 1, "the cover alone must ground it");
|
|
96
|
+
assert.equal(c.climbs ?? 0, 0, "no consensus climb may run");
|
|
97
|
+
await mind.store.close();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/** A flat index entry for `tonight` with NO learnt structure over those bytes —
|
|
101
|
+
* the state the 31.7M-node store holds for it (written by an earlier deposit
|
|
102
|
+
* path; today's deposits always intern the structure beside the flat copy, so
|
|
103
|
+
* the state is built through the store's own write API) — and the case-folded
|
|
104
|
+
* `Tonight`, learnt with a continuation. */
|
|
105
|
+
async function byteOnly() {
|
|
106
|
+
const mind = new Mind({
|
|
107
|
+
seed: 7,
|
|
108
|
+
profile: true,
|
|
109
|
+
store: new SQliteStore({ path: ":memory:", D: 1024, maxGroup: 4 }),
|
|
110
|
+
});
|
|
111
|
+
await mind.ingest([[
|
|
112
|
+
"Tonight",
|
|
113
|
+
"The composer of Tonight is Leonard Bernstein.",
|
|
114
|
+
]]);
|
|
115
|
+
await mind.store.putBranch(
|
|
116
|
+
Array.from(enc("tonight"), (b) => -(b + 1)),
|
|
117
|
+
new Float32Array(1024).fill(1 / 32),
|
|
118
|
+
);
|
|
119
|
+
await mind.buildCanonIndex();
|
|
120
|
+
return mind;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
test("152.4 a name found only through the bytes still asks the canonical class", async () => {
|
|
124
|
+
const mind = await byteOnly();
|
|
125
|
+
mind.beginResponse(undefined, textCanon);
|
|
126
|
+
try {
|
|
127
|
+
const named = exactNaming(mind, enc("tonight"));
|
|
128
|
+
assert.notEqual(named.id, null, "the flat entry names the bytes");
|
|
129
|
+
assert.equal(named.byBytes, true, "…and nothing but the bytes names them");
|
|
130
|
+
const id = resolve(mind, enc("tonight"));
|
|
131
|
+
assert.equal(dec(mind.store.bytes(id)), "Tonight");
|
|
132
|
+
assert.ok(mind.store.hasNext(id), "the class member that continues");
|
|
133
|
+
} finally {
|
|
134
|
+
mind.endResponse();
|
|
135
|
+
}
|
|
136
|
+
await mind.store.close();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("152.5 a query named only by bytes is answered through the form that continues", async () => {
|
|
140
|
+
const mind = await byteOnly();
|
|
141
|
+
assert.equal(
|
|
142
|
+
await mind.respondText("tonight"),
|
|
143
|
+
"The composer of Tonight is Leonard Bernstein.",
|
|
144
|
+
);
|
|
145
|
+
await mind.store.close();
|
|
146
|
+
});
|