@hviana/sema 0.9.1 → 0.9.3
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/dist/src/meter.d.ts +19 -0
- package/dist/src/meter.js +19 -0
- package/dist/src/mind/evidence.d.ts +30 -0
- package/dist/src/mind/evidence.js +102 -0
- package/dist/src/mind/mechanisms/cast.d.ts +1 -1
- package/dist/src/mind/mechanisms/cast.js +73 -12
- package/dist/src/mind/mechanisms/cover.js +25 -3
- package/dist/src/mind/mechanisms/recall.js +93 -9
- package/dist/src/mind/mechanisms/reference.js +25 -0
- package/dist/src/mind/mind.d.ts +7 -0
- package/dist/src/mind/mind.js +3 -0
- package/dist/src/mind/pipeline-mechanism.js +15 -1
- package/dist/src/mind/pipeline.js +51 -8
- package/dist/src/mind/reasoning.js +65 -18
- package/dist/src/mind/resonance.d.ts +3 -2
- package/dist/src/mind/resonance.js +3 -2
- package/dist/src/mind/traverse.d.ts +94 -2
- package/dist/src/mind/traverse.js +611 -7
- package/dist/src/mind/types.d.ts +12 -0
- package/docs/INDEX.md +18 -17
- package/docs/INVARIANTS.md +1 -0
- package/docs/architecture/evidence.md +256 -0
- package/docs/architecture/exact-vs-approximate.md +10 -9
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/meter.ts +19 -0
- package/src/mind/evidence.ts +125 -0
- package/src/mind/mechanisms/cast.ts +77 -3
- package/src/mind/mechanisms/cover.ts +42 -3
- package/src/mind/mechanisms/recall.ts +123 -11
- package/src/mind/mechanisms/reference.ts +24 -1
- package/src/mind/mind.ts +6 -0
- package/src/mind/pipeline-mechanism.ts +15 -1
- package/src/mind/pipeline.ts +64 -8
- package/src/mind/reasoning.ts +82 -17
- package/src/mind/resonance.ts +3 -2
- package/src/mind/traverse.ts +652 -7
- package/src/mind/types.ts +10 -0
- package/test/154-the-question-names-the-step.test.mjs +368 -0
- package/test/155-the-relation-read-off-another-instance.test.mjs +376 -0
- package/test/29-counterfactual.test.mjs +21 -11
- package/test/76-reference-binding.test.mjs +32 -0
package/src/mind/types.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { MindConfig } from "../config.js";
|
|
|
12
12
|
import type { Meter } from "../meter.js";
|
|
13
13
|
import type { GraphSearch, Leaf, Seg, Site } from "./graph-search.js";
|
|
14
14
|
import type { Rationale } from "./rationale.js";
|
|
15
|
+
import type { WindowIndex } from "./evidence.js";
|
|
15
16
|
import type { ContentFold, Grid } from "../geometry.js";
|
|
16
17
|
|
|
17
18
|
/** One {@link MindContext._depositTrees} entry — see that field's doc.
|
|
@@ -411,6 +412,15 @@ export interface MindContext extends GraphSearchHost {
|
|
|
411
412
|
* ordinary respond() and for the first turn of a conversation. */
|
|
412
413
|
currentTurnStart: number;
|
|
413
414
|
_edgeGuide: Vec | null;
|
|
415
|
+
/** The question currently being answered, as the material `chooseNext`'s
|
|
416
|
+
* exact tier witnesses a continuation's establishing contexts against —
|
|
417
|
+
* its bytes (canonical when the response's canon preserves offsets) and
|
|
418
|
+
* their window index. Set and cleared with `_edgeGuide`. `points` are the
|
|
419
|
+
* anchors the response's consensus climb scored, once it has climbed —
|
|
420
|
+
* the proposals the exact tier reads co-instances from. */
|
|
421
|
+
_edgeAsked:
|
|
422
|
+
| { bytes: Uint8Array; index: WindowIndex; points?: readonly number[] }
|
|
423
|
+
| null;
|
|
414
424
|
_edgeChoice: Map<number, number>;
|
|
415
425
|
_prevSeen: Set<number> | null;
|
|
416
426
|
/** Session cache of node-id → perceived gist, for candidate scoring
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
// 154-the-question-names-the-step.test.mjs — a stored structure is identified
|
|
2
|
+
// by the material at hand, wherever the pieces of that material came from.
|
|
3
|
+
//
|
|
4
|
+
// THE OPERATION (src/mind/evidence.ts). A stored form is WITNESSED by some
|
|
5
|
+
// material when every one of its bytes lies in a W-window the material holds —
|
|
6
|
+
// in any order, at any place. The corpus writes down, for every continuation,
|
|
7
|
+
// the questions that establish it (its predecessors). A continuation is NAMED
|
|
8
|
+
// when one of those questions is witnessed by the asker's bytes plus the node
|
|
9
|
+
// being followed.
|
|
10
|
+
//
|
|
11
|
+
// THE DEFECTS IT CLOSES, measured on the 31.7M-node store and on 5,236 held-out
|
|
12
|
+
// 2WikiMultihopQA compositional questions deposited exactly as the trainer
|
|
13
|
+
// deposits their evidence triples (example/train_base/corpora/wiki2.ts):
|
|
14
|
+
// • the forward choice among a node's continuations read popularity only, so
|
|
15
|
+
// `Who is the father of Frederick II?` answered the most-poured of his
|
|
16
|
+
// eight facts (his citizenship) — the father fact's own establishing
|
|
17
|
+
// question `Frederick II father` lay wholly inside the question;
|
|
18
|
+
// • the walk consumed EVERY form recognised inside the first hop's answer, the
|
|
19
|
+
// entity the hop introduced included, so the chain could not step past it
|
|
20
|
+
// (3 pivot steps over 133 compositional questions);
|
|
21
|
+
// • the second hop's establishing question is held by neither the question
|
|
22
|
+
// (1% of the 5,236) nor the first hop's fact, only by both (41%).
|
|
23
|
+
//
|
|
24
|
+
// Pinned:
|
|
25
|
+
// 154.1 witnessing is order-free, complete or nothing, credits a window to the
|
|
26
|
+
// last source that holds it, and refuses a form below one window;
|
|
27
|
+
// 154.2 the continuation the question names wins over the most-poured one;
|
|
28
|
+
// 154.3 the second hop is named by question material plus the entity the
|
|
29
|
+
// first hop introduced — neither alone holds it;
|
|
30
|
+
// 154.4 a question that names no further step is not extended: the material
|
|
31
|
+
// that named the first hop cannot name a second;
|
|
32
|
+
// 154.5 a fragment (`director`, inside every `… director` question) answers
|
|
33
|
+
// other questions: the cover voices none of its continuations unless the
|
|
34
|
+
// question names one;
|
|
35
|
+
// 154.6 …and neither does recall's argument binding, when the fragment is the
|
|
36
|
+
// query's sole argument;
|
|
37
|
+
// 154.7 a comparison needs the question to name two things: an analog
|
|
38
|
+
// evidenced only by windows the dominant holds, or by scaffolding, is
|
|
39
|
+
// not compared (it used to glue its bare question onto the answer);
|
|
40
|
+
// 154.8 an argument the question holds only under the response's
|
|
41
|
+
// equivalence (`Man At Bath` for the stored `Man at Bath`, inside the
|
|
42
|
+
// question) still binds: the climb proposes it, canonical containment
|
|
43
|
+
// decides.
|
|
44
|
+
|
|
45
|
+
import { test } from "node:test";
|
|
46
|
+
import assert from "node:assert/strict";
|
|
47
|
+
import { Mind } from "../dist/src/index.js";
|
|
48
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
49
|
+
import { windowIndex, witness } from "../dist/src/mind/evidence.js";
|
|
50
|
+
|
|
51
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
52
|
+
|
|
53
|
+
/** Deposit triples as the trainer does: the relation fact under both the
|
|
54
|
+
* subject-relation key and the bare subject (wiki2.ts). */
|
|
55
|
+
function deposits(triples) {
|
|
56
|
+
const items = [];
|
|
57
|
+
for (const [s, r, o] of triples) {
|
|
58
|
+
const fact = `The ${r} of ${s} is ${o}.`;
|
|
59
|
+
items.push([`${s} ${r}`, fact], [s, fact]);
|
|
60
|
+
}
|
|
61
|
+
return items;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const TRIPLES = [
|
|
65
|
+
// Distractors deposited FIRST: every relation the questions name has older,
|
|
66
|
+
// unrelated instances, so neither first-insertion nor popularity can land on
|
|
67
|
+
// the asked fact by accident.
|
|
68
|
+
["Marius", "place of death", "Marseille"],
|
|
69
|
+
["Jinpa", "director", "Pema Tseden"],
|
|
70
|
+
["Kalippava", "place of death", "Kochi"],
|
|
71
|
+
["Le Guignolo", "director", "Georges Lautner"],
|
|
72
|
+
// Frederick II: the citizenship fact first and repeated, so popularity and
|
|
73
|
+
// first-insertion both pick it.
|
|
74
|
+
["Frederick II", "country of citizenship", "Holy Roman Empire"],
|
|
75
|
+
["Frederick II", "place of death", "Wartburg"],
|
|
76
|
+
["Frederick II", "father", "Peter III of Aragon"],
|
|
77
|
+
["Peter III of Aragon", "father", "James I of Aragon"],
|
|
78
|
+
["Peter III of Aragon", "place of birth", "Valencia"],
|
|
79
|
+
// A two-hop chain.
|
|
80
|
+
["Beat Girl", "director", "Edmond T. Gréville"],
|
|
81
|
+
["Edmond T. Gréville", "place of death", "Nice"],
|
|
82
|
+
["Edmond T. Gréville", "place of birth", "Nice"],
|
|
83
|
+
["Polish-Russian War", "director", "Xawery Żuławski"],
|
|
84
|
+
["Xawery Żuławski", "mother", "Małgorzata Braunek"],
|
|
85
|
+
["Juan Carlos Gumucio", "spouse", "Marie Colvin"],
|
|
86
|
+
["Marie Colvin", "place of death", "Homs"],
|
|
87
|
+
["Ronnie Rocket", "director", "David Lynch"],
|
|
88
|
+
["Who...", "performer", "Ayumi Hamasaki"],
|
|
89
|
+
["Ayumi Hamasaki", "place of birth", "Fukuoka"],
|
|
90
|
+
["David Lynch", "place of birth", "Missoula, Montana"],
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
/** Filler facts in the same template, lexically varied (the test/99 recipe):
|
|
94
|
+
* without them the store is too small for any window to be SCAFFOLDING, and
|
|
95
|
+
* ` is `, `The `, ` of ` would read as owed question material — a regime the
|
|
96
|
+
* trained store never is in. */
|
|
97
|
+
const WORDS =
|
|
98
|
+
("alpha bravo charlie delta echo foxtrot golf hotel india juliet " +
|
|
99
|
+
"kilo lima mike november oscar papa quebec romeo sierra tango uniform " +
|
|
100
|
+
"victor whiskey xray yankee zulu amber bronze copper dahlia ember fjord " +
|
|
101
|
+
"gossamer harbour indigo jasmine kestrel lantern marigold nectar opal")
|
|
102
|
+
.split(" ");
|
|
103
|
+
const RELATIONS = ["award received", "employer", "director", "performer"];
|
|
104
|
+
function fillers(n) {
|
|
105
|
+
const out = [];
|
|
106
|
+
for (let i = 0; i < n; i++) {
|
|
107
|
+
const w = (k) => WORDS[(i * 7 + k * 13) % WORDS.length];
|
|
108
|
+
out.push([
|
|
109
|
+
`${w(0)} ${w(1)}`,
|
|
110
|
+
RELATIONS[i % RELATIONS.length],
|
|
111
|
+
`${w(2)} ${w(3)}`,
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function fixture() {
|
|
118
|
+
const mind = new Mind({
|
|
119
|
+
seed: 7,
|
|
120
|
+
profile: true,
|
|
121
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
122
|
+
});
|
|
123
|
+
await mind.ingest(deposits(fillers(120)));
|
|
124
|
+
await mind.ingest(deposits(TRIPLES));
|
|
125
|
+
// Pour the citizenship fact again, so the distributional ladder prefers it.
|
|
126
|
+
await mind.ingest([[
|
|
127
|
+
"Frederick II",
|
|
128
|
+
"The country of citizenship of Frederick II is Holy Roman Empire.",
|
|
129
|
+
]]);
|
|
130
|
+
return mind;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
test("154.1 witnessing is order-free, complete or nothing, and credits the last holder", () => {
|
|
134
|
+
const W = 4;
|
|
135
|
+
const question = enc("What is the country of citizenship of Frederick II?");
|
|
136
|
+
const node = enc("Frederick II");
|
|
137
|
+
const qi = windowIndex(question, W);
|
|
138
|
+
const ni = windowIndex(node, W);
|
|
139
|
+
// Reordered pieces are witnessed; the question is credited only with what
|
|
140
|
+
// the node does not hold.
|
|
141
|
+
const w = witness(enc("Frederick II country of citizenship"), [qi, ni], W);
|
|
142
|
+
assert.equal(w.complete, true);
|
|
143
|
+
const said = w.spans.map(([s, e]) =>
|
|
144
|
+
new TextDecoder().decode(question.subarray(s, e))
|
|
145
|
+
);
|
|
146
|
+
assert.deepEqual(said, [" country of citizenship"]);
|
|
147
|
+
// One byte no source holds refuses the whole form.
|
|
148
|
+
assert.equal(
|
|
149
|
+
witness(enc("Frederick II country of citizenshop"), [qi, ni], W).complete,
|
|
150
|
+
false,
|
|
151
|
+
);
|
|
152
|
+
// Below one window nothing is witnessed.
|
|
153
|
+
assert.equal(witness(enc("Fre"), [qi, ni], W).complete, false);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/** The answer plus the rationale steps the assertions read. */
|
|
157
|
+
async function ask(mind, question) {
|
|
158
|
+
const steps = [];
|
|
159
|
+
const answer = await mind.respondText(question, (st) => {
|
|
160
|
+
const name = st.mechanism.at(-1);
|
|
161
|
+
if (
|
|
162
|
+
name === "recallByResonance" || name === "askedContinuation" ||
|
|
163
|
+
name === "pivotStep" || name === "liftAnswer"
|
|
164
|
+
) {
|
|
165
|
+
steps.push({
|
|
166
|
+
name,
|
|
167
|
+
note: st.note ?? "",
|
|
168
|
+
inputs: st.inputs.map((o) => o.text ?? ""),
|
|
169
|
+
outputs: st.outputs.map((o) => o.text ?? ""),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
return { answer, steps };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const FATHER = "The father of Frederick II is Peter III of Aragon.";
|
|
177
|
+
const DEATH = "The place of death of Edmond T. Gréville is Nice.";
|
|
178
|
+
|
|
179
|
+
test("154.2 the continuation the question names wins over the most-poured one", async () => {
|
|
180
|
+
const mind = await fixture();
|
|
181
|
+
const { answer, steps } = await ask(
|
|
182
|
+
mind,
|
|
183
|
+
"Who is the father of Frederick II?",
|
|
184
|
+
);
|
|
185
|
+
// The projection that grounds on the argument `Frederick II` crosses the
|
|
186
|
+
// father fact — the one whose own question `Frederick II father` the asker's
|
|
187
|
+
// bytes witness — not the citizenship fact popularity prefers.
|
|
188
|
+
const bound = steps.find((s) =>
|
|
189
|
+
s.name === "recallByResonance" && /argument binding/.test(s.note)
|
|
190
|
+
);
|
|
191
|
+
assert.ok(bound, "the argument binding must ground");
|
|
192
|
+
assert.equal(bound.outputs[0], FATHER);
|
|
193
|
+
const named = steps.find((s) =>
|
|
194
|
+
s.name === "askedContinuation" && s.inputs[1] === "Frederick II father"
|
|
195
|
+
);
|
|
196
|
+
assert.ok(named, "the rationale names the witnessed question");
|
|
197
|
+
assert.deepEqual(named.outputs, [FATHER]);
|
|
198
|
+
assert.match(answer, /The father of Frederick II is Peter III of Aragon\./);
|
|
199
|
+
await mind.store.close();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("154.3 the second hop is named by the question plus the entity the first hop introduced", async () => {
|
|
203
|
+
const mind = await fixture();
|
|
204
|
+
const { answer, steps } = await ask(
|
|
205
|
+
mind,
|
|
206
|
+
"Where was the place of death of the director of film Beat Girl?",
|
|
207
|
+
);
|
|
208
|
+
const pivot = steps.find((s) => s.name === "pivotStep");
|
|
209
|
+
assert.ok(pivot, "the chain must step past the first hop");
|
|
210
|
+
assert.equal(pivot.inputs[1], "Edmond T. Gréville");
|
|
211
|
+
assert.equal(pivot.outputs[0], DEATH);
|
|
212
|
+
// …through the question neither source holds alone.
|
|
213
|
+
assert.ok(
|
|
214
|
+
steps.some((s) =>
|
|
215
|
+
s.name === "askedContinuation" &&
|
|
216
|
+
s.inputs[1] === "Edmond T. Gréville place of death" &&
|
|
217
|
+
s.outputs.includes(DEATH)
|
|
218
|
+
),
|
|
219
|
+
"the hop is named by `Edmond T. Gréville place of death`",
|
|
220
|
+
);
|
|
221
|
+
assert.match(answer, /The place of death of Edmond T\. Gréville is Nice\./);
|
|
222
|
+
// The same chain whatever grounded the first hop: a CAST substitution, which
|
|
223
|
+
// declares the anchor it voices — whose continuation IS the answer, voiced and
|
|
224
|
+
// not withheld — and a cover, which declares nothing, so what it spoke for is
|
|
225
|
+
// what of the answer the question already holds, never the entity it added.
|
|
226
|
+
for (
|
|
227
|
+
const [question, fact] of [
|
|
228
|
+
[
|
|
229
|
+
"What is the place of birth of the performer of song Who...?",
|
|
230
|
+
"The place of birth of Ayumi Hamasaki is Fukuoka.",
|
|
231
|
+
],
|
|
232
|
+
[
|
|
233
|
+
"Where was the place of death of Juan Carlos Gumucio's wife?",
|
|
234
|
+
"The place of death of Marie Colvin is Homs.",
|
|
235
|
+
],
|
|
236
|
+
]
|
|
237
|
+
) {
|
|
238
|
+
const chained = await ask(mind, question);
|
|
239
|
+
assert.equal(chained.answer, fact, question);
|
|
240
|
+
}
|
|
241
|
+
await mind.store.close();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("154.4 a question that names no further step is not extended", async () => {
|
|
245
|
+
const mind = await fixture();
|
|
246
|
+
const { answer, steps } = await ask(
|
|
247
|
+
mind,
|
|
248
|
+
"Who is the father of Frederick II?",
|
|
249
|
+
);
|
|
250
|
+
// `Peter III of Aragon father` is a stored question too — but `father` was
|
|
251
|
+
// said by the hop that reached Peter III, so nothing names the grandfather.
|
|
252
|
+
assert.equal(
|
|
253
|
+
steps.filter((s) => s.name === "pivotStep").length,
|
|
254
|
+
0,
|
|
255
|
+
"no further step is taken",
|
|
256
|
+
);
|
|
257
|
+
assert.doesNotMatch(answer, /James I/);
|
|
258
|
+
assert.doesNotMatch(answer, /Valencia/);
|
|
259
|
+
// …and a one-hop question whose answer introduces an entity with facts of
|
|
260
|
+
// its own stops at that answer: nothing the asker still owes is discriminative
|
|
261
|
+
// (`Who is the` is frame), so no step can claim to pay it by restating ` is `.
|
|
262
|
+
// The material that named a hop names no second one, however the question
|
|
263
|
+
// is phrased: `Who was Frederick II's father?` says `father` once.
|
|
264
|
+
const again = await ask(mind, "Who was Frederick II's father?");
|
|
265
|
+
assert.equal(again.answer, FATHER);
|
|
266
|
+
const one = await ask(mind, "Who is the director of Beat Girl?");
|
|
267
|
+
assert.equal(one.answer, "The director of Beat Girl is Edmond T. Gréville.");
|
|
268
|
+
assert.equal(one.steps.filter((s) => s.name === "pivotStep").length, 0);
|
|
269
|
+
await mind.store.close();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("154.5 a fragment answers other questions unless the question names one", async () => {
|
|
273
|
+
const mind = await fixture();
|
|
274
|
+
const { steps } = await ask(
|
|
275
|
+
mind,
|
|
276
|
+
"Where did the director of film Beat Girl die?",
|
|
277
|
+
);
|
|
278
|
+
// The cover's own composition: `director` sits inside every `… director`
|
|
279
|
+
// question, so its continuations are the directors of OTHER films — voicing
|
|
280
|
+
// one of them by popularity glued a stranger's fact onto the answer.
|
|
281
|
+
const composed = steps.filter((s) => s.name === "liftAnswer").map((s) =>
|
|
282
|
+
s.outputs[0]
|
|
283
|
+
);
|
|
284
|
+
assert.ok(composed.length > 0, "the cover composed");
|
|
285
|
+
for (const c of composed) {
|
|
286
|
+
assert.equal(c, "The director of Beat Girl is Edmond T. Gréville.");
|
|
287
|
+
}
|
|
288
|
+
assert.ok(mind.lastCost.counters.unaskedFragments > 0);
|
|
289
|
+
await mind.store.close();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("154.6 recall's argument binding does not bind a fragment the question names nothing of", async () => {
|
|
293
|
+
const mind = await fixture();
|
|
294
|
+
// Subjects ending in a parenthesis (2Wiki's own titles) let the fold intern
|
|
295
|
+
// `director` as a node of its own, which inherits every director fact.
|
|
296
|
+
await mind.ingest(deposits([
|
|
297
|
+
["Vasantha Sena (1967 film)", "director", "B. S. Ranga"],
|
|
298
|
+
["Guilty of Treason (film)", "director", "Felix E. Feist"],
|
|
299
|
+
]));
|
|
300
|
+
const steps = [];
|
|
301
|
+
const answer = await mind.respondText(
|
|
302
|
+
"When was the director of film Jinpa born?",
|
|
303
|
+
(st) => {
|
|
304
|
+
if (st.mechanism.includes("recallByResonance")) {
|
|
305
|
+
steps.push({
|
|
306
|
+
name: st.mechanism.at(-1),
|
|
307
|
+
outputs: st.outputs.map((o) => o.text ?? ""),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
);
|
|
312
|
+
// `director` — the only constituent long enough to bind — leads to every
|
|
313
|
+
// director fact, and the binding voiced the most-poured stranger's.
|
|
314
|
+
assert.ok(
|
|
315
|
+
steps.some((s) => s.name === "unaskedFragments"),
|
|
316
|
+
"the binding is refused",
|
|
317
|
+
);
|
|
318
|
+
for (const s of steps.filter((s) => s.name === "recallByResonance")) {
|
|
319
|
+
for (const o of s.outputs) assert.doesNotMatch(o, /Vasantha|Guilty/);
|
|
320
|
+
}
|
|
321
|
+
assert.equal(answer, "The director of Jinpa is Pema Tseden.");
|
|
322
|
+
await mind.store.close();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("154.7 a comparison needs the question to name two things", async () => {
|
|
326
|
+
const mind = await fixture();
|
|
327
|
+
const notes = [];
|
|
328
|
+
const answer = await mind.respondText(
|
|
329
|
+
"Tell me the father of Frederick II.",
|
|
330
|
+
(st) => {
|
|
331
|
+
if (st.mechanism.at(-1) === "validateAnalogy") notes.push(st.note ?? "");
|
|
332
|
+
},
|
|
333
|
+
);
|
|
334
|
+
// `Peter III of Aragon father` is reached through ` father` (the dominant
|
|
335
|
+
// `Frederick II father` holds it) and ` of ` (scaffolding): one structure
|
|
336
|
+
// named, so nothing is compared.
|
|
337
|
+
assert.ok(
|
|
338
|
+
notes.some((n) => /names one structure, not two/.test(n)),
|
|
339
|
+
"the comparison is refused",
|
|
340
|
+
);
|
|
341
|
+
assert.equal(answer, FATHER);
|
|
342
|
+
await mind.store.close();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("154.8 an argument held only under the response's equivalence still binds", async () => {
|
|
346
|
+
const mind = await fixture();
|
|
347
|
+
await mind.ingest(deposits([
|
|
348
|
+
["Man at Bath", "director", "Christophe Honoré"],
|
|
349
|
+
["At the End of the Tunnel", "director", "Rodrigo Grande"],
|
|
350
|
+
]));
|
|
351
|
+
// The trainer builds the canonical-form index after training (progress.ts).
|
|
352
|
+
await mind.buildCanonIndex();
|
|
353
|
+
// 2Wiki title-cases its questions; the title sits inside the question,
|
|
354
|
+
// where no fold boundary of the query lines up with the stored form.
|
|
355
|
+
assert.equal(
|
|
356
|
+
await mind.respondText(
|
|
357
|
+
"Where does the director of film Man At Bath work at?",
|
|
358
|
+
),
|
|
359
|
+
"The director of Man at Bath is Christophe Honoré.",
|
|
360
|
+
);
|
|
361
|
+
assert.equal(
|
|
362
|
+
await mind.respondText(
|
|
363
|
+
"Where did the director of film At The End Of The Tunnel work?",
|
|
364
|
+
),
|
|
365
|
+
"The director of At the End of the Tunnel is Rodrigo Grande.",
|
|
366
|
+
);
|
|
367
|
+
await mind.store.close();
|
|
368
|
+
});
|