@hviana/sema 0.9.0 → 0.9.1
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 +20 -0
- package/dist/src/meter.js +21 -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/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 +101 -58
- package/dist/src/mind/mechanisms/recall.js +0 -1
- package/dist/src/mind/mind.js +2 -2
- package/dist/src/mind/pipeline.d.ts +5 -1
- package/dist/src/mind/pipeline.js +175 -87
- 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 +445 -321
- package/dist/src/mind/recognition.js +29 -13
- package/dist/src/mind/resonance.js +1 -11
- package/dist/src/mind/traverse.d.ts +3 -3
- package/dist/src/mind/traverse.js +3 -3
- package/dist/src/mind/types.d.ts +7 -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 +18 -18
- package/docs/INVARIANTS.md +16 -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/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 +21 -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/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 +149 -84
- package/src/mind/mechanisms/extraction.ts +1 -2
- package/src/mind/mechanisms/prefix-completion.ts +1 -1
- package/src/mind/mechanisms/recall.ts +1 -3
- package/src/mind/mechanisms/reference.ts +1 -1
- package/src/mind/mind.ts +5 -30
- package/src/mind/pipeline.ts +206 -102
- package/src/mind/primitives.ts +119 -43
- package/src/mind/reasoning.ts +558 -413
- 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 +3 -3
- package/src/mind/types.ts +9 -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/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,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
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// 153-a-cheaper-bound-is-looked-at-first.test.mjs — the market and the span
|
|
2
|
+
// probe do the same work for less.
|
|
3
|
+
//
|
|
4
|
+
// THE MARKET. Mechanisms run in declared order and are pruned by the
|
|
5
|
+
// incumbent — but a mechanism floored LOWER than one about to invest, declared
|
|
6
|
+
// after it, could not prune it. Measured on the 31.7M-node store: a query that
|
|
7
|
+
// opens a trained form (#114 of the battery) was won by prefix-completion at
|
|
8
|
+
// grade 1 after CAST (floor grade 2) had paid the consensus climb and the weave,
|
|
9
|
+
// and confluence, extraction and reference their reads. The grounding loop now
|
|
10
|
+
// runs such a mechanism AHEAD (pipeline.ts, "a cheaper bound is looked at before
|
|
11
|
+
// a dearer one is paid for") and skips what cannot beat the grade it reaches.
|
|
12
|
+
//
|
|
13
|
+
// THE SPAN PROBE. Recognition's interior pass probes every span of up to
|
|
14
|
+
// `reach` bytes past each endpoint, and each probe hashed its span from the
|
|
15
|
+
// start — O(n·reach²) bytes (251,660,406 for one response on that store).
|
|
16
|
+
// `Store.flatSpans` extends the hash a start was last probed at instead.
|
|
17
|
+
//
|
|
18
|
+
// Pinned:
|
|
19
|
+
// 153.1 a mechanism floored below CAST's, run ahead, skips CAST and the rest
|
|
20
|
+
// of the composition market — and the weave is never paid for;
|
|
21
|
+
// 153.2 the decision is the declared order's: the same answer, from the same
|
|
22
|
+
// mechanism, as a market whose bounds are hidden from the run-ahead;
|
|
23
|
+
// 153.3 a bound that does not undercut the dearer floor runs nothing ahead:
|
|
24
|
+
// a query the cover grounds whole runs the cover alone;
|
|
25
|
+
// 153.4 the span prober answers exactly `findFlatBranch` over the same span,
|
|
26
|
+
// in every order a scanner can ask in.
|
|
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
|
+
|
|
33
|
+
const FORM =
|
|
34
|
+
"What time does the movie start? We would need to start between 7-7:30pm";
|
|
35
|
+
const QUERY = "What time does the movie start?";
|
|
36
|
+
|
|
37
|
+
async function fixture() {
|
|
38
|
+
const mind = new Mind({
|
|
39
|
+
seed: 7,
|
|
40
|
+
profile: true,
|
|
41
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
42
|
+
});
|
|
43
|
+
await mind.ingest([
|
|
44
|
+
[FORM, "Sure, let me check the showtimes."],
|
|
45
|
+
["Do you like thrillers?", "Yes, I love them."],
|
|
46
|
+
["The movie was great.", "I agree."],
|
|
47
|
+
]);
|
|
48
|
+
return mind;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The decision a market reaches when no mechanism can be run ahead: every
|
|
52
|
+
* floor answers the run-ahead's refusing probe with 0, which no later floor
|
|
53
|
+
* undercuts — so the loop is the plain declared-order loop. (A real
|
|
54
|
+
* `worthRunning` accepts a floor of −∞; only the refusing probe declines it.) */
|
|
55
|
+
function declaredOrder(mind) {
|
|
56
|
+
for (let i = 0; i < mind.mechanisms.length; i++) {
|
|
57
|
+
const m = mind.mechanisms[i];
|
|
58
|
+
mind.mechanisms[i] = {
|
|
59
|
+
...m,
|
|
60
|
+
floor: (ctx, q, pre, worth) =>
|
|
61
|
+
worth(-Infinity) ? m.floor(ctx, q, pre, worth) : Promise.resolve(0),
|
|
62
|
+
run: m.run.bind(m),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The winning candidate's provenance, read off the rationale. */
|
|
68
|
+
async function decide(mind, q) {
|
|
69
|
+
let winner = null;
|
|
70
|
+
const answer = await mind.respondText(q, (s) => {
|
|
71
|
+
if (s.mechanism.at(-1) === "decideGrounding") {
|
|
72
|
+
winner = s.data.candidates.find((c) => c.decided);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return { answer, winner };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
test("153.1 a cheaper bound run ahead skips the composition market", async () => {
|
|
79
|
+
const mind = await fixture();
|
|
80
|
+
const answer = await mind.respondText(QUERY);
|
|
81
|
+
const c = mind.lastCost.counters;
|
|
82
|
+
const phases = mind.lastCost.phases;
|
|
83
|
+
assert.equal(answer, "Sure, let me check the showtimes.");
|
|
84
|
+
assert.ok(
|
|
85
|
+
c.mechanismsBounded >= 4,
|
|
86
|
+
`cast, confluence, extraction and reference are bounded out: ${c.mechanismsBounded}`,
|
|
87
|
+
);
|
|
88
|
+
assert.equal(phases["cast.run"], undefined, "CAST never runs");
|
|
89
|
+
assert.equal(phases["weave"], undefined, "the weave is never paid for");
|
|
90
|
+
await mind.store.close();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("153.2 the decision is the declared order's", async () => {
|
|
94
|
+
const ahead = await fixture();
|
|
95
|
+
const plain = await fixture();
|
|
96
|
+
declaredOrder(plain);
|
|
97
|
+
const a = await decide(ahead, QUERY);
|
|
98
|
+
const p = await decide(plain, QUERY);
|
|
99
|
+
assert.equal(a.answer, p.answer);
|
|
100
|
+
assert.equal(a.winner.provenance, p.winner.provenance);
|
|
101
|
+
assert.equal(a.winner.weight, p.winner.weight);
|
|
102
|
+
// …and the oracle really is the dearer market: there, CAST paid for the
|
|
103
|
+
// weave, and confluence ran.
|
|
104
|
+
assert.ok(plain.lastCost.phases["weave"] !== undefined);
|
|
105
|
+
assert.ok(plain.lastCost.phases["confluence.run"] !== undefined);
|
|
106
|
+
assert.equal(ahead.lastCost.phases["confluence.run"], undefined);
|
|
107
|
+
await ahead.store.close();
|
|
108
|
+
await plain.store.close();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("153.3 a cover that grounds whole runs nothing ahead", async () => {
|
|
112
|
+
const mind = await fixture();
|
|
113
|
+
assert.equal(
|
|
114
|
+
await mind.respondText("Do you like thrillers?"),
|
|
115
|
+
"Yes, I love them.",
|
|
116
|
+
);
|
|
117
|
+
const c = mind.lastCost.counters;
|
|
118
|
+
assert.equal(c.mechanismRuns, 1, "the cover alone");
|
|
119
|
+
assert.equal(c.mechanismsBounded ?? 0, 0);
|
|
120
|
+
await mind.store.close();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("153.4 the span prober answers exactly findFlatBranch", async () => {
|
|
124
|
+
const mind = await fixture();
|
|
125
|
+
const store = mind.store;
|
|
126
|
+
const bytes = new TextEncoder().encode(
|
|
127
|
+
`${FORM} Do you like thrillers? The movie was great. ${QUERY}`,
|
|
128
|
+
);
|
|
129
|
+
const n = bytes.length;
|
|
130
|
+
const want = (s, e) => store.findFlatBranch(bytes.subarray(s, e));
|
|
131
|
+
const probe = store.flatSpans(bytes);
|
|
132
|
+
let hits = 0, asked = 0;
|
|
133
|
+
const check = (s, e) => {
|
|
134
|
+
const got = probe(s, e);
|
|
135
|
+
assert.equal(got, want(s, e), `span [${s},${e})`);
|
|
136
|
+
asked++;
|
|
137
|
+
if (got !== null) hits++;
|
|
138
|
+
};
|
|
139
|
+
// Ends ascending per start (the extension path)…
|
|
140
|
+
for (let e = 1; e <= n; e++) {
|
|
141
|
+
for (let s = Math.max(0, e - 40); s < e; s++) {
|
|
142
|
+
check(s, e);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// …a suffix then its one-byte trim (the short path), and revisits that end
|
|
146
|
+
// BEFORE the last probe of their start (the rehash path).
|
|
147
|
+
for (let s = n - 1; s >= 0; s--) {
|
|
148
|
+
check(s, n);
|
|
149
|
+
check(s, n - 1 > s ? n - 1 : n);
|
|
150
|
+
check(s, Math.min(n, s + 3));
|
|
151
|
+
}
|
|
152
|
+
assert.ok(asked > 5_000, `probes asked: ${asked}`);
|
|
153
|
+
assert.ok(hits > 50, `the buffer must hold stored flat spans: ${hits}`);
|
|
154
|
+
await store.close();
|
|
155
|
+
});
|
|
@@ -264,6 +264,38 @@ test("3.1 — a two-topic query fuses BOTH points of attention", async () => {
|
|
|
264
264
|
);
|
|
265
265
|
});
|
|
266
266
|
|
|
267
|
+
test("3.1 — the second topic is fused on its OWN wording, never on a sub-window anchor", async () => {
|
|
268
|
+
// Observed before the fix (attention.ts canonicalChunkId): the region
|
|
269
|
+
// `ace and ` — inside the OTHER topic's words, "workplace and about" — had a
|
|
270
|
+
// saturated window replaced by its unsaturated 2-byte prefix `e `, that
|
|
271
|
+
// sub-window anchor voted for the Dream Team context, and its position was
|
|
272
|
+
// the "second cluster" the dispersion gate let the topic through on. The
|
|
273
|
+
// fusion passed on scaffolding. Pinned here: the Dream Team root is a
|
|
274
|
+
// committed point of attention whose every contributing region lies in the
|
|
275
|
+
// topic's own words, AND it is fused into the answer.
|
|
276
|
+
const m = await twoTopicMind();
|
|
277
|
+
let anchors = null;
|
|
278
|
+
const got = await m.respondText(TWO_TOPIC_QUERY, (s) => {
|
|
279
|
+
if (s.mechanism.at(-1) === "climbConsensus" && s.data?.anchors) {
|
|
280
|
+
anchors = s.data.anchors;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
const topic = (a) =>
|
|
284
|
+
new TextDecoder().decode(m.store.bytes(a.anchor)).includes("Dream Team");
|
|
285
|
+
const root = anchors?.find((a) => a.commit.status === "root" && topic(a));
|
|
286
|
+
await m.store.close();
|
|
287
|
+
assert.ok(root, "the Dream Team context must be a committed root");
|
|
288
|
+
const own = TWO_TOPIC_QUERY.indexOf(" the 1992 Dream Team");
|
|
289
|
+
const foreign = root.contributingSpans.filter(([s]) => s < own);
|
|
290
|
+
assert.deepEqual(
|
|
291
|
+
foreign,
|
|
292
|
+
[],
|
|
293
|
+
`the Dream Team root is supported from outside its own words: ` +
|
|
294
|
+
JSON.stringify(foreign.map(([s, e]) => TWO_TOPIC_QUERY.slice(s, e))),
|
|
295
|
+
);
|
|
296
|
+
assert.match(got, /Barcelona|gold|Dream Team/i);
|
|
297
|
+
});
|
|
298
|
+
|
|
267
299
|
test("3.1 — the trace surfaces MORE THAN ONE ordered anchor for a two-topic query", async () => {
|
|
268
300
|
const m = await twoTopicMind();
|
|
269
301
|
const steps = [];
|
package/test/36-bloom.test.mjs
CHANGED
|
@@ -14,11 +14,18 @@
|
|
|
14
14
|
// persisted filter (or a stale one); reopen must
|
|
15
15
|
// rebuild/top-up from the table and STILL dedup all
|
|
16
16
|
// previously-minted content, with zero duplicates.
|
|
17
|
+
//
|
|
18
|
+
// The CANON index's filter (canon_bloom) has the same hazard — a false
|
|
19
|
+
// negative there is a canonical form `canonFind` silently fails to propose —
|
|
20
|
+
// and is pinned the same way: across reopen, across rows added after a
|
|
21
|
+
// persisted filter exists, and against a writer that adds rows without
|
|
22
|
+
// touching the filter (its stamp disagrees with the meta, so it is rebuilt).
|
|
17
23
|
|
|
18
24
|
import { test } from "node:test";
|
|
19
25
|
import assert from "node:assert/strict";
|
|
20
26
|
import { execFileSync } from "node:child_process";
|
|
21
27
|
import { rmSync } from "node:fs";
|
|
28
|
+
import { DatabaseSync } from "node:sqlite";
|
|
22
29
|
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
23
30
|
|
|
24
31
|
const cleanup = (path) => {
|
|
@@ -121,3 +128,49 @@ test("crash: reopen after an unclean exit still dedups everything", (t) => {
|
|
|
121
128
|
execFileSync(process.execPath, ["-e", phase2], { stdio: "pipe" });
|
|
122
129
|
assert.ok(true);
|
|
123
130
|
});
|
|
131
|
+
|
|
132
|
+
test("canon filter: persisted with its rows, rebuilt when its stamp is stale", async (t) => {
|
|
133
|
+
const path = `/tmp/bloom-canon-${process.pid}`;
|
|
134
|
+
t.after(() => cleanup(path));
|
|
135
|
+
const H = (i) => (i * 2654435761) >>> 0;
|
|
136
|
+
const findsAll = (s, from, to) => {
|
|
137
|
+
for (let i = from; i < to; i++) {
|
|
138
|
+
assert.deepEqual(s.canonFind(H(i)), [i], `canon row ${i} not proposed`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Session 1: rows written by the store itself, then committed and closed.
|
|
143
|
+
let s = new SQliteStore({ path, D: 64 });
|
|
144
|
+
await s.size();
|
|
145
|
+
for (let i = 0; i < 200; i++) s.canonAdd(H(i), i);
|
|
146
|
+
await s.setMeta("canon.upto", "200");
|
|
147
|
+
s.commit();
|
|
148
|
+
findsAll(s, 0, 200);
|
|
149
|
+
await s.close();
|
|
150
|
+
|
|
151
|
+
// Session 2: the persisted filter is loaded, and rows added on top of it
|
|
152
|
+
// are seen in this session and after the next reopen.
|
|
153
|
+
s = new SQliteStore({ path, D: 64 });
|
|
154
|
+
await s.size();
|
|
155
|
+
findsAll(s, 0, 200);
|
|
156
|
+
for (let i = 200; i < 300; i++) s.canonAdd(H(i), i);
|
|
157
|
+
await s.setMeta("canon.upto", "300");
|
|
158
|
+
s.commit();
|
|
159
|
+
findsAll(s, 0, 300);
|
|
160
|
+
await s.close();
|
|
161
|
+
|
|
162
|
+
// A writer that knows nothing of the filter (an older build) adds rows and
|
|
163
|
+
// advances the cursor, as every canon build does.
|
|
164
|
+
const db = new DatabaseSync(path + ".sqlite");
|
|
165
|
+
for (let i = 300; i < 350; i++) {
|
|
166
|
+
db.prepare("INSERT INTO canon (h, id) VALUES (?, ?)").run(H(i), i);
|
|
167
|
+
}
|
|
168
|
+
db.prepare("UPDATE meta SET val = '350' WHERE key = 'canon.upto'").run();
|
|
169
|
+
db.close();
|
|
170
|
+
|
|
171
|
+
s = new SQliteStore({ path, D: 64 });
|
|
172
|
+
await s.size();
|
|
173
|
+
findsAll(s, 0, 350);
|
|
174
|
+
assert.deepEqual(s.canonFind(H(9999)), [], "an absent key is not proposed");
|
|
175
|
+
await s.close();
|
|
176
|
+
});
|
|
@@ -215,3 +215,78 @@ test("fuseAttention: 1 cluster, low breadth — neither measure saves it (the li
|
|
|
215
215
|
);
|
|
216
216
|
await m.store.close();
|
|
217
217
|
});
|
|
218
|
+
|
|
219
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
220
|
+
// The dispersion question asked of the CONTEXT (reasoning.ts sharedPlaces).
|
|
221
|
+
// The votes that land on a root are a lossy witness of where the query and the
|
|
222
|
+
// root's context agree: a region votes once, for its top anchor, so a frame
|
|
223
|
+
// every context shares corroborates whichever anchor a tie hands it. The
|
|
224
|
+
// context's own bytes answer the question exactly — at window scale, in how
|
|
225
|
+
// many separate places of the query does it share content? These two cases
|
|
226
|
+
// pin both directions with REAL anchors (a stored context and its learnt
|
|
227
|
+
// continuation), each fabricated as a one-cluster, low-breadth root so that
|
|
228
|
+
// only the new reading can decide.
|
|
229
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
230
|
+
|
|
231
|
+
async function sharedPlacesCase(contextText, query) {
|
|
232
|
+
const m = mk(1);
|
|
233
|
+
await m.ingest([
|
|
234
|
+
...CORPUS,
|
|
235
|
+
[contextText, "the omega continuation"],
|
|
236
|
+
]);
|
|
237
|
+
const q = enc(query);
|
|
238
|
+
m.beginResponse();
|
|
239
|
+
const anchor = m.recogniseSpan(enc(contextText)).sites
|
|
240
|
+
.find((s) => s.start === 0 && s.end === contextText.length)?.payload;
|
|
241
|
+
m.endResponse();
|
|
242
|
+
assert.ok(anchor !== undefined, "the context must be a stored form");
|
|
243
|
+
const root = {
|
|
244
|
+
anchor,
|
|
245
|
+
vote: 1,
|
|
246
|
+
idfVote: 1,
|
|
247
|
+
peak: 1,
|
|
248
|
+
start: 0,
|
|
249
|
+
end: q.length,
|
|
250
|
+
clusters: 1,
|
|
251
|
+
breadth: 0.1,
|
|
252
|
+
};
|
|
253
|
+
const placeholder = { ...root, start: q.length, end: q.length };
|
|
254
|
+
const primary = enc("PRIMARYANCHORTEXT");
|
|
255
|
+
const out = dec(
|
|
256
|
+
(await fuseAttention(m, q, {
|
|
257
|
+
product: primary,
|
|
258
|
+
accounted: [],
|
|
259
|
+
remainder: [],
|
|
260
|
+
cost: 0,
|
|
261
|
+
}, {
|
|
262
|
+
attention: async () => ({
|
|
263
|
+
roots: [placeholder, root],
|
|
264
|
+
ranked: [placeholder, root],
|
|
265
|
+
}),
|
|
266
|
+
guide: gistOf(m, q),
|
|
267
|
+
})).product,
|
|
268
|
+
);
|
|
269
|
+
await m.store.close();
|
|
270
|
+
return { out, primary: dec(primary) };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
test("fuseAttention: a context sharing ONE phrase with the query does not fuse, however long the phrase (the live echo shape)", async () => {
|
|
274
|
+
// The live defect's shape: the query's closing phrase is byte-identical to
|
|
275
|
+
// how an unrelated conversation ended. Many windows, ONE place.
|
|
276
|
+
const { out, primary } = await sharedPlacesCase(
|
|
277
|
+
"Thank you very much!",
|
|
278
|
+
"red circle then 2+2 equals what. Thank you very much!",
|
|
279
|
+
);
|
|
280
|
+
assert.equal(out, primary, `a one-place echo fused in: "${out}"`);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("fuseAttention: a context sharing TWO separate places with the query fuses, even when its votes formed one cluster", async () => {
|
|
284
|
+
const { out, primary } = await sharedPlacesCase(
|
|
285
|
+
"red circle at noon, and then: Thank you very much!",
|
|
286
|
+
"red circle then 2+2 equals what. Thank you very much!",
|
|
287
|
+
);
|
|
288
|
+
assert.ok(
|
|
289
|
+
out.includes("omega") && out.includes(primary),
|
|
290
|
+
`a two-place context must fuse in, got "${out}"`,
|
|
291
|
+
);
|
|
292
|
+
});
|
|
@@ -32,7 +32,8 @@ import assert from "node:assert/strict";
|
|
|
32
32
|
import { Mind, SQliteStore } from "../dist/src/index.js";
|
|
33
33
|
import { textCanon } from "../dist/src/canon.js";
|
|
34
34
|
import { recognise } from "../dist/src/mind/recognition.js";
|
|
35
|
-
import {
|
|
35
|
+
import { perceive, resolve } from "../dist/src/mind/primitives.js";
|
|
36
|
+
import { latin1 } from "../dist/src/bytes.js";
|
|
36
37
|
|
|
37
38
|
const enc = (s) => new TextEncoder().encode(s);
|
|
38
39
|
const dec = (b) => new TextDecoder().decode(b).replace(/\0+$/, "");
|
|
@@ -71,7 +72,7 @@ test("recognise(): a real leading connective word ('And ') still finds the train
|
|
|
71
72
|
const boundaries = [30, 61];
|
|
72
73
|
m.perceiveMemo = new Map();
|
|
73
74
|
m.perceiveMemo.set(
|
|
74
|
-
|
|
75
|
+
latin1(query),
|
|
75
76
|
perceive(m, query, undefined, undefined, boundaries),
|
|
76
77
|
);
|
|
77
78
|
|
|
@@ -156,10 +156,10 @@ test("7. a bigger query costs more than a smaller one", async () => {
|
|
|
156
156
|
"the capital of France is and the capital of Japan is and Paris is in",
|
|
157
157
|
);
|
|
158
158
|
const big = mind.lastCost.counters;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
);
|
|
159
|
+
// Fold work in both of its forms: the vector fold (perception) and the
|
|
160
|
+
// identity fold `exactNode` reads a node id through without vectors.
|
|
161
|
+
const folded = (c) => (c.perceivedBytes ?? 0) + (c.identityBytes ?? 0);
|
|
162
|
+
assert.ok(folded(big) > folded(small), "fold cost tracks query length");
|
|
163
163
|
await mind.store.close();
|
|
164
164
|
});
|
|
165
165
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 90-connector-read-cap.test.mjs — the "already answered" probe must read by
|
|
2
2
|
// the QUERY, not by the corpus.
|
|
3
3
|
//
|
|
4
|
-
// `
|
|
4
|
+
// `offerConnectors` (src/mind/mechanisms/cover.ts) drops a site whose
|
|
5
5
|
// continuation already appears elsewhere in the query — stale transcript
|
|
6
6
|
// evidence, whose bridges would only be discarded later. The test is a
|
|
7
7
|
// substring search, so it needs the candidate's bytes; it used to reconstruct
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// B reconstructed against a 3-byte prompt. So the invariant here is per-read
|
|
29
29
|
// SIZE.
|
|
30
30
|
//
|
|
31
|
-
// It is measured by calling `
|
|
31
|
+
// It is measured by calling `offerConnectors` DIRECTLY and diffing the meter
|
|
32
32
|
// across it. A whole-response counter cannot express this: `bytesRead` sums
|
|
33
33
|
// every reader in the pipeline, and the answer itself is a long continuation
|
|
34
34
|
// that is legitimately read in full — an earlier draft of this file asserted on
|
|
@@ -46,7 +46,7 @@ import { test } from "node:test";
|
|
|
46
46
|
import assert from "node:assert/strict";
|
|
47
47
|
import { Mind } from "../dist/src/index.js";
|
|
48
48
|
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
49
|
-
import {
|
|
49
|
+
import { offerConnectors } from "../dist/src/mind/mechanisms/cover.js";
|
|
50
50
|
|
|
51
51
|
/** Learnt CONTINUATIONS far longer than the query that will be asked — the
|
|
52
52
|
* shape the cap governs: an uncapped probe reconstructs each one in full
|
|
@@ -80,7 +80,7 @@ test("connector probe reads by the query, not by the learnt continuation", async
|
|
|
80
80
|
mind.beginResponse();
|
|
81
81
|
try {
|
|
82
82
|
const { sites } = mind.recogniseSpan(bytes);
|
|
83
|
-
// EXACT ISOLATION.
|
|
83
|
+
// EXACT ISOLATION. offerConnectors also reads bytes the probe has
|
|
84
84
|
// nothing to do with (the n-ary bridge reads each ordered node in full, and
|
|
85
85
|
// legitimately so). The probe itself early-returns when answeredSpans is
|
|
86
86
|
// empty, so running the SAME call both ways and differencing leaves exactly
|
|
@@ -88,12 +88,12 @@ test("connector probe reads by the query, not by the learnt continuation", async
|
|
|
88
88
|
const m = mind.meter;
|
|
89
89
|
mind.answeredSpans = [];
|
|
90
90
|
const a0 = m.byteReads, b0 = m.bytesRead;
|
|
91
|
-
|
|
91
|
+
offerConnectors(mind, sites, bytes);
|
|
92
92
|
const baseReads = m.byteReads - a0, baseBytes = m.bytesRead - b0;
|
|
93
93
|
|
|
94
94
|
mind.answeredSpans = [[0, 1]];
|
|
95
95
|
const a1 = m.byteReads, b1 = m.bytesRead;
|
|
96
|
-
|
|
96
|
+
offerConnectors(mind, sites, bytes);
|
|
97
97
|
const reads = (m.byteReads - a1) - baseReads;
|
|
98
98
|
const read = (m.bytesRead - b1) - baseBytes;
|
|
99
99
|
|
|
@@ -107,7 +107,7 @@ test("connector probe reads by the query, not by the learnt continuation", async
|
|
|
107
107
|
// below is trivially true and proves nothing.
|
|
108
108
|
assert.ok(
|
|
109
109
|
reads > 0,
|
|
110
|
-
`
|
|
110
|
+
`offerConnectors made no byte reads — the probe never ran (sites=${sites.length}), ` +
|
|
111
111
|
`so this file is not testing anything`,
|
|
112
112
|
);
|
|
113
113
|
|