@hviana/sema 0.8.3 → 0.8.6

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 (88) hide show
  1. package/AGENTS.md +11 -10
  2. package/README.md +17 -38
  3. package/dist/example/demo.js +85 -34
  4. package/dist/src/geometry.d.ts +0 -10
  5. package/dist/src/geometry.js +0 -12
  6. package/dist/src/meter.d.ts +31 -5
  7. package/dist/src/meter.js +31 -5
  8. package/dist/src/mind/articulation.js +1 -1
  9. package/dist/src/mind/attention.js +2 -1
  10. package/dist/src/mind/derivation.d.ts +201 -0
  11. package/dist/src/mind/derivation.js +327 -0
  12. package/dist/src/mind/graph-search.d.ts +2 -1
  13. package/dist/src/mind/graph-search.js +37 -15
  14. package/dist/src/mind/match.d.ts +2 -0
  15. package/dist/src/mind/match.js +2 -0
  16. package/dist/src/mind/mechanisms/alu.js +0 -2
  17. package/dist/src/mind/mechanisms/cast.d.ts +1 -5
  18. package/dist/src/mind/mechanisms/cast.js +15 -18
  19. package/dist/src/mind/mechanisms/confluence.d.ts +0 -3
  20. package/dist/src/mind/mechanisms/confluence.js +3 -9
  21. package/dist/src/mind/mechanisms/cover.js +17 -20
  22. package/dist/src/mind/mechanisms/extraction.d.ts +0 -1
  23. package/dist/src/mind/mechanisms/extraction.js +13 -8
  24. package/dist/src/mind/mechanisms/prefix-completion.js +0 -1
  25. package/dist/src/mind/mechanisms/recall.d.ts +0 -1
  26. package/dist/src/mind/mechanisms/recall.js +8 -9
  27. package/dist/src/mind/mechanisms/reference.js +3 -4
  28. package/dist/src/mind/pipeline-mechanism.d.ts +1 -4
  29. package/dist/src/mind/pipeline.js +106 -41
  30. package/dist/src/mind/rationale.d.ts +0 -11
  31. package/dist/src/mind/rationale.js +6 -32
  32. package/dist/src/mind/reasoning.d.ts +4 -30
  33. package/dist/src/mind/reasoning.js +191 -151
  34. package/dist/src/mind/types.js +6 -3
  35. package/docs/INDEX.md +23 -24
  36. package/docs/INVARIANTS.md +16 -17
  37. package/docs/architecture/bounded-reads.md +4 -4
  38. package/docs/architecture/closure.md +78 -0
  39. package/docs/architecture/commonality.md +27 -18
  40. package/docs/architecture/cost-model.md +5 -5
  41. package/docs/architecture/exact-vs-approximate.md +4 -4
  42. package/docs/architecture/factored-machinery.md +14 -14
  43. package/docs/architecture/mechanism-market.md +9 -9
  44. package/docs/architecture/meter.md +4 -5
  45. package/docs/architecture/store.md +2 -2
  46. package/docs/architecture/thresholds.md +1 -1
  47. package/docs/failures/tempting-but-wrong.md +11 -1
  48. package/docs/harness/gates.md +6 -6
  49. package/docs/mechanisms/cover.md +2 -2
  50. package/example/demo.ts +90 -37
  51. package/jsr.json +1 -1
  52. package/package.json +1 -1
  53. package/src/geometry.ts +0 -13
  54. package/src/meter.ts +31 -5
  55. package/src/mind/articulation.ts +0 -1
  56. package/src/mind/attention.ts +2 -1
  57. package/src/mind/derivation.ts +477 -0
  58. package/src/mind/graph-search.ts +37 -20
  59. package/src/mind/match.ts +2 -0
  60. package/src/mind/mechanisms/alu.ts +0 -2
  61. package/src/mind/mechanisms/cast.ts +17 -21
  62. package/src/mind/mechanisms/confluence.ts +3 -13
  63. package/src/mind/mechanisms/cover.ts +17 -20
  64. package/src/mind/mechanisms/extraction.ts +13 -9
  65. package/src/mind/mechanisms/prefix-completion.ts +0 -1
  66. package/src/mind/mechanisms/recall.ts +7 -9
  67. package/src/mind/mechanisms/reference.ts +2 -3
  68. package/src/mind/pipeline-mechanism.ts +1 -4
  69. package/src/mind/pipeline.ts +121 -46
  70. package/src/mind/rationale.ts +6 -36
  71. package/src/mind/reasoning.ts +220 -178
  72. package/src/mind/types.ts +5 -2
  73. package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +3 -3
  74. package/test/133-the-decision-point-renders-the-state.test.mjs +204 -0
  75. package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
  76. package/test/135-one-law-any-producer.test.mjs +289 -0
  77. package/test/136-the-two-named-limits.test.mjs +205 -0
  78. package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
  79. package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
  80. package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
  81. package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
  82. package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
  83. package/test/142-the-layer-offers-only-what-the-law-admits.test.mjs +93 -0
  84. package/test/143-cycles-terminate-and-are-not-closure.test.mjs +62 -0
  85. package/test/36-already-answered-fusion.test.mjs +20 -2
  86. package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
  87. package/test/38-reason-restate-guard.test.mjs +22 -2
  88. package/test/55-cost-meter.test.mjs +6 -3
@@ -0,0 +1,93 @@
1
+ // 142 — the layer offers only what the law admits, which is what makes a refusal final.
2
+ //
3
+ // THE CONTRACT THIS PINS. `closure` stops when the law refuses the continuation the layer
4
+ // offered, and reads that refusal as "no continuation exists". That is valid only under
5
+ // ONE of two architectures: the layer searches for an admissible continuation and offers
6
+ // only a valid one (X), rather than offering candidates for the law to filter (Y). Sema is
7
+ // X — the producer tries the forward absorb first and reaches for the pivot only when the
8
+ // absorb's guards fail, returning null when neither exists — so the one offer the law can
9
+ // still refuse is the pivot without ownership, and it is the layer's last.
10
+ //
11
+ // 142.1 pins the premise: the law CAN refuse (a continuation that neither carries question
12
+ // material nor declares a move), and admits the two species. Without a refusal the contract
13
+ // would be vacuous. 142.2 pins the consequence in the engine: the walk ends at that refusal
14
+ // and the satisfying answer is untouched.
15
+
16
+ import { test } from "node:test";
17
+ import assert from "node:assert/strict";
18
+ import { Mind } from "../dist/src/index.js";
19
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
20
+ import { admissible } from "../dist/src/mind/derivation.js";
21
+
22
+ const QUERY = new TextEncoder().encode("ABCDEFGHIJ");
23
+ const W = 4;
24
+ const state = () => ({
25
+ product: new Uint8Array(0),
26
+ accounted: [],
27
+ remainder: [[0, 10]],
28
+ cost: 0,
29
+ });
30
+
31
+ test("142.1 the law refuses what neither carries nor reaches, and admits both species", () => {
32
+ // neither: no window held, no move declared
33
+ assert.equal(
34
+ admissible(
35
+ state(),
36
+ { product: new TextEncoder().encode("Paris"), contains: true, cost: 1 },
37
+ QUERY,
38
+ W,
39
+ ),
40
+ null,
41
+ "an offer that neither carries nor reaches is refused — so offering one is not free",
42
+ );
43
+ // carries: the product holds a window of the remainder
44
+ assert.notEqual(
45
+ admissible(
46
+ state(),
47
+ {
48
+ product: new TextEncoder().encode("ZZZZABCDZZ"),
49
+ contains: true,
50
+ cost: 1,
51
+ },
52
+ QUERY,
53
+ W,
54
+ ),
55
+ null,
56
+ "carrying admits",
57
+ );
58
+ // reaches: a declared move, which needs no question material
59
+ assert.notEqual(
60
+ admissible(
61
+ state(),
62
+ {
63
+ product: new TextEncoder().encode("Paris"),
64
+ contains: true,
65
+ reaches: true,
66
+ cost: 1,
67
+ },
68
+ QUERY,
69
+ W,
70
+ ),
71
+ null,
72
+ "a declared move admits on its own ground",
73
+ );
74
+ });
75
+
76
+ test("142.2 the walk ends at the refusal, with the satisfying answer untouched", async () => {
77
+ const LINKS = [
78
+ ["What is the capital of France", "The capital of France is Paris"],
79
+ ["Paris", "Paris is famous for the Eiffel Tower"],
80
+ ["the Eiffel Tower", "the Eiffel Tower is in Paris"],
81
+ ];
82
+ const store = new SQliteStore({ path: ":memory:" });
83
+ const mind = new Mind({ seed: 7, store, profile: true });
84
+ await mind.ingest(LINKS);
85
+ const out = await mind.respond("What is the capital of France famous for");
86
+ const text = new TextDecoder().decode(out.bytes).replace(/\0+/g, "").trim();
87
+ await store.close();
88
+ assert.equal(
89
+ text,
90
+ LINKS[1][1],
91
+ "the layer's last offer was refused, and the answer stands",
92
+ );
93
+ });
@@ -0,0 +1,62 @@
1
+ // 143 — cycles, in the engine: three topologies, and what must hold for each.
2
+ //
3
+ // The law holds no `visited`, no `depth`, no `hop`, no `once` and no `cap` — cycle
4
+ // prevention belongs to the layer that controls states and consumption, and a cycle is not
5
+ // closure. So the engine must terminate on each topology anyway, and the answer must be a
6
+ // fact of the corpus rather than a composition typed around the loop.
7
+ //
8
+ // A→B→A · A→B→C→A · A→B, B→C, C→B (a loop with a tail, where the refusal is not trivial).
9
+ // Each fact is filed under both of its ends, the way the store files one, so the walk has
10
+ // every edge the loop needs.
11
+
12
+ import { test } from "node:test";
13
+ import assert from "node:assert/strict";
14
+ import { Mind } from "../dist/src/index.js";
15
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
16
+
17
+ const TOPOLOGIES = {
18
+ "A→B→A": [
19
+ ["Alice knows Bob", "Alice knows Bob"],
20
+ ["Bob knows Alice", "Bob knows Alice"],
21
+ ],
22
+ "A→B→C→A": [
23
+ ["Alice knows Bob", "Alice knows Bob"],
24
+ ["Bob knows Carol", "Bob knows Carol"],
25
+ ["Carol knows Alice", "Carol knows Alice"],
26
+ ],
27
+ "A→B, B→C, C→B": [
28
+ ["Alice knows Bob", "Alice knows Bob"],
29
+ ["Bob knows Carol", "Bob knows Carol"],
30
+ ["Carol knows Bob", "Carol knows Bob"],
31
+ ],
32
+ };
33
+
34
+ async function ask(corpus, query) {
35
+ const store = new SQliteStore({ path: ":memory:" });
36
+ const mind = new Mind({ seed: 7, store, profile: true });
37
+ await mind.ingest(corpus);
38
+ const out = await mind.respond(query);
39
+ const text = new TextDecoder().decode(out.bytes).replace(/\0+/g, "").trim();
40
+ await store.close();
41
+ return text;
42
+ }
43
+
44
+ for (const [name, corpus] of Object.entries(TOPOLOGIES)) {
45
+ test(`143 ${name} terminates and answers from the corpus`, async () => {
46
+ const first = await ask(corpus, "Alice knows");
47
+ const second = await ask(corpus, "Alice knows");
48
+ assert.equal(
49
+ second,
50
+ first,
51
+ "two runs agree: no order-dependent wander around the loop",
52
+ );
53
+ if (first.length > 0) {
54
+ assert.ok(
55
+ corpus.some(([, fact]) => first.includes(fact) || fact.includes(first)),
56
+ `the answer is one of the corpus's own facts, not a composition typed around the loop: ${
57
+ JSON.stringify(first)
58
+ }`,
59
+ );
60
+ }
61
+ });
62
+ }
@@ -94,7 +94,16 @@ test("fuseAttention: a root whose continuation is already answered in the query
94
94
  }),
95
95
  guide,
96
96
  };
97
- const out = dec(await fuseAttention(m, q, primary, pre));
97
+ // fusion consumes and returns the derivation STATE; these cases exercise the
98
+ // mechanism's own gates, so the state is the minimum one for them.
99
+ const out = dec(
100
+ (await fuseAttention(m, q, {
101
+ product: primary,
102
+ accounted: [],
103
+ remainder: [],
104
+ cost: 0,
105
+ }, pre)).product,
106
+ );
98
107
  assert.ok(
99
108
  !out.includes("reply-greet"),
100
109
  `a root whose continuation is already answered in the query must not fuse in, got "${out}"`,
@@ -126,7 +135,16 @@ test("fuseAttention: ordinary multi-topic fusion (no embedded answers) is comple
126
135
  }),
127
136
  guide,
128
137
  };
129
- const out = dec(await fuseAttention(m, q, primary, pre));
138
+ // fusion consumes and returns the derivation STATE; these cases exercise the
139
+ // mechanism's own gates, so the state is the minimum one for them.
140
+ const out = dec(
141
+ (await fuseAttention(m, q, {
142
+ product: primary,
143
+ accounted: [],
144
+ remainder: [],
145
+ cost: 0,
146
+ }, pre)).product,
147
+ );
130
148
  assert.ok(
131
149
  out.includes("answer alpha"),
132
150
  `an ordinary further topic must still fuse in, got "${out}"`,
@@ -127,7 +127,16 @@ test("fuseAttention: 2 clusters, low breadth — dispersion alone is enough (gap
127
127
  }),
128
128
  guide,
129
129
  };
130
- const out = dec(await fuseAttention(m, q, primary, pre));
130
+ // fusion consumes and returns the derivation STATE; these cases exercise the
131
+ // mechanism's own gates, so the state is the minimum one for them.
132
+ const out = dec(
133
+ (await fuseAttention(m, q, {
134
+ product: primary,
135
+ accounted: [],
136
+ remainder: [],
137
+ cost: 0,
138
+ }, pre)).product,
139
+ );
131
140
  assert.equal(
132
141
  out,
133
142
  "answer alpha" + dec(primary),
@@ -154,7 +163,16 @@ test("fuseAttention: 1 cluster, dominant breadth — dominance alone is enough (
154
163
  }),
155
164
  guide,
156
165
  };
157
- const out = dec(await fuseAttention(m, q, primary, pre));
166
+ // fusion consumes and returns the derivation STATE; these cases exercise the
167
+ // mechanism's own gates, so the state is the minimum one for them.
168
+ const out = dec(
169
+ (await fuseAttention(m, q, {
170
+ product: primary,
171
+ accounted: [],
172
+ remainder: [],
173
+ cost: 0,
174
+ }, pre)).product,
175
+ );
158
176
  assert.equal(
159
177
  out,
160
178
  "answer alpha" + dec(primary),
@@ -180,7 +198,16 @@ test("fuseAttention: 1 cluster, low breadth — neither measure saves it (the li
180
198
  }),
181
199
  guide,
182
200
  };
183
- const out = dec(await fuseAttention(m, q, primary, pre));
201
+ // fusion consumes and returns the derivation STATE; these cases exercise the
202
+ // mechanism's own gates, so the state is the minimum one for them.
203
+ const out = dec(
204
+ (await fuseAttention(m, q, {
205
+ product: primary,
206
+ accounted: [],
207
+ remainder: [],
208
+ cost: 0,
209
+ }, pre)).product,
210
+ );
184
211
  assert.equal(
185
212
  out,
186
213
  dec(primary),
@@ -60,7 +60,17 @@ test("reason(): a forward hop landing on bytes already present in the query is r
60
60
  // `reason()` reports the WHOLE extension (bytes + what it carried + how many
61
61
  // steps) so a caller can price it; these assertions measure the bytes, and
62
62
  // that is all this change touches — the law they pin is untouched.
63
- const out = (await reason(m, query, answer, new Set(), pre)).bytes;
63
+ // `reason()` now consumes and returns the derivation STATE; with no remainder
64
+ // the derivation is CLOSED, so the law admits any contained continuation —
65
+ // which is exactly what these two cases exercise. The assertions below are
66
+ // untouched.
67
+ const state = {
68
+ product: answer,
69
+ accounted: [],
70
+ remainder: [],
71
+ cost: 0,
72
+ };
73
+ const out = (await reason(m, query, state, new Set(), pre)).product;
64
74
  assert.equal(
65
75
  dec(out),
66
76
  "bridge context",
@@ -88,7 +98,17 @@ test("reason(): an ordinary forward hop onto genuinely new content is unaffected
88
98
  // `reason()` reports the WHOLE extension (bytes + what it carried + how many
89
99
  // steps) so a caller can price it; these assertions measure the bytes, and
90
100
  // that is all this change touches — the law they pin is untouched.
91
- const out = (await reason(m, query, answer, new Set(), pre)).bytes;
101
+ // `reason()` now consumes and returns the derivation STATE; with no remainder
102
+ // the derivation is CLOSED, so the law admits any contained continuation —
103
+ // which is exactly what these two cases exercise. The assertions below are
104
+ // untouched.
105
+ const state = {
106
+ product: answer,
107
+ accounted: [],
108
+ remainder: [],
109
+ cost: 0,
110
+ };
111
+ const out = (await reason(m, query, state, new Set(), pre)).product;
92
112
  assert.equal(
93
113
  dec(out),
94
114
  "a wholly new fact never mentioned in any query",
@@ -419,7 +419,7 @@ test("12. the extension's cost obeys the ladder's own inequality", async () => {
419
419
  await store.close();
420
420
 
421
421
  const steps = c.reasonSteps ?? 0;
422
- const carried = c.reasonCarriedBytes ?? 0;
422
+ const carried = c.reasonAccountedBytes ?? 0;
423
423
  assert.ok(
424
424
  steps >= 1,
425
425
  `the extension must run for this to mean anything (got ${steps})`,
@@ -765,7 +765,7 @@ test("18. the remainder the pipeline decides on is visible", async () => {
765
765
  };
766
766
 
767
767
  // The pivoting query: the reasoner runs, so the remainder is non-empty
768
- // (measured: reasonSteps 1, reasonCarriedBytes 11).
768
+ // (measured: reasonSteps 1, reasonAccountedBytes 11).
769
769
  const pivot = await run("What is the capital of France famous for");
770
770
  assert.ok(
771
771
  (pivot.steps ?? 0) >= 1,
@@ -949,8 +949,11 @@ test("21. the price's second term has one definition, and it is the complement",
949
949
  // Since the value did NOT change, the lot is only pinned if something would
950
950
  // fail when the helper sums the wrong thing — so this test uses an input where
951
951
  // the two candidate readings DIFFER.
952
+ // PATH ONLY: the span algebra moved to the closure law's home
953
+ // (src/mind/derivation.ts) when the law was extracted; the assertions below
954
+ // are untouched and still pin the same contract.
952
955
  const { unexplainedSpans, unaccountedBytes } = await import(
953
- "../dist/src/mind/rationale.js"
956
+ "../dist/src/mind/derivation.js"
954
957
  );
955
958
  // Empty accounted ⇒ the whole query is unaccounted.
956
959
  assert.equal(unaccountedBytes(unexplainedSpans(10, [])), 10);