@hviana/sema 0.8.9 → 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.
Files changed (111) hide show
  1. package/AGENTS.md +7 -7
  2. package/dist/src/alu/src/index.d.ts +1 -1
  3. package/dist/src/alu/src/index.js +1 -1
  4. package/dist/src/alu/src/parser.js +2 -6
  5. package/dist/src/alu/src/resonance.d.ts +13 -0
  6. package/dist/src/alu/src/resonance.js +41 -0
  7. package/dist/src/alu/test/alu.test.js +39 -0
  8. package/dist/src/bytes.d.ts +6 -2
  9. package/dist/src/bytes.js +10 -4
  10. package/dist/src/canon.js +44 -0
  11. package/dist/src/geometry.d.ts +19 -1
  12. package/dist/src/geometry.js +125 -141
  13. package/dist/src/meter.d.ts +27 -0
  14. package/dist/src/meter.js +28 -1
  15. package/dist/src/mind/articulation.js +14 -1
  16. package/dist/src/mind/attention.d.ts +12 -0
  17. package/dist/src/mind/attention.js +44 -16
  18. package/dist/src/mind/bridge.js +3 -3
  19. package/dist/src/mind/derivation.d.ts +40 -0
  20. package/dist/src/mind/derivation.js +34 -0
  21. package/dist/src/mind/graph-search.d.ts +89 -15
  22. package/dist/src/mind/graph-search.js +345 -174
  23. package/dist/src/mind/learning.js +1 -1
  24. package/dist/src/mind/mechanisms/cover.d.ts +19 -3
  25. package/dist/src/mind/mechanisms/cover.js +101 -58
  26. package/dist/src/mind/mechanisms/recall.js +0 -1
  27. package/dist/src/mind/mind.js +2 -2
  28. package/dist/src/mind/pipeline.d.ts +5 -1
  29. package/dist/src/mind/pipeline.js +175 -87
  30. package/dist/src/mind/primitives.d.ts +25 -5
  31. package/dist/src/mind/primitives.js +107 -44
  32. package/dist/src/mind/reasoning.d.ts +18 -4
  33. package/dist/src/mind/reasoning.js +445 -321
  34. package/dist/src/mind/recognition.js +55 -73
  35. package/dist/src/mind/resonance.js +1 -11
  36. package/dist/src/mind/traverse.d.ts +3 -3
  37. package/dist/src/mind/traverse.js +3 -3
  38. package/dist/src/mind/types.d.ts +7 -1
  39. package/dist/src/store-sqlite.d.ts +25 -0
  40. package/dist/src/store-sqlite.js +89 -1
  41. package/dist/src/store.d.ts +48 -4
  42. package/dist/src/store.js +86 -6
  43. package/docs/INDEX.md +18 -18
  44. package/docs/INVARIANTS.md +16 -16
  45. package/docs/architecture/bounded-reads.md +1 -1
  46. package/docs/architecture/caches.md +5 -4
  47. package/docs/architecture/closure.md +45 -5
  48. package/docs/architecture/cost-model.md +16 -0
  49. package/docs/architecture/factored-machinery.md +14 -13
  50. package/docs/architecture/fold-contract.md +51 -1
  51. package/docs/architecture/mechanism-market.md +21 -0
  52. package/docs/architecture/memoization.md +3 -3
  53. package/docs/architecture/meter.md +2 -1
  54. package/docs/architecture/saturation.md +12 -0
  55. package/docs/architecture/store.md +25 -2
  56. package/docs/failures/tempting-but-wrong.md +13 -2
  57. package/docs/harness/gates.md +12 -10
  58. package/docs/mechanisms/cover.md +23 -6
  59. package/jsr.json +1 -1
  60. package/package.json +1 -1
  61. package/src/alu/README.md +10 -2
  62. package/src/alu/src/index.ts +1 -0
  63. package/src/alu/src/parser.ts +6 -6
  64. package/src/alu/src/resonance.ts +42 -0
  65. package/src/alu/test/alu.test.ts +40 -0
  66. package/src/bytes.ts +13 -3
  67. package/src/canon.ts +40 -0
  68. package/src/geometry.ts +183 -154
  69. package/src/meter.ts +28 -1
  70. package/src/mind/articulation.ts +14 -2
  71. package/src/mind/attention.ts +47 -25
  72. package/src/mind/bridge.ts +3 -3
  73. package/src/mind/derivation.ts +77 -0
  74. package/src/mind/graph-search.ts +449 -221
  75. package/src/mind/learning.ts +1 -7
  76. package/src/mind/match.ts +1 -2
  77. package/src/mind/mechanisms/cast.ts +1 -2
  78. package/src/mind/mechanisms/cover.ts +149 -84
  79. package/src/mind/mechanisms/extraction.ts +1 -2
  80. package/src/mind/mechanisms/prefix-completion.ts +1 -1
  81. package/src/mind/mechanisms/recall.ts +1 -3
  82. package/src/mind/mechanisms/reference.ts +1 -1
  83. package/src/mind/mind.ts +5 -30
  84. package/src/mind/pipeline.ts +206 -102
  85. package/src/mind/primitives.ts +119 -43
  86. package/src/mind/reasoning.ts +558 -413
  87. package/src/mind/recognition.ts +49 -65
  88. package/src/mind/resonance.ts +2 -16
  89. package/src/mind/trace.ts +1 -1
  90. package/src/mind/traverse.ts +3 -3
  91. package/src/mind/types.ts +9 -11
  92. package/src/store-sqlite.ts +92 -1
  93. package/src/store.ts +113 -7
  94. package/test/105-derive-through-reports-its-refusal.test.mjs +8 -5
  95. package/test/106-the-join-fires.test.mjs +21 -0
  96. package/test/111-the-cover-assembly-is-counted.test.mjs +8 -5
  97. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +18 -12
  98. package/test/136-the-two-named-limits.test.mjs +3 -2
  99. package/test/137-the-law-lives-once-and-below.test.mjs +21 -0
  100. package/test/148-exact-shortcuts-agree.test.mjs +188 -0
  101. package/test/149-the-closure-engine.test.mjs +138 -0
  102. package/test/150-the-join-is-output-sensitive.test.mjs +66 -0
  103. package/test/151-the-cover-pays-for-what-it-reaches.test.mjs +142 -0
  104. package/test/152-the-read-side-names-as-the-write-side.test.mjs +146 -0
  105. package/test/153-a-cheaper-bound-is-looked-at-first.test.mjs +155 -0
  106. package/test/24-generalization.test.mjs +32 -0
  107. package/test/36-bloom.test.mjs +53 -0
  108. package/test/37-cluster-dispersion-fusion.test.mjs +75 -0
  109. package/test/48-recognise-turn-connective.test.mjs +3 -2
  110. package/test/55-cost-meter.test.mjs +4 -4
  111. package/test/90-connector-read-cap.test.mjs +7 -7
@@ -46,15 +46,18 @@ test("the pairwise assembly is counted, with no rationale attached", async () =>
46
46
  });
47
47
 
48
48
  test("the n-ary interior's allowance is counted", async () => {
49
- // Three sites in one query: the n-ary path passes an allowance that grows
50
- // with the intermediate answers' bytes — the quantity that would grow with a
51
- // hub query's answers.
49
+ // Three touching sites in one query, each answered: the n-ary path passes an
50
+ // allowance that grows with the intermediate answers' bytes — the quantity
51
+ // that would grow with a hub query's answers. The connectors are bridged
52
+ // only for the pairs the search REACHES (ConnectorLicence), so the lightest
53
+ // cover must stand on all three answers: a query recognised whole (an
54
+ // `ab cd` learnt as its own context) never reaches its parts' pair.
52
55
  const mind = await fixture([
53
56
  ["ab", "x"],
54
57
  ["cd", "y"],
55
- ["ab cd", "z"],
58
+ ["ef", "z"],
56
59
  ]);
57
- await mind.respondText("ab cd");
60
+ await mind.respondText("abcdef");
58
61
  const c = mind.lastCost?.counters ?? {};
59
62
  assert.ok(
60
63
  (c.coverAllowanceBytes ?? 0) > 0,
@@ -1,17 +1,18 @@
1
1
  // 128-the-leads-somewhere-pair-agrees.test.mjs — THE PAIR, PINNED
2
2
  //
3
- // "Does this node lead somewhere?" is answered in TWO homes, and they cannot
4
- // share a name: `traverse.ts`'s `leadsSomewhere` is `cachedHasNext || hasHalo`,
5
- // and `primitives.ts` spells out `hasNext || hasHalo` because `traverse.ts`
6
- // imports that file (a cycle) — the comment there says so. Two spellings of
7
- // one predicate is a drift risk, so this pins the AGREEMENT, node by node,
8
- // through the observable surface only: `mind.leadsSomewhere(id)` against
9
- // `store.hasNext(id) || store.hasHalo(id)`.
3
+ // "Does this node lead somewhere?" has ONE raw definition — the store's
4
+ // `leadsSomewhere` (`hasNext || hasHalo`) — and ONE memoised reading of it,
5
+ // `traverse.ts`'s `leadsSomewhere` (`cachedHasNext || hasHalo`), which the Mind
6
+ // lends to the search. The callers that used to spell the pair out by hand
7
+ // (`primitives.ts`, the search's bare-host fallback) now ask the store. This
8
+ // pins the AGREEMENT, node by node, through the observable surface only:
9
+ // `mind.leadsSomewhere(id)` against `store.leadsSomewhere(id)`, and both against
10
+ // the two probes the definition is made of.
10
11
  //
11
12
  // It is NOT a claim that the two are the same function: one reads a memo, the
12
13
  // other reads the store. The claim is that the memoised reading and the store
13
- // reading agree — what makes the spelled-out copy correct, and what would break
14
- // first if `cachedHasNext` ever outlived a store write.
14
+ // reading agree — what would break first if `cachedHasNext` ever outlived a
15
+ // store write.
15
16
  //
16
17
  // THE REACH OF THIS PIN, said as it is: halos are poured on the WRITE side, so
17
18
  // a synthetic corpus of this shape carries NONE (measured: 0 nodes with a halo
@@ -49,16 +50,21 @@ test("the two homes of 'leads somewhere' agree, node by node", async () => {
49
50
  let withHalo = 0;
50
51
  for (let id = 0; id < total; id++) {
51
52
  const named = m.leadsSomewhere(id);
52
- const spelled = m.store.hasNext(id) || m.store.hasHalo(id);
53
+ const stored = m.store.leadsSomewhere(id);
53
54
  const hasEdge = m.store.hasNext(id);
54
55
  const hasHalo = m.store.hasHalo(id);
55
56
  if (hasEdge) withEdge++;
56
57
  if (hasHalo) withHalo++;
58
+ assert.equal(
59
+ stored,
60
+ hasEdge || hasHalo,
61
+ `node ${id}: the store's definition must be its two probes`,
62
+ );
57
63
  assert.equal(
58
64
  named,
59
- spelled,
65
+ stored,
60
66
  `node ${id}: traverse.leadsSomewhere says ${named}, ` +
61
- `primitives' spelling says ${spelled} (hasNext=${hasEdge}, hasHalo=${hasHalo})`,
67
+ `the store says ${stored} (hasNext=${hasEdge}, hasHalo=${hasHalo})`,
62
68
  );
63
69
  }
64
70
 
@@ -77,8 +77,9 @@ test("136.1 the fuse gate asks the LAW — one condition, one home, one less sum
77
77
  );
78
78
  assert.match(
79
79
  src,
80
- /const fused = closed\(state\)/,
81
- "the fuse gate must ask the law's `closed(state)` — the condition the state " +
80
+ /engages: \(d: DerivationState\) => !closed\(d\)/,
81
+ "the fuse gate must ask the law's `closed` of the state fusion is offered " +
82
+ "against (the fusion layer engages only while it is open) — the condition the state " +
82
83
  "already carries. If it went back to summing `unaccounted(explained)`, that " +
83
84
  "is a second reading of one condition AND an extra sum per response: 136.3 " +
84
85
  "measures the equivalence, so re-read it before restoring the total",
@@ -398,3 +398,24 @@ test("137.6 no export is genuinely dead — the triage, as a guard", async () =>
398
398
  "genuinely dead exports found — remove them, or state where they are consumed",
399
399
  );
400
400
  });
401
+
402
+ test("137.7 no transition of the derivation is built by hand outside the law", async () => {
403
+ // `advance` is the ONLY transition (derivation.ts). A state spread with a new
404
+ // product — `{ ...state, product: … }` — is a transition the law never saw:
405
+ // the fusion used to hand-build one when the law refused it, which is exactly
406
+ // the bypass a fixed point exists to forbid. Comments are stripped, so prose
407
+ // that DESCRIBES the shape cannot satisfy or break this.
408
+ const { text } = await graph();
409
+ const offenders = [...text.entries()]
410
+ .filter(([f]) => f !== "src/mind/derivation.ts")
411
+ .filter(([, s]) =>
412
+ /\.\.\.\s*(?:state|d0|d|reasoned|closed_)\s*,\s*product\s*:/.test(s)
413
+ )
414
+ .map(([f]) => f);
415
+ assert.deepEqual(
416
+ offenders,
417
+ [],
418
+ "a derivation state is advanced by hand outside derivation.ts — offer the " +
419
+ "step to the law (`admissible` + `advance`) instead",
420
+ );
421
+ });
@@ -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
+ });