@hviana/sema 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/AGENTS.md +470 -0
  2. package/AUTHORS.md +12 -0
  3. package/COMMERCIAL-LICENSE.md +19 -0
  4. package/CONTRIBUTING.md +15 -0
  5. package/HOW_IT_WORKS.md +4210 -0
  6. package/LICENSE.md +117 -0
  7. package/README.md +290 -0
  8. package/TRADEMARKS.md +19 -0
  9. package/example/demo.ts +46 -0
  10. package/example/train_base.ts +2675 -0
  11. package/index.html +893 -0
  12. package/package.json +25 -0
  13. package/src/alphabet.ts +34 -0
  14. package/src/alu/README.md +332 -0
  15. package/src/alu/src/alu.ts +541 -0
  16. package/src/alu/src/expr.ts +339 -0
  17. package/src/alu/src/index.ts +115 -0
  18. package/src/alu/src/kernel-arith.ts +377 -0
  19. package/src/alu/src/kernel-bits.ts +199 -0
  20. package/src/alu/src/kernel-logic.ts +102 -0
  21. package/src/alu/src/kernel-nd.ts +235 -0
  22. package/src/alu/src/kernel-numeric.ts +466 -0
  23. package/src/alu/src/operation.ts +344 -0
  24. package/src/alu/src/parser.ts +574 -0
  25. package/src/alu/src/resonance.ts +161 -0
  26. package/src/alu/src/text.ts +83 -0
  27. package/src/alu/src/value.ts +327 -0
  28. package/src/alu/test/alu.test.ts +1004 -0
  29. package/src/bytes.ts +62 -0
  30. package/src/config.ts +227 -0
  31. package/src/derive/README.md +295 -0
  32. package/src/derive/src/deduction.ts +263 -0
  33. package/src/derive/src/index.ts +24 -0
  34. package/src/derive/src/priority-queue.ts +70 -0
  35. package/src/derive/src/rewrite.ts +127 -0
  36. package/src/derive/src/trie.ts +242 -0
  37. package/src/derive/test/derive.test.ts +137 -0
  38. package/src/extension.ts +42 -0
  39. package/src/geometry.ts +511 -0
  40. package/src/index.ts +38 -0
  41. package/src/ingest-cache.ts +224 -0
  42. package/src/mind/articulation.ts +137 -0
  43. package/src/mind/attention.ts +1061 -0
  44. package/src/mind/canonical.ts +107 -0
  45. package/src/mind/graph-search.ts +1100 -0
  46. package/src/mind/index.ts +18 -0
  47. package/src/mind/junction.ts +347 -0
  48. package/src/mind/learning.ts +246 -0
  49. package/src/mind/match.ts +507 -0
  50. package/src/mind/mechanisms/alu.ts +33 -0
  51. package/src/mind/mechanisms/cast.ts +568 -0
  52. package/src/mind/mechanisms/confluence.ts +268 -0
  53. package/src/mind/mechanisms/cover.ts +248 -0
  54. package/src/mind/mechanisms/extraction.ts +422 -0
  55. package/src/mind/mechanisms/recall.ts +241 -0
  56. package/src/mind/mind.ts +483 -0
  57. package/src/mind/pipeline-mechanism.ts +326 -0
  58. package/src/mind/pipeline.ts +300 -0
  59. package/src/mind/primitives.ts +201 -0
  60. package/src/mind/rationale.ts +275 -0
  61. package/src/mind/reasoning.ts +198 -0
  62. package/src/mind/recognition.ts +234 -0
  63. package/src/mind/resonance.ts +0 -0
  64. package/src/mind/trace.ts +108 -0
  65. package/src/mind/traverse.ts +556 -0
  66. package/src/mind/types.ts +281 -0
  67. package/src/rabitq-hnsw/README.md +303 -0
  68. package/src/rabitq-hnsw/src/database.ts +492 -0
  69. package/src/rabitq-hnsw/src/heap.ts +90 -0
  70. package/src/rabitq-hnsw/src/hnsw.ts +514 -0
  71. package/src/rabitq-hnsw/src/index.ts +15 -0
  72. package/src/rabitq-hnsw/src/prng.ts +39 -0
  73. package/src/rabitq-hnsw/src/rabitq.ts +308 -0
  74. package/src/rabitq-hnsw/src/store.ts +994 -0
  75. package/src/rabitq-hnsw/test/hnsw.test.ts +1213 -0
  76. package/src/store-sqlite.ts +928 -0
  77. package/src/store.ts +2148 -0
  78. package/src/vec.ts +124 -0
  79. package/test/00-extract.test.mjs +151 -0
  80. package/test/01-floor.test.mjs +20 -0
  81. package/test/02-roundtrip.test.mjs +83 -0
  82. package/test/03-recall.test.mjs +98 -0
  83. package/test/04-think.test.mjs +627 -0
  84. package/test/05-concepts.test.mjs +84 -0
  85. package/test/08-storage.test.mjs +948 -0
  86. package/test/09-edges.test.mjs +65 -0
  87. package/test/11-universality.test.mjs +180 -0
  88. package/test/13-conversation.test.mjs +228 -0
  89. package/test/14-scaling.test.mjs +503 -0
  90. package/test/15-decomposition-gap.test.mjs +0 -0
  91. package/test/16-bridge.test.mjs +250 -0
  92. package/test/17-intelligence.test.mjs +240 -0
  93. package/test/18-alu.test.mjs +209 -0
  94. package/test/19-nd.test.mjs +254 -0
  95. package/test/20-stability.test.mjs +489 -0
  96. package/test/21-partial.test.mjs +158 -0
  97. package/test/22-multihop.test.mjs +130 -0
  98. package/test/23-rationale.test.mjs +316 -0
  99. package/test/24-generalization.test.mjs +416 -0
  100. package/test/25-embedding.test.mjs +75 -0
  101. package/test/26-cooperative.test.mjs +89 -0
  102. package/test/27-saturation-drop.test.mjs +637 -0
  103. package/test/28-unknowable.test.mjs +88 -0
  104. package/test/29-counterfactual.test.mjs +476 -0
  105. package/test/30-conflict-resolution.test.mjs +166 -0
  106. package/test/31-audit.test.mjs +288 -0
  107. package/test/32-confluence.test.mjs +173 -0
  108. package/test/33-multi-candidate.test.mjs +222 -0
  109. package/test/34-cross-region.test.mjs +252 -0
  110. package/tsconfig.json +16 -0
@@ -0,0 +1,88 @@
1
+ // 28-unknowable.test.mjs — the reach threshold: silence for an unrelated query.
2
+ //
3
+ // When a query is structurally unrelated to everything in the store — its gist
4
+ // sits below the reach threshold (1 − 1/(2·maxGroup), half a river quantum) —
5
+ // the system returns null rather than fabricate an answer from spurious
6
+ // byte-level overlaps. The validation is applied at every grounding site:
7
+ // consensus-climb anchors and last-resort whole-query hits alike.
8
+ //
9
+ // The assertions describe BEHAVIOUR only.
10
+
11
+ import { test } from "node:test";
12
+ import assert from "node:assert/strict";
13
+ import { Mind } from "../dist/src/index.js";
14
+
15
+ const newMind = () => new Mind({ seed: 7 });
16
+
17
+ // ═══════════════════════════════════════════════════════════════════════
18
+ // Section A — a known query still answers (regression guard)
19
+ // ═══════════════════════════════════════════════════════════════════════
20
+
21
+ test("A1: a deposited fact is still recalled", async () => {
22
+ const m = newMind();
23
+ await m.ingest([
24
+ ["what is ice?", "ice is frozen water"],
25
+ ["what is fire?", "fire is hot plasma"],
26
+ ["what is steam?", "steam is vaporised water"],
27
+ ]);
28
+ assert.equal(await m.respondText("what is ice?"), "ice is frozen water");
29
+ await m.store.close();
30
+ });
31
+
32
+ test("A2: a paraphrased question still grounds", async () => {
33
+ const m = newMind();
34
+ await m.ingest([
35
+ ["the capital of France is Paris", "Paris is the largest city in France"],
36
+ ]);
37
+ const r = await m.respond("what is the capital of France?");
38
+ assert.notEqual(r.v, null);
39
+ await m.store.close();
40
+ });
41
+
42
+ // ═══════════════════════════════════════════════════════════════════════
43
+ // Section B — an unrelated query returns null (the reach threshold)
44
+ // ═══════════════════════════════════════════════════════════════════════
45
+
46
+ test("B1: animal facts are silent on quantum physics", async () => {
47
+ const m = newMind();
48
+ await m.ingest([
49
+ ["what is a cat?", "a cat is a small feline"],
50
+ ["what is a dog?", "a dog is a loyal canine"],
51
+ ["what do cats eat?", "cats eat meat and fish"],
52
+ ]);
53
+ const r = await m.respond("explain quantum chromodynamics");
54
+ assert.equal(r.v, null);
55
+ assert.equal(r.bytes.length, 0);
56
+ await m.store.close();
57
+ });
58
+
59
+ test("B2: animal facts are silent on corporate finance", async () => {
60
+ const m = newMind();
61
+ await m.ingest([
62
+ ["what is a cat?", "a cat is a small feline"],
63
+ ["what is a dog?", "a dog is a loyal canine"],
64
+ ["what do cats eat?", "cats eat meat and fish"],
65
+ ]);
66
+ const r = await m.respond(
67
+ "describe the WACC calculation for cross-border M&A",
68
+ );
69
+ assert.equal(r.v, null);
70
+ await m.store.close();
71
+ });
72
+
73
+ test("B3: determinism — same unrelated query, same silence, across runs", async () => {
74
+ const run = async () => {
75
+ const m = newMind();
76
+ await m.ingest([
77
+ ["roses are red", "violets are blue"],
78
+ ["sugar is sweet", "and so are you"],
79
+ ]);
80
+ const r = await m.respond(
81
+ "detailed analysis of relativistic time dilation in binary pulsar systems",
82
+ );
83
+ await m.store.close();
84
+ return r.bytes.length;
85
+ };
86
+ assert.equal(await run(), 0);
87
+ assert.equal(await run(), 0);
88
+ });
@@ -0,0 +1,476 @@
1
+ // 29-counterfactual.test.mjs — COUNTERFACTUAL ANALYSIS AND STRUCTURAL ANALOGY.
2
+ //
3
+ // Counterfactual reasoning asks "what if X were different?" — substitute one
4
+ // element of a known structure for an analog and trace what changes. It composes
5
+ // three capabilities the reasoner does not yet have:
6
+ //
7
+ // 1. STRUCTURAL ANALOGY — given a form F in context C, find an analog F' that
8
+ // plays the SAME role in a structurally-similar context C'. Halos already
9
+ // capture distributional roles; the missing piece is validating that the
10
+ // CONTEXTS (not just the forms) are structurally alike.
11
+ //
12
+ // 2. COUNTERFACTUAL PROJECTION — given an analog F' and a property P
13
+ // associated with F, project what would follow if F' carried P instead of
14
+ // its own property.
15
+ //
16
+ // 3. ANALOGICAL COMPARISON — given two forms from different domains whose
17
+ // structural roles align, articulate HOW they are alike.
18
+ //
19
+ // Analysis via Structural Transfer) is implemented in mind.ts.
20
+ //
21
+ // The tests use only the existing API: respond / respondText.
22
+
23
+ import { test } from "node:test";
24
+ import assert from "node:assert/strict";
25
+ import { Mind } from "../dist/src/index.js";
26
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
27
+
28
+ const mk = (seed = 7) =>
29
+ new Mind({ seed, store: new SQliteStore({ path: ":memory:" }) });
30
+ const ask = async (m, q) =>
31
+ (await m.respondText(q)).replace(/\s+/g, " ").trim();
32
+
33
+ // ═══════════════════════════════════════════════════════════════════════════
34
+ // Section A — structural analogy: same role, different filler
35
+ // ═══════════════════════════════════════════════════════════════════════════
36
+
37
+ // A1 — "Steel" and "Ice" both play the role "material with a property" in the
38
+ // frame "X is Y". When asked "What if steel were cold?", the system must:
39
+ // 1. Find that "steel" is structurally analogous to "ice" (both appear in
40
+ // "X is Y" frames)
41
+ // 2. Substitute "cold" (ice's property) for "hard" (steel's property)
42
+ // 3. Answer with what steel would be like if it were cold
43
+ //
44
+ // Today: the system cannot find structural analogs. It returns "" (nothing
45
+ // resonates cleanly) or "hard" (steel's own property, ignoring the
46
+ // counterfactual). Neither is correct.
47
+ test("A1 — find structural analog and substitute property", async () => {
48
+ const m = mk();
49
+ await m.ingest([
50
+ ["Ice is cold", "cold"],
51
+ ["Fire is hot", "hot"],
52
+ ["Steel is hard", "hard"],
53
+ ["Water is wet", "wet"],
54
+ ]);
55
+
56
+ const got = await ask(m, "What if steel were cold?");
57
+ // When CAST lands: the answer must reference steel AND a cold-like property
58
+ // (e.g. "steel would be brittle", "steel would feel cold", or similar).
59
+ // Today: returns "" or "hard" — the counterfactual substitution is absent.
60
+ assert.ok(
61
+ /steel/i.test(got) && /cold|brittle|chill/i.test(got),
62
+ `CAST not yet implemented — expected steel+cold, got "${got}"`,
63
+ );
64
+ await m.store.close();
65
+ });
66
+
67
+ // A2 — "Leonardo da Vinci" and "Michelangelo" are both painters. When asked
68
+ // "who is the Michelangelo of literature?", the system must find that
69
+ // Michelangelo plays the role "creator in domain D" and find the analog in
70
+ // the literature domain (Shakespeare, Homer, etc.).
71
+ //
72
+ // Enough exemplars on both sides of the analogy give the halos mass to
73
+ // detect the shared structural role ("ARTWORK was VERBED by ARTIST")
74
+ // across domains — painters, sculptors, and writers all occupy the same
75
+ // seat, so their distributional signatures converge.
76
+ test("A2 — cross-domain analog via shared structural role", async () => {
77
+ const m = mk();
78
+ await m.ingest([
79
+ // painting exemplars
80
+ ["The Mona Lisa was painted by Leonardo da Vinci.", "Leonardo da Vinci"],
81
+ ["The Starry Night was painted by Vincent van Gogh.", "Vincent van Gogh"],
82
+ [
83
+ "The Night Watch was painted by Rembrandt van Rijn.",
84
+ "Rembrandt van Rijn",
85
+ ],
86
+ // sculpture exemplars
87
+ ["The David was sculpted by Michelangelo.", "Michelangelo"],
88
+ ["The Thinker was sculpted by Auguste Rodin.", "Auguste Rodin"],
89
+ // writing exemplars
90
+ ["Hamlet was written by William Shakespeare.", "William Shakespeare"],
91
+ ["The Odyssey was written by Homer.", "Homer"],
92
+ ["Macbeth was written by William Shakespeare.", "William Shakespeare"],
93
+ ["The Iliad was written by Homer.", "Homer"],
94
+ // domain facts — each artist grounded in their field
95
+ ["Leonardo da Vinci", "Leonardo was a Renaissance painter"],
96
+ ["Michelangelo", "Michelangelo was a sculptor and painter"],
97
+ ["William Shakespeare", "Shakespeare was an English playwright"],
98
+ ["Homer", "Homer was an ancient Greek poet"],
99
+ ]);
100
+
101
+ const got = await ask(
102
+ m,
103
+ "Michelangelo is to sculpture as who is to literature?",
104
+ );
105
+ // When CAST lands: the answer should name a writer (Shakespeare or Homer),
106
+ // not a painter and not empty.
107
+ assert.ok(
108
+ /Shakespeare|Homer|writer|literature/i.test(got),
109
+ `CAST not yet implemented — expected a writer analog, got "${got}"`,
110
+ );
111
+ await m.store.close();
112
+ });
113
+
114
+ // ═══════════════════════════════════════════════════════════════════════════
115
+ // Section B — counterfactual projection: what changes under substitution?
116
+ // ═══════════════════════════════════════════════════════════════════════════
117
+
118
+ // B1 — The graph knows:
119
+ // • Ice → cold, Fire → hot, Steel → hard (properties)
120
+ // • "If ice were hot, it would melt" (a counterfactual relation)
121
+ // Query: "What if steel were hot?"
122
+ // Correct: steel is analog to ice (both materials), hot substitutes for hard,
123
+ // the counterfactual relation projects: steel would melt.
124
+ //
125
+ // Today: the system cannot do this. It returns "" or "hard" or "melt" (the
126
+ // last by accident — whole-query resonance on "were hot" → the ice fact,
127
+ // ignoring steel).
128
+ test("B1 — project counterfactual outcome through analog substitution", async () => {
129
+ const m = mk();
130
+ await m.ingest([
131
+ ["Ice is cold", "cold"],
132
+ ["Fire is hot", "hot"],
133
+ ["Steel is hard", "hard"],
134
+ ["If ice were hot, it would melt", "melt"],
135
+ ]);
136
+
137
+ const got = await ask(m, "What if steel were hot?");
138
+ // When CAST lands: the answer must mention steel AND the projected outcome
139
+ // (melt, soften, etc.).
140
+ assert.ok(
141
+ /steel/i.test(got) && /melt|soften|liquid/i.test(got),
142
+ `CAST not yet implemented — expected steel+melt, got "${got}"`,
143
+ );
144
+ await m.store.close();
145
+ });
146
+
147
+ // B2 — Counterfactual in a multi-hop chain. The graph knows:
148
+ // • Extraction skill: "X was painted by Y → Y" (3 exemplars)
149
+ // • Picasso → co-founded Cubism (downstream fact)
150
+ // • Michelangelo → painted Sistine Chapel (downstream fact for the
151
+ // counterfactual substitute)
152
+ // Baseline (works today): "The Weeping Woman was painted by Pablo Picasso."
153
+ // → "Pablo Picasso co-founded the Cubist movement"
154
+ // Counterfactual: "What if The Weeping Woman had been painted by
155
+ // Michelangelo?" — must substitute Michelangelo for Picasso and return
156
+ // Michelangelo's fact, not Picasso's.
157
+ //
158
+ // Today: the counterfactual query either returns Picasso's fact (no
159
+ // substitution), or a jumble of exemplar names + Michelangelo's fact
160
+ // (the climb finds the exemplars but cannot redirect the hop).
161
+ test("B2 — counterfactual substitution redirects the downstream hop", async () => {
162
+ const m = mk();
163
+ await m.ingest([
164
+ ["The Mona Lisa was painted by Leonardo da Vinci.", "Leonardo da Vinci"],
165
+ ["The Starry Night was painted by Vincent van Gogh.", "Vincent van Gogh"],
166
+ [
167
+ "The Night Watch was painted by Rembrandt van Rijn.",
168
+ "Rembrandt van Rijn",
169
+ ],
170
+ ["Pablo Picasso", "Pablo Picasso co-founded the Cubist movement"],
171
+ ["Michelangelo", "Michelangelo painted the Sistine Chapel ceiling"],
172
+ ]);
173
+
174
+ // Baseline must work first (extraction + hop, same as the demo).
175
+ const baseline = await ask(
176
+ m,
177
+ "The Weeping Woman was painted by Pablo Picasso.",
178
+ );
179
+ assert.ok(
180
+ baseline.includes("Cubist"),
181
+ `baseline extraction+hop failed — got "${baseline}"`,
182
+ );
183
+
184
+ // Counterfactual: substitute Michelangelo for Picasso.
185
+ const got = await ask(
186
+ m,
187
+ "What if The Weeping Woman had been painted by Michelangelo?",
188
+ );
189
+ // When CAST lands: must carry Michelangelo's fact AND NOT Picasso's fact
190
+ // AND NOT the exemplar painters' names. Clean substitution.
191
+ assert.ok(
192
+ /Sistine/i.test(got) &&
193
+ !/Cubist/i.test(got) &&
194
+ !/Leonardo|van Gogh|Rembrandt/i.test(got),
195
+ `CAST not yet implemented — expected clean Michelangelo substitution, got "${got}"`,
196
+ );
197
+ await m.store.close();
198
+ });
199
+
200
+ // B3 — The system must DISTINGUISH "What is X?" from "What if X were Y?".
201
+ // The counterfactual framing changes the answer. When the graph knows facts
202
+ // about both the original and the substitute, the counterfactual must follow
203
+ // the substitute, not the original.
204
+ //
205
+ // Today: "What if the capital of France were Lyon?" returns the Paris fact
206
+ // (or both Paris and Rome facts jumbled together). The word "if" is just
207
+ // bytes; no mechanism interprets it as a substitution directive.
208
+ test("B3 — counterfactual framing is distinguished from factual recall", async () => {
209
+ const m = mk();
210
+ await m.ingest([
211
+ ["what is the capital of France?", "Paris is the capital of France"],
212
+ ["what is the capital of Italy?", "Rome is the capital of Italy"],
213
+ ["Lyon is a city in France", "Lyon is known for its cuisine"],
214
+ ]);
215
+
216
+ // Factual baseline.
217
+ assert.ok(
218
+ (await ask(m, "what is the capital of France?")).includes("Paris"),
219
+ );
220
+
221
+ // Counterfactual: substitute Lyon for Paris.
222
+ const got = await ask(m, "what if the capital of France were Lyon?");
223
+ // When CAST lands: must talk about LYON (the substitute), not Paris.
224
+ assert.ok(
225
+ /Lyon/i.test(got) && !/Paris/i.test(got),
226
+ `CAST not yet implemented — expected Lyon (not Paris), got "${got}"`,
227
+ );
228
+ await m.store.close();
229
+ });
230
+
231
+ // ═══════════════════════════════════════════════════════════════════════════
232
+ // Section C — analogical comparison: "how is X like Y?"
233
+ // ═══════════════════════════════════════════════════════════════════════════
234
+
235
+ // C1 — Comparing two forms across domains by their shared structural role.
236
+ // "How is ice like steel?" — both are materials that appear in "X is Y"
237
+ // frames. The answer must identify the shared structure, not just list
238
+ // individual properties and not just echo the query words.
239
+ //
240
+ // Today: returns "ice like steel?" (an echo) or "" or "cold"/"hard"
241
+ // individually. No comparison.
242
+ test("C1 — analogical comparison identifies shared structure", async () => {
243
+ const m = mk();
244
+ await m.ingest([
245
+ ["Ice is cold", "cold"],
246
+ ["Steel is hard", "hard"],
247
+ ["Fire is hot", "hot"],
248
+ ]);
249
+
250
+ const got = await ask(m, "How is ice like steel?");
251
+ // When CAST lands: must say something about WHY they are alike — both ARE
252
+ // something (share the "X is Y" structure). Must not be empty, must not
253
+ // just echo the query, must not be just a single property.
254
+ assert.ok(
255
+ got.length > 0 &&
256
+ got !== "ice like steel" &&
257
+ got !== "ice like steel?" &&
258
+ !/^(cold|hard|hot)$/i.test(got),
259
+ `CAST not yet implemented — expected a comparison, got "${got}"`,
260
+ );
261
+ await m.store.close();
262
+ });
263
+
264
+ // C2 — Cross-domain comparison. "How is Shakespeare like Leonardo da Vinci?"
265
+ // Both are creators in their domains (writer, painter). The answer must
266
+ // identify the shared ROLE without confusing the domains or listing
267
+ // individual biographies.
268
+ //
269
+ // Enough exemplars on both sides give the halos mass to detect the shared
270
+ // creator role across painting, sculpture, and writing.
271
+ test("C2 — cross-domain comparison identifies shared role", async () => {
272
+ const m = mk();
273
+ await m.ingest([
274
+ // painting exemplars
275
+ ["The Mona Lisa was painted by Leonardo da Vinci.", "Leonardo da Vinci"],
276
+ ["The Starry Night was painted by Vincent van Gogh.", "Vincent van Gogh"],
277
+ [
278
+ "The Night Watch was painted by Rembrandt van Rijn.",
279
+ "Rembrandt van Rijn",
280
+ ],
281
+ ["The Last Supper was painted by Leonardo da Vinci.", "Leonardo da Vinci"],
282
+ ["The Scream was painted by Edvard Munch.", "Edvard Munch"],
283
+ [
284
+ "The Girl with a Pearl Earring was painted by Johannes Vermeer.",
285
+ "Johannes Vermeer",
286
+ ],
287
+ // sculpture exemplars
288
+ ["The David was sculpted by Michelangelo.", "Michelangelo"],
289
+ ["The Thinker was sculpted by Auguste Rodin.", "Auguste Rodin"],
290
+ ["The Pietà was sculpted by Michelangelo.", "Michelangelo"],
291
+ ["The Gates of Hell were sculpted by Auguste Rodin.", "Auguste Rodin"],
292
+ // writing exemplars
293
+ ["Hamlet was written by William Shakespeare.", "William Shakespeare"],
294
+ ["The Odyssey was written by Homer.", "Homer"],
295
+ ["Macbeth was written by William Shakespeare.", "William Shakespeare"],
296
+ ["The Iliad was written by Homer.", "Homer"],
297
+ ["King Lear was written by William Shakespeare.", "William Shakespeare"],
298
+ [
299
+ "The Hymn to Demeter was written by Homer.",
300
+ "Homer",
301
+ ],
302
+ // domain facts
303
+ ["Leonardo da Vinci", "Leonardo was a Renaissance polymath"],
304
+ ["William Shakespeare", "Shakespeare wrote 39 plays"],
305
+ ["Michelangelo", "Michelangelo was a sculptor and painter"],
306
+ ["Homer", "Homer was an ancient Greek poet"],
307
+ ]);
308
+
309
+ const got = await ask(m, "How is Shakespeare like Leonardo da Vinci?");
310
+ // When CAST lands: must articulate the shared role (both created works in
311
+ // their domains), not just dump one person's biography.
312
+ assert.ok(
313
+ got.length > 0 &&
314
+ !/39 plays/i.test(got) &&
315
+ !/Renaissance polymath/i.test(got),
316
+ `CAST not yet implemented — expected shared-role comparison, got "${got}"`,
317
+ );
318
+ await m.store.close();
319
+ });
320
+
321
+ // C3 — CAST's terminal treatment must not be a blanket rule. A comparison's
322
+ // seat sentence can itself contain a further pivotable term with its OWN
323
+ // downstream fact ("Mona Lisa" inside "The Mona Lisa was painted by Leonardo
324
+ // da Vinci" leads on to "Mona Lisa hangs in the Louvre") — a genuine further
325
+ // hop distinct from Leonardo/Shakespeare's own biographies, which C2 pins
326
+ // must NEVER surface. Proven end-to-end through respond/ask, the real
327
+ // public surface: the composed comparison must carry BOTH the shared-role
328
+ // comparison AND the Louvre fact, and still never leak either analog's own
329
+ // biography.
330
+ test("C3 — a further hop inside a comparison's seat still fires", async () => {
331
+ const m = mk();
332
+ await m.ingest([
333
+ ["The Mona Lisa was painted by Leonardo da Vinci.", "Leonardo da Vinci"],
334
+ ["The Starry Night was painted by Vincent van Gogh.", "Vincent van Gogh"],
335
+ [
336
+ "The Night Watch was painted by Rembrandt van Rijn.",
337
+ "Rembrandt van Rijn",
338
+ ],
339
+ ["The David was sculpted by Michelangelo.", "Michelangelo"],
340
+ ["The Thinker was sculpted by Auguste Rodin.", "Auguste Rodin"],
341
+ ["Hamlet was written by William Shakespeare.", "William Shakespeare"],
342
+ ["The Odyssey was written by Homer.", "Homer"],
343
+ ["Macbeth was written by William Shakespeare.", "William Shakespeare"],
344
+ ["The Iliad was written by Homer.", "Homer"],
345
+ ["Leonardo da Vinci", "Leonardo was a Renaissance polymath"],
346
+ ["William Shakespeare", "Shakespeare wrote 39 plays"],
347
+ // the further hop: a term INSIDE the seat sentence, not the analog itself
348
+ ["Mona Lisa", "Mona Lisa hangs in the Louvre"],
349
+ ]);
350
+
351
+ const got = await ask(m, "How is Shakespeare like Leonardo da Vinci?");
352
+ assert.ok(
353
+ /Louvre/i.test(got),
354
+ `a genuine further hop inside the comparison's seat did not fire — got "${got}"`,
355
+ );
356
+ assert.ok(
357
+ !/39 plays/i.test(got) && !/Renaissance polymath/i.test(got),
358
+ `the analogs' own biographies must stay untouched — got "${got}"`,
359
+ );
360
+ await m.store.close();
361
+ });
362
+
363
+ // C4 — A CAST analog can be referenced INDIRECTLY, by a synonym/concept
364
+ // (halo) link, not just by its own literal node id. "Il Divino" is
365
+ // concept-merged with "Michelangelo" (three shared contexts give the halos
366
+ // mass); Michelangelo's own fact happens to mention the nickname by name.
367
+ // Redirecting to Michelangelo must stay terminal on the SYNONYM too — never
368
+ // hopping from "Il Divino" (inside the substitute's own fact) into "Il
369
+ // Divino"'s unrelated nickname trivia, the same class of leak C2 pins for
370
+ // the literal analog name.
371
+ test("C4 — a synonym of a CAST analog is protected the same as the analog itself", async () => {
372
+ const m = mk();
373
+ await m.ingest([
374
+ ["The Mona Lisa was painted by Leonardo da Vinci.", "Leonardo da Vinci"],
375
+ ["The Starry Night was painted by Vincent van Gogh.", "Vincent van Gogh"],
376
+ [
377
+ "The Night Watch was painted by Rembrandt van Rijn.",
378
+ "Rembrandt van Rijn",
379
+ ],
380
+ ["Pablo Picasso", "Pablo Picasso co-founded the Cubist movement"],
381
+ ["Michelangelo", "Il Divino painted the Sistine Chapel ceiling"],
382
+ // concept-merge "Michelangelo" with "Il Divino" via three shared contexts
383
+ ["a nickname meaning the divine one", "Michelangelo"],
384
+ ["a nickname meaning the divine one", "Il Divino"],
385
+ ["also called", "Michelangelo"],
386
+ ["also called", "Il Divino"],
387
+ ["known as", "Michelangelo"],
388
+ ["known as", "Il Divino"],
389
+ // "Il Divino"'s own further fact — must NOT leak via the synonym link
390
+ ["Il Divino", "Il Divino was a nickname coined by his contemporaries"],
391
+ ]);
392
+
393
+ const baseline = await ask(
394
+ m,
395
+ "The Weeping Woman was painted by Pablo Picasso.",
396
+ );
397
+ assert.ok(
398
+ baseline.includes("Cubist"),
399
+ `baseline extraction+hop failed — got "${baseline}"`,
400
+ );
401
+
402
+ const got = await ask(
403
+ m,
404
+ "What if The Weeping Woman had been painted by Michelangelo?",
405
+ );
406
+ assert.ok(
407
+ /Sistine/i.test(got),
408
+ `the substitution itself must still fire — got "${got}"`,
409
+ );
410
+ assert.ok(
411
+ !/nickname coined by his contemporaries/i.test(got),
412
+ `the analog's SYNONYM must stay unconsumed-but-untouched too — got "${got}"`,
413
+ );
414
+ await m.store.close();
415
+ });
416
+
417
+ // ═══════════════════════════════════════════════════════════════════════════
418
+ // Section D — graded alignment and site-aware consensus climb
419
+ // ═══════════════════════════════════════════════════════════════════════════
420
+ //
421
+ // D1 verifies the SITE-REGIONS fix: the consensus climb now uses recognised
422
+ // sites as additional voting regions, so words crossing W-boundaries (which
423
+ // perceived sub-regions split into non-resonant chunks) can still vote for
424
+ // their containing contexts. Without this fix, the climb sees only W-byte
425
+ // fragments; with it, the full word "frigid" votes for contexts that contain
426
+ // it, enabling CAST to find a weave across diverse anchors.
427
+ //
428
+ // Three template facts share the same structure ("X is Y so X is Z") but
429
+ // differ in the Y-property word. The query uses a Y-word ("frigid") that IS
430
+ // literally in one anchor but NOT in another — the weave forms because the
431
+ // climb now sees anchors from BOTH the subject and predicate domains via the
432
+ // recognised site, and CAST's graded alignment bridges the gap where literal
433
+ // coverage is absent for a given anchor.
434
+
435
+ test("D1 — site-aware climb finds diverse anchors for CAST weave", async () => {
436
+ const m = mk(7);
437
+ await m.ingest([
438
+ ["ice is cold so ice is brittle", "brittle"],
439
+ ["steel is hard so steel is strong", "strong"],
440
+ ["water is frigid so water is freezing", "freezing"],
441
+ ]);
442
+
443
+ // "steel is frigid" — "frigid" literally aligns with water/frigid
444
+ // context but NOT with ice/cold. The site-regions fix lets the climb
445
+ // vote with the full word "frigid" (which perception splits at W=4).
446
+ // CAST substitution fires: the deepest displaced seat wins.
447
+ const r = await m.respond("steel is frigid");
448
+ assert.equal(r.provenance, "cast", `CAST must fire — got ${r.provenance}`);
449
+ const got = await ask(m, "steel is frigid");
450
+ // The substitution transfers "steel" into the displaced property
451
+ // seat. The deepest seat is "frigid" in the water context, whose
452
+ // continuation is "freezing".
453
+ assert.ok(
454
+ /freezing/i.test(got),
455
+ `expected property transfer containing "freezing", got "${got}"`,
456
+ );
457
+ await m.store.close();
458
+ });
459
+
460
+ test("D2 — site-aware climb is seed-independent", async () => {
461
+ for (const seed of [1, 7, 42, 99]) {
462
+ const m = mk(seed);
463
+ await m.ingest([
464
+ ["ice is cold so ice is brittle", "brittle"],
465
+ ["steel is hard so steel is strong", "strong"],
466
+ ["water is frigid so water is freezing", "freezing"],
467
+ ]);
468
+ const r = await m.respond("steel is frigid");
469
+ assert.equal(
470
+ r.provenance,
471
+ "cast",
472
+ `seed ${seed}: CAST must fire — got ${r.provenance}`,
473
+ );
474
+ await m.store.close();
475
+ }
476
+ });