@hviana/sema 0.8.3 → 0.8.5

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 (85) 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 +11 -0
  7. package/dist/src/meter.js +11 -0
  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 +183 -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 +65 -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 +11 -0
  55. package/src/mind/articulation.ts +0 -1
  56. package/src/mind/attention.ts +2 -1
  57. package/src/mind/derivation.ts +473 -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 +208 -178
  72. package/src/mind/types.ts +5 -2
  73. package/test/133-the-decision-point-renders-the-state.test.mjs +204 -0
  74. package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
  75. package/test/135-one-law-any-producer.test.mjs +289 -0
  76. package/test/136-the-two-named-limits.test.mjs +205 -0
  77. package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
  78. package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
  79. package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
  80. package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
  81. package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
  82. package/test/36-already-answered-fusion.test.mjs +20 -2
  83. package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
  84. package/test/38-reason-restate-guard.test.mjs +22 -2
  85. package/test/55-cost-meter.test.mjs +4 -1
@@ -0,0 +1,289 @@
1
+ // 135-one-law-any-producer.test.mjs — the law over REAL states, from different
2
+ // producers, with the same verdicts.
3
+ //
4
+ // THE CLAIM THE EXTRACTION HAS TO EARN, and the one test/133 and test/134 do not
5
+ // reach: composition means a product is consumable by ANY transition the law
6
+ // admits, and the law never asks who produced it. 133 shows that over synthetic
7
+ // states and one provenance label each; 134 shows it against the engine's own
8
+ // refusal on ONE fixture. This file takes the states the ENGINE actually
9
+ // produced, across five fixtures whose winners are DIFFERENT mechanisms, and asks
10
+ // the law about them:
11
+ //
12
+ // • the winners must really span several mechanisms — otherwise nothing here is
13
+ // exercised;
14
+ // • for every fixture, the state's own published fields (the remainder's spans
15
+ // and bytes, the supplied fixed point) decide every continuation, and the
16
+ // verdicts follow the law's clauses and nothing else;
17
+ // • and re-labelling a state with every other mechanism's provenance changes NO
18
+ // verdict — the invariant, over real states rather than synthetic ones.
19
+ //
20
+ // The observed behaviour is then read back through the same law: a fixture that
21
+ // was extended took a step the law admits, one that was stopped had a supplied
22
+ // fixed point (FIXED), and one that was never extended had no continuation
23
+ // offered.
24
+
25
+ import { test } from "node:test";
26
+ import assert from "node:assert/strict";
27
+
28
+ const law = await import("../dist/src/mind/derivation.js");
29
+ const { Mind, SQliteStore } = await import("../dist/src/index.js");
30
+ const enc = (s) => new TextEncoder().encode(s);
31
+ const dec = (b) => new TextDecoder().decode(b).replace(/\s+/g, " ").trim();
32
+
33
+ const FIVE = [
34
+ {
35
+ name: "join chain (test/120)",
36
+ pairs: (() => {
37
+ const F = [
38
+ "The director of Eva is Gustaf Molander.",
39
+ "The country of Gustaf Molander is Sweden.",
40
+ "The capital of Sweden is Stockholm.",
41
+ "The mayor of Stockholm is Karin Wanngard.",
42
+ "The party of Karin Wanngard is the Social Democrats.",
43
+ ];
44
+ const K = [
45
+ ["eva director", 0],
46
+ ["gustaf molander country", 1],
47
+ ["sweden capital", 2],
48
+ ["stockholm mayor", 3],
49
+ ["karin wanngard party", 4],
50
+ ];
51
+ const out = [];
52
+ for (const [key, i] of K) {
53
+ out.push([key.split(" ").slice(0, -1).join(" "), F[i]], [key, F[i]]);
54
+ }
55
+ return out;
56
+ })(),
57
+ query: "eva director country capital mayor party",
58
+ },
59
+ {
60
+ name: "nested completion (test/98)",
61
+ pairs: [["a", "p q"], ["p", "r"], ["q", "s"], ["r s", "m n"]],
62
+ query: "a",
63
+ },
64
+ {
65
+ name: "the brake (test/110)",
66
+ pairs: [
67
+ ["What is the capital of France", "The capital of France is Paris"],
68
+ ["Paris", "Paris is famous for the Eiffel Tower"],
69
+ ["the Eiffel Tower", "the Eiffel Tower is in Paris"],
70
+ ],
71
+ query: "What is the capital of France famous for",
72
+ },
73
+ {
74
+ name: "declared fixed point (test/100)",
75
+ pairs: [
76
+ ["How do I compile hello.c?", "Run gcc hello.c"],
77
+ ["How do I compile server.c?", "Run gcc server.c"],
78
+ ["How do I compile parser.c?", "Run gcc parser.c"],
79
+ ],
80
+ query: "How do I compile program.c?",
81
+ },
82
+ {
83
+ name: "two topics",
84
+ pairs: [
85
+ ["What is the capital of France", "The capital of France is Paris"],
86
+ ["Paris", "Paris is famous for the Eiffel Tower"],
87
+ ["2+2", "2+2 equals 4"],
88
+ ["Gustaf Molander", "Gustaf Molander was a Swedish film director"],
89
+ ],
90
+ query: "What is the capital of France? And what is 2 + 2?",
91
+ },
92
+ ];
93
+
94
+ const WALK = new Set([
95
+ "pivotStep",
96
+ "absorbForward",
97
+ "pivotRefused",
98
+ "fuseAttention",
99
+ ]);
100
+
101
+ async function observe(f) {
102
+ const store = new SQliteStore({ path: ":memory:" });
103
+ const mind = new Mind({ seed: 7, store, profile: true });
104
+ await mind.ingest(f.pairs);
105
+ const steps = [];
106
+ const answer = String(await mind.respondText(f.query, (s) => steps.push(s)))
107
+ .trim();
108
+ // The winner's provenance is read from the POST-GROUNDING step, which every
109
+ // response emits. It is deliberately NOT read from the decision payload: that
110
+ // step is `narrowDecision` (which carries only a margin) when the market held
111
+ // one candidate, so reading it left the producer null on two of these five
112
+ // fixtures -- and a `Set` of producers then counted that null as a producer,
113
+ // which is how an earlier version of this file passed for the wrong reason.
114
+ const pg = steps.filter((s) => s.data && s.data.fixed !== undefined).pop()
115
+ ?.data;
116
+ const counters = mind.lastCost?.counters ?? {};
117
+ const observed = {
118
+ name: f.name,
119
+ query: f.query,
120
+ answer,
121
+ provenance: pg?.provenance ?? null,
122
+ remainderSpans: pg?.remainderSpans ?? 0,
123
+ remainderBytes: pg?.remainderBytes ?? 0,
124
+ fixed: pg?.fixed === true,
125
+ walk: steps.map((s) => s.mechanism.at(-1)).filter((m) => WALK.has(m)),
126
+ pivotSteps: counters.pivotSteps ?? 0,
127
+ W: mind.space.maxGroup,
128
+ };
129
+ await store.close();
130
+ return observed;
131
+ }
132
+
133
+ test("135. the law decides real states the same way, whoever produced them", async () => {
134
+ const seen = [];
135
+ for (const f of FIVE) seen.push(await observe(f));
136
+
137
+ // (1) THE FIXTURES MUST EXERCISE SEVERAL PRODUCERS, or nothing below is a test.
138
+ assert.ok(
139
+ seen.every((o) => o.provenance !== null),
140
+ `every fixture must report its winner: got ${
141
+ JSON.stringify(seen.map((o) => o.provenance))
142
+ }`,
143
+ );
144
+ const producers = new Set(seen.map((o) => o.provenance));
145
+ assert.ok(
146
+ producers.size >= 3,
147
+ `the fixtures' winners must span several mechanisms, got ${
148
+ JSON.stringify([...producers])
149
+ }`,
150
+ );
151
+ // (1b) AND THE BRANCHES BELOW MUST FIRE. A test whose every fixture fell
152
+ // through the same branch would pass while pinning one clause.
153
+ assert.ok(seen.some((o) => o.fixed), "one fixture must supply a fixed point");
154
+ assert.ok(
155
+ seen.some((o) => o.walk.includes("pivotRefused")),
156
+ "one fixture must show a refusal",
157
+ );
158
+ assert.ok(
159
+ seen.some((o) => o.remainderBytes > 0 && !o.fixed),
160
+ "one fixture must owe material and not be fixed",
161
+ );
162
+ assert.ok(
163
+ seen.some((o) => o.remainderBytes === 0 && !o.fixed),
164
+ "one fixture must be closed with no supplied fixed point",
165
+ );
166
+
167
+ // (2) THE STATE'S OWN FIELDS DECIDE, over the same battery for every fixture.
168
+ const battery = (o) => {
169
+ const query = enc(o.query);
170
+ const alien = enc("qqqqqqqqqqqqqqqq");
171
+ return {
172
+ carries: { product: query, contains: true, cost: 1 },
173
+ moves: { product: alien, contains: true, reaches: true, cost: 1 },
174
+ plain: { product: alien, contains: true, cost: 1 },
175
+ alien: { product: alien, contains: false, cost: 1 },
176
+ };
177
+ };
178
+ const stateOf = (o) => {
179
+ // The remainder as the fixture's own published counts describe it: the
180
+ // grounding accounts for the query's head and owes its tail.
181
+ assert.ok(
182
+ o.remainderSpans <= 1,
183
+ `${o.name}: this construction assumes at most one owed span, got ${o.remainderSpans}`,
184
+ );
185
+ const remainder = o.remainderBytes === 0
186
+ ? []
187
+ : [[o.query.length - o.remainderBytes, o.query.length]];
188
+ return {
189
+ product: enc(o.answer),
190
+ accounted: [],
191
+ remainder,
192
+ cost: 0,
193
+ fixed: o.fixed,
194
+ };
195
+ };
196
+ for (const o of seen) {
197
+ const query = enc(o.query);
198
+ const state = stateOf(o);
199
+ const conts = battery(o);
200
+ const closed = state.remainder.length === 0;
201
+ // the law's clauses, per fixture, on the fixture's own state
202
+ for (const [cn, t] of Object.entries(conts)) {
203
+ const verdict = law.admissible(state, t, query, o.W);
204
+ if (o.fixed) {
205
+ assert.equal(
206
+ verdict,
207
+ null,
208
+ `${o.name}: a supplied fixed point stops ${cn}`,
209
+ );
210
+ continue;
211
+ }
212
+ if (cn === "alien") {
213
+ assert.equal(
214
+ verdict,
215
+ null,
216
+ `${o.name}: identity comes before progress`,
217
+ );
218
+ continue;
219
+ }
220
+ if (closed) {
221
+ assert.notEqual(
222
+ verdict,
223
+ null,
224
+ `${o.name}: a closed state admits ${cn}`,
225
+ );
226
+ continue;
227
+ }
228
+ if (cn === "carries" || cn === "moves") {
229
+ assert.notEqual(verdict, null, `${o.name}: ${cn} is progress`);
230
+ } else {
231
+ assert.equal(
232
+ verdict,
233
+ null,
234
+ `${o.name}: ${cn} is neither progress nor closed`,
235
+ );
236
+ }
237
+ }
238
+
239
+ // (2b) COST IS NOT A TERM. The law says the ladder is the ORDER, not a
240
+ // clause; a state's verdict must not move when its cost does.
241
+ for (const c of [0, 1, 1e6]) {
242
+ for (const [cn, t] of Object.entries(conts)) {
243
+ assert.deepEqual(
244
+ law.admissible({ ...state, cost: c }, t, query, o.W),
245
+ law.admissible(state, t, query, o.W),
246
+ `${o.name} x ${cn}: the verdict moved with the cost (${c})`,
247
+ );
248
+ }
249
+ }
250
+
251
+ // (3) RE-LABELLING CHANGES NOTHING, over real states this time.
252
+ for (const label of producers) {
253
+ const labelled = { ...state, provenance: label };
254
+ for (const [cn, t] of Object.entries(conts)) {
255
+ assert.deepEqual(
256
+ law.admissible(labelled, t, query, o.W),
257
+ law.admissible(state, t, query, o.W),
258
+ `${o.name} x ${cn}: the verdict changed under the label "${label}"`,
259
+ );
260
+ }
261
+ }
262
+ }
263
+
264
+ // (4) AND THE ENGINE'S OBSERVED WALK IS THE LAW'S CONSEQUENCE.
265
+ for (const o of seen) {
266
+ if (o.fixed) {
267
+ assert.deepEqual(
268
+ o.walk,
269
+ [],
270
+ `${o.name}: a supplied fixed point must stop the walk (saw ${o.walk})`,
271
+ );
272
+ continue;
273
+ }
274
+ if (o.walk.includes("pivotStep") || o.walk.includes("absorbForward")) {
275
+ assert.ok(
276
+ o.pivotSteps >= 1 || o.walk.includes("absorbForward"),
277
+ `${o.name}: a taken step must be one the meter counts`,
278
+ );
279
+ }
280
+ // a refusal, where it happened, is a step the law declined — and the fixture
281
+ // that has one is the one whose remainder is not empty
282
+ if (o.walk.includes("pivotRefused")) {
283
+ assert.ok(
284
+ o.remainderBytes > 0,
285
+ `${o.name}: a refusal implies there was material left to engage`,
286
+ );
287
+ }
288
+ }
289
+ });
@@ -0,0 +1,205 @@
1
+ // 136-the-two-named-limits.test.mjs — the two sites the extraction LEAVES ALONE,
2
+ // pinned so that changing them requires the measurement the report says is
3
+ // missing.
4
+ //
5
+ // The investigation found two places where the architecture's own reading and
6
+ // the code's reading differ, and the plan's rule is: register, prove, decide
7
+ // whether it is a bug, decide whether it is a consequence, and ONLY THEN propose
8
+ // a change. Neither has been proven a bug, so neither was changed -- and these
9
+ // two assertions exist so that the next person cannot change them by accident,
10
+ // believing they are tidying up:
11
+ //
12
+ // 136.1 THE FUSE GATE KEEPS ITS OWN READING. It decides with the TOTAL
13
+ // unaccounted bytes against one quantum, while the state's `remainder` is
14
+ // per-span by construction. The two readings AGREE unless every gap is below
15
+ // one quantum and their sum is at or above it -- and that construction is what
16
+ // nobody has built, so switching the gate to the state's own `closed` would
17
+ // change an answer no one has measured. The assertion therefore pins both
18
+ // halves: that the two readings DO differ on that construction (so the reason
19
+ // lives in the test and not only in a comment), and that the gate still uses
20
+ // the total.
21
+ //
22
+ // 136.2 THE EXTENSION IS NOT PRICED. The reasoner reports what its steps cost
23
+ // and what they accounted for, and the caller never folds that into the
24
+ // decided candidate: the candidate's weight is the ladder formula alone. This
25
+ // is a DIVERGENCE between the documented intent and the implementation, on
26
+ // purpose un-fixed: pricing it would change which grounding wins, and that
27
+ // comparison has not been measured. The assertion pins the current arithmetic
28
+ // on a fixture where the walk really takes a step (guarded), so folding the
29
+ // cost in breaks it and forces the measurement.
30
+
31
+ import { test } from "node:test";
32
+ import assert from "node:assert/strict";
33
+ import { readFile } from "node:fs/promises";
34
+ import { dirname, join } from "node:path";
35
+ import { fileURLToPath } from "node:url";
36
+
37
+ const here = dirname(fileURLToPath(import.meta.url));
38
+ const law = await import("../dist/src/mind/derivation.js");
39
+ const { STEP, PASS } = await import("../dist/src/mind/graph-search.js");
40
+ const { Mind, SQliteStore } = await import("../dist/src/index.js");
41
+
42
+ test("136.1 the fuse gate asks the LAW — one condition, one home, one less sum", async () => {
43
+ // THE DISCRIMINATING CONSTRUCTION, from the law's own functions: a query whose
44
+ // accounted spans leave only sub-quantum gaps, whose SUM is at or above one
45
+ // quantum. The two READINGS differ here — that is why the register kept them
46
+ // apart — and the question is whether the difference can reach a real response.
47
+ const W = 4;
48
+ const len = 12;
49
+ const explained = [[0, 3], [6, 9]];
50
+ const gaps = law.unexplainedSpans(len, explained);
51
+ assert.deepEqual(
52
+ gaps,
53
+ [[3, 6], [9, 12]],
54
+ "the construction must leave exactly the two sub-quantum gaps",
55
+ );
56
+ const total = law.unaccountedBytes(gaps);
57
+ const perSpan = law.remainderOf(len, explained, W);
58
+ assert.ok(
59
+ total >= W,
60
+ `on the raw spans the total reading opens: ${total} >= ${W}`,
61
+ );
62
+ assert.deepEqual(
63
+ perSpan,
64
+ [],
65
+ "and the per-span reading closes — the difference is real at this level",
66
+ );
67
+
68
+ // AND IT CANNOT REACH A RESPONSE, which 136.3 measures: the ACCOUNTING applies
69
+ // the same W floor, so a sub-quantum gap never survives into `explained`. The
70
+ // gate therefore asks the law, and that is an OPTIMISATION and not a tidy-up —
71
+ // `remainderOf` is already computed for the state, while the total the gate used
72
+ // to sum (`unaccounted(explained)`) was one more pass over the spans per
73
+ // response, for the same answer.
74
+ const src = await readFile(
75
+ join(here, "..", "src", "mind", "pipeline.ts"),
76
+ "utf8",
77
+ );
78
+ assert.match(
79
+ src,
80
+ /const fused = closed\(state\)/,
81
+ "the fuse gate must ask the law's `closed(state)` — the condition the state " +
82
+ "already carries. If it went back to summing `unaccounted(explained)`, that " +
83
+ "is a second reading of one condition AND an extra sum per response: 136.3 " +
84
+ "measures the equivalence, so re-read it before restoring the total",
85
+ );
86
+ assert.ok(
87
+ !/const remainder = unaccounted\(explained\);/.test(src),
88
+ "the total-bytes sum is back: the gate has two readings of one condition again",
89
+ );
90
+ });
91
+
92
+ test("136.2 the extension is not priced — and it pays for itself", async () => {
93
+ const store = new SQliteStore({ path: ":memory:" });
94
+ const mind = new Mind({ seed: 7, store, profile: true });
95
+ await mind.ingest([
96
+ ["What is the capital of France", "The capital of France is Paris"],
97
+ ["Paris", "Paris is famous for the Eiffel Tower"],
98
+ ["the Eiffel Tower", "the Eiffel Tower is in Paris"],
99
+ ]);
100
+ const steps = [];
101
+ await mind.respondText(
102
+ "What is the capital of France famous for",
103
+ (s) => steps.push(s),
104
+ );
105
+ const counters = mind.lastCost?.counters ?? {};
106
+ const dg = steps.filter((s) => Array.isArray(s.data?.candidates)).pop()?.data;
107
+ const winner = dg?.candidates.find((c) => c.decided);
108
+ await store.close();
109
+
110
+ // THE VACUITY GUARD: the walk must really have taken a step, or the equality
111
+ // below would hold for a response that never extended anything.
112
+ assert.ok(
113
+ (counters.reasonSteps ?? 0) >= 1,
114
+ `the fixture must take at least one extension step, got ${counters.reasonSteps}`,
115
+ );
116
+ assert.ok(winner, "the response must publish its decided candidate");
117
+ // AND THE EXTENSION PAYS FOR ITSELF, measured: the walk's steps are answered by
118
+ // the material its witnesses carried, by orders of magnitude (this fixture takes
119
+ // ONE extension: 1 step against 11 bytes carried — 1 against 11000 in the one
120
+ // currency). That is why no price DECISION is added for the extension: its
121
+ // warrant is already the closure law's (a step must engage the remainder or
122
+ // move), its cost is counted and reported (`reasonSteps`), and a second,
123
+ // price-shaped admission would be a second home for the same decision — one that
124
+ // would refuse the IDENTITY steps the law admits, which is the documented
125
+ // failure of the (c) remedy (material lost: "hot" for "cold hot").
126
+ assert.ok(
127
+ (counters.reasonCarriedBytes ?? 0) * PASS >
128
+ (counters.reasonSteps ?? 0) * STEP,
129
+ "the extension is carrying less than its steps are worth — measured 11 bytes " +
130
+ "against 1 step here. If this ever fails, the absent price comparison HAS " +
131
+ "teeth and the register's GAP 2 is real: measure what pricing the extension " +
132
+ "does to the suite before touching this",
133
+ );
134
+ assert.equal(
135
+ winner.weight,
136
+ STEP + PASS * winner.unexplainedBytes,
137
+ "the winning candidate's weight is now something other than the ladder " +
138
+ "formula on its own accounting — most likely the extension's cost was " +
139
+ "folded in. That is a behaviour change: measure what pricing the " +
140
+ "extension does to the suite's answers and counters, then update this test",
141
+ );
142
+ });
143
+
144
+ test("136.3 the gate's total and the law's per-span reading agree — measured, not argued", async () => {
145
+ // GAP 1 of the §22 register. The two readings WOULD diverge if a gap below one
146
+ // quantum could survive into the accounting — a total >= W while every gap < W —
147
+ // and the register kept that as an unmeasured limit. It is measured here and it
148
+ // does not happen: on every construction below the response either reports NO
149
+ // remainder or one whose gaps are each >= W, because the ACCOUNTING applies the
150
+ // same W floor the gate does. Sub-quantum material between recognised spans sits
151
+ // INSIDE the composed spans, so it never becomes a gap; and material the cover
152
+ // cannot compose stays unrecognised in runs the recognition's own floor already
153
+ // treats as non-evidence. The gate's total reading is therefore not a second,
154
+ // laxer condition: it is the same condition, which is what makes 136.1's source
155
+ // pin safe rather than a hazard.
156
+ const { Mind, SQliteStore } = await import("../dist/src/index.js");
157
+ const corpus = [
158
+ ["What is 2 + 2?", "What is 2 + 2? 4"],
159
+ ["What is 3 + 3?", "What is 3 + 3? 6"],
160
+ ["Paris", "Paris is the capital of France"],
161
+ ["Hamlet", "Hamlet was written by Shakespeare"],
162
+ ["Romeo", "Romeo loves Juliet"],
163
+ ["Macbeth", "Macbeth is a Scottish play"],
164
+ ];
165
+ const queries = [
166
+ "Paris,Hamlet,Romeo,Macbeth", // four 1-byte gaps: total 4 = W
167
+ "Paris; Hamlet; Romeo; Macbeth", // four 2-byte gaps: total 8
168
+ "Paris - Hamlet - Romeo - Macbeth", // four 3-byte gaps: total 12
169
+ "2+2, 3+3, 4+4", // sub-quantum separators inside a composition
170
+ "Paris and Hamlet and Romeo and Macbeth", // 5-byte gaps: a genuine remainder
171
+ ];
172
+ const store = new SQliteStore({ path: ":memory:" });
173
+ const mind = new Mind({ seed: 7, store, profile: true });
174
+ await mind.ingest(corpus);
175
+ const W = mind.space.maxGroup;
176
+ let closed = 0;
177
+ let open = 0;
178
+ for (const q of queries) {
179
+ const steps = [];
180
+ await mind.respondText(q, (s) => steps.push(s));
181
+ const pg = steps.filter((s) => s.data && s.data.fixed !== undefined).pop()
182
+ ?.data;
183
+ const spans = pg?.remainderSpans ?? 0;
184
+ const bytes = pg?.remainderBytes ?? 0;
185
+ assert.equal(
186
+ bytes >= W,
187
+ spans > 0,
188
+ `${JSON.stringify(q)}: the gate's total reading says ${
189
+ bytes >= W ? "open" : "closed"
190
+ } ` +
191
+ `(${bytes} bytes vs W=${W}) while the law's per-span reading says ` +
192
+ `${
193
+ spans > 0 ? "open" : "closed"
194
+ } (${spans} spans) — they diverged, so the ` +
195
+ `accounting no longer applies the W floor and the gate IS a laxer second reading`,
196
+ );
197
+ if (bytes > 0) open++;
198
+ else closed++;
199
+ }
200
+ assert.ok(
201
+ open > 0 && closed > 0,
202
+ `the set must exercise both sides, got open=${open} closed=${closed}`,
203
+ );
204
+ await store.close();
205
+ });