@hviana/sema 0.9.0 → 0.9.2

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 (118) 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 +33 -0
  14. package/dist/src/meter.js +34 -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/evidence.d.ts +24 -0
  22. package/dist/src/mind/evidence.js +90 -0
  23. package/dist/src/mind/graph-search.d.ts +89 -15
  24. package/dist/src/mind/graph-search.js +345 -174
  25. package/dist/src/mind/learning.js +1 -1
  26. package/dist/src/mind/mechanisms/cover.d.ts +19 -3
  27. package/dist/src/mind/mechanisms/cover.js +142 -61
  28. package/dist/src/mind/mechanisms/recall.js +10 -3
  29. package/dist/src/mind/mind.d.ts +6 -0
  30. package/dist/src/mind/mind.js +5 -2
  31. package/dist/src/mind/pipeline.d.ts +5 -1
  32. package/dist/src/mind/pipeline.js +220 -90
  33. package/dist/src/mind/primitives.d.ts +25 -5
  34. package/dist/src/mind/primitives.js +107 -44
  35. package/dist/src/mind/reasoning.d.ts +18 -4
  36. package/dist/src/mind/reasoning.js +487 -328
  37. package/dist/src/mind/recognition.js +29 -13
  38. package/dist/src/mind/resonance.js +1 -11
  39. package/dist/src/mind/traverse.d.ts +45 -5
  40. package/dist/src/mind/traverse.js +285 -8
  41. package/dist/src/mind/types.d.ts +16 -1
  42. package/dist/src/store-sqlite.d.ts +25 -0
  43. package/dist/src/store-sqlite.js +89 -1
  44. package/dist/src/store.d.ts +48 -4
  45. package/dist/src/store.js +86 -6
  46. package/docs/INDEX.md +20 -19
  47. package/docs/INVARIANTS.md +17 -16
  48. package/docs/architecture/bounded-reads.md +1 -1
  49. package/docs/architecture/caches.md +5 -4
  50. package/docs/architecture/closure.md +45 -5
  51. package/docs/architecture/cost-model.md +16 -0
  52. package/docs/architecture/evidence.md +113 -0
  53. package/docs/architecture/exact-vs-approximate.md +10 -9
  54. package/docs/architecture/factored-machinery.md +14 -13
  55. package/docs/architecture/fold-contract.md +51 -1
  56. package/docs/architecture/mechanism-market.md +21 -0
  57. package/docs/architecture/memoization.md +3 -3
  58. package/docs/architecture/meter.md +2 -1
  59. package/docs/architecture/saturation.md +12 -0
  60. package/docs/architecture/store.md +25 -2
  61. package/docs/failures/tempting-but-wrong.md +13 -2
  62. package/docs/harness/gates.md +12 -10
  63. package/docs/mechanisms/cover.md +23 -6
  64. package/jsr.json +1 -1
  65. package/package.json +1 -1
  66. package/src/alu/README.md +10 -2
  67. package/src/alu/src/index.ts +1 -0
  68. package/src/alu/src/parser.ts +6 -6
  69. package/src/alu/src/resonance.ts +42 -0
  70. package/src/alu/test/alu.test.ts +40 -0
  71. package/src/bytes.ts +13 -3
  72. package/src/canon.ts +40 -0
  73. package/src/geometry.ts +183 -154
  74. package/src/meter.ts +34 -1
  75. package/src/mind/articulation.ts +14 -2
  76. package/src/mind/attention.ts +47 -25
  77. package/src/mind/bridge.ts +3 -3
  78. package/src/mind/derivation.ts +77 -0
  79. package/src/mind/evidence.ts +107 -0
  80. package/src/mind/graph-search.ts +449 -221
  81. package/src/mind/learning.ts +1 -7
  82. package/src/mind/match.ts +1 -2
  83. package/src/mind/mechanisms/cast.ts +1 -2
  84. package/src/mind/mechanisms/cover.ts +207 -87
  85. package/src/mind/mechanisms/extraction.ts +1 -2
  86. package/src/mind/mechanisms/prefix-completion.ts +1 -1
  87. package/src/mind/mechanisms/recall.ts +17 -5
  88. package/src/mind/mechanisms/reference.ts +1 -1
  89. package/src/mind/mind.ts +9 -30
  90. package/src/mind/pipeline.ts +263 -104
  91. package/src/mind/primitives.ts +119 -43
  92. package/src/mind/reasoning.ts +611 -419
  93. package/src/mind/recognition.ts +24 -9
  94. package/src/mind/resonance.ts +2 -16
  95. package/src/mind/trace.ts +1 -1
  96. package/src/mind/traverse.ts +321 -8
  97. package/src/mind/types.ts +15 -11
  98. package/src/store-sqlite.ts +92 -1
  99. package/src/store.ts +113 -7
  100. package/test/105-derive-through-reports-its-refusal.test.mjs +8 -5
  101. package/test/106-the-join-fires.test.mjs +21 -0
  102. package/test/111-the-cover-assembly-is-counted.test.mjs +8 -5
  103. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +18 -12
  104. package/test/136-the-two-named-limits.test.mjs +3 -2
  105. package/test/137-the-law-lives-once-and-below.test.mjs +21 -0
  106. package/test/148-exact-shortcuts-agree.test.mjs +188 -0
  107. package/test/149-the-closure-engine.test.mjs +138 -0
  108. package/test/150-the-join-is-output-sensitive.test.mjs +66 -0
  109. package/test/151-the-cover-pays-for-what-it-reaches.test.mjs +142 -0
  110. package/test/152-the-read-side-names-as-the-write-side.test.mjs +146 -0
  111. package/test/153-a-cheaper-bound-is-looked-at-first.test.mjs +155 -0
  112. package/test/154-the-question-names-the-step.test.mjs +281 -0
  113. package/test/24-generalization.test.mjs +32 -0
  114. package/test/36-bloom.test.mjs +53 -0
  115. package/test/37-cluster-dispersion-fusion.test.mjs +75 -0
  116. package/test/48-recognise-turn-connective.test.mjs +3 -2
  117. package/test/55-cost-meter.test.mjs +4 -4
  118. package/test/90-connector-read-cap.test.mjs +7 -7
@@ -4,26 +4,56 @@
4
4
  // fuseAttention — fuse independent points of attention (multi-topic)
5
5
  import { rItem, rNode } from "./trace.js";
6
6
 
7
- import { bytesEqual, indexOf } from "../bytes.js";
7
+ import { bytesEqual, indexOf, latin1 } from "../bytes.js";
8
8
  import type { Attention, MindContext } from "./types.js";
9
- import { resolve } from "./primitives.js";
10
- import { corpusN, hubBound } from "./traverse.js";
9
+ import { read, resolve } from "./primitives.js";
10
+ import { countClusters } from "./attention.js";
11
+ import {
12
+ guidedFirst,
13
+ hubBound,
14
+ namedContinuations,
15
+ offsetCanon,
16
+ } from "./traverse.js";
17
+ import { unspoken, type WindowIndex, windowIndex } from "./evidence.js";
11
18
  import { containsSpan, follow, haloSiblings, project } from "./match.js";
12
19
  import { joinWithBridge, pivotInto } from "./resonance.js";
13
- import { admissible, advance, type Continuation } from "./derivation.js";
14
- import type { Precomputed } from "./pipeline-mechanism.js";
15
- import { type Rationale } from "./rationale.js";
16
20
  import {
17
- closure,
21
+ closed,
22
+ closeOver,
23
+ type ClosureLayer,
24
+ type Continuation,
18
25
  type DerivationState,
19
26
  type Offer,
20
27
  restates,
21
28
  type Span,
22
29
  unaccountedBytes,
30
+ type Witness,
23
31
  } from "./derivation.js";
32
+ import type { Precomputed } from "./pipeline-mechanism.js";
33
+ import type { Rationale } from "./rationale.js";
24
34
  import { STEP } from "./graph-search.js";
25
35
 
26
- /** Extend a grounded answer forward across facts (multi-hop reasoning).
36
+ /** What one ADMITTED transition did to the question, published on the meter:
37
+ * the bytes its witnesses CARRIED and the remainder the move DRAINED. The two
38
+ * transitions that go through the law — the walk's steps and the fusion —
39
+ * report through this one function, so the two counters have one spelling.
40
+ * Write-only (meter.ts contract 1). */
41
+ function meterTransition(
42
+ ctx: MindContext,
43
+ before: DerivationState,
44
+ after: DerivationState,
45
+ witnesses: ReadonlyArray<Witness>,
46
+ ): void {
47
+ if (!ctx.meter) return;
48
+ for (const w of witnesses) {
49
+ if (w.window) ctx.meter.reasonCarriedBytes += w.window[1] - w.window[0];
50
+ }
51
+ ctx.meter.closureDrainedBytes += unaccountedBytes(before.remainder) -
52
+ unaccountedBytes(after.remainder);
53
+ }
54
+
55
+ /** Extend a grounded answer forward across facts (multi-hop reasoning) — the
56
+ * WALK, run as one layer of the closure engine ({@link walkLayer}).
27
57
  * Pivots on the longest unconsumed learnt context each answer contains,
28
58
  * then follows the pivot's continuation to the next fact. **The chain ends
29
59
  * when it STOPS, never when a count runs out**: every exit is a refusal (no
@@ -38,7 +68,7 @@ import { STEP } from "./graph-search.js";
38
68
  * when it declared one — see the pivot's own containment rule. `pre` is the
39
69
  * response's shared pre-computation — the post-grounding stages read the
40
70
  * same container the mechanisms did. */
41
- export async function reason(
71
+ export function reason(
42
72
  ctx: MindContext,
43
73
  query: Uint8Array,
44
74
  d0: DerivationState,
@@ -46,33 +76,30 @@ export async function reason(
46
76
  pre: Precomputed,
47
77
  voiced: readonly Uint8Array[] = [],
48
78
  ): Promise<DerivationState> {
49
- const answer = d0.product;
50
- /** The query material the GROUNDING left uncovered — the state's own
51
- * remainder. Only the reasoner's OWN extensions are judged against it; a
52
- * mechanism carrying its own `used` set owns its shape. */
53
- const uncovered = d0.remainder;
54
- // Echo guard: a query that is ITSELF a learnt continuation (some context's
55
- // answer) is being asked back at the system — hopping forward from it would
56
- // chain through the very fact that produced it and echo the conversation
57
- // back. The grounded answer alone is the honest read-out. Deliberately a
58
- // broad structural gate; pinned by test/31-audit.
59
- const qId = pre.queryResolved;
60
- if (qId !== null && ctx.store.prevCount(qId) > 0) {
61
- // THE ECHO GUARD, NAMED — the audit's point 1 could not attribute two real
62
- // walks that stopped with no note at all, and this is where one of them went:
63
- // the query IS a learnt continuation, so the reasoner returns the grounded
64
- // read-out and never offers anything. Without this note the walk looks like
65
- // an unexplained stop; with it, "no note" can only mean the offer was never
66
- // asked. No decision changes: the guard returns exactly where it did.
67
- ctx.trace?.step(
68
- "echoGuard",
69
- [rItem(query, "query")],
70
- [],
71
- "the query is itself a learnt continuation — answering the grounded read-out without walking",
72
- );
73
- return d0;
74
- }
79
+ return closeOver(d0, query, ctx.space.maxGroup, [
80
+ walkLayer(ctx, query, preConsumed, pre, voiced),
81
+ ]);
82
+ }
75
83
 
84
+ /** The multi-hop walk as a {@link ClosureLayer}: it OFFERS a forward absorb or a
85
+ * pivot, the law admits. Its preamble — the two guards that say the question
86
+ * was already answered by the query's own position in the graph, and the
87
+ * pre-consumption of what the grounding spoke for — runs on the FIRST offer,
88
+ * against the state the engine hands it; a guard that fires makes that first
89
+ * offer `null`, so the walk ends where it begins and reports nothing more. */
90
+ export function walkLayer(
91
+ ctx: MindContext,
92
+ query: Uint8Array,
93
+ preConsumed: ReadonlySet<number>,
94
+ pre: Precomputed,
95
+ voiced: readonly Uint8Array[] = [],
96
+ ): ClosureLayer {
97
+ /** The preamble ran (on the first offer), and whether a guard stopped it. */
98
+ let begun = false;
99
+ let stopped = false;
100
+ let groundedId: number | null = null;
101
+ let groundedPrev: number[] | null = null;
102
+ let startedFrom: Uint8Array = new Uint8Array(0);
76
103
  // Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
77
104
  // the hub bound, via the store's LIMITed edge reads: a common continuation's
78
105
  // reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
@@ -81,61 +108,15 @@ export async function reason(
81
108
  // read order); a pivot suppressed only by a beyond-cap neighbour may now
82
109
  // fire — the same visibility trade chooseNext documents.
83
110
  const bound = hubBound(ctx);
84
-
85
- // ANSWERED DIRECTLY — the echo guard's other half, and the same principle:
86
- // the QUERY's own position in the graph, not the answer's content, says the
87
- // read-out is complete. Above: the query is itself a learnt CONTINUATION.
88
- // Here: the query is a learnt CONTEXT and the grounded answer is one of ITS
89
- // OWN continuations. Either way the question was answered directly and there
90
- // is nothing left to chain for.
91
- //
92
- // Every stopping condition in the loop below judges the ANSWER (`consumed` /
93
- // the law's `restates` / `bytesEqual`); none asks whether the QUESTION was
94
- // satisfied. So a single-hop question whose answer happens to name another
95
- // learnt context extends past a correct answer and REPLACES it:
96
- //
97
- // asked "<subj> father"
98
- // hop 1 "The father of <subj> is Ernest I of Anhalt-Dessau." <- correct
99
- // pivot "Ernest I of Anhalt-Dessau" <- a learnt context too
100
- // got "The date of death of Ernest I of Anhalt-Dessau is 12 June 1516."
101
- //
102
- // Any store holding a bare-entity context alongside a relation fact has that
103
- // shape; it is not exotic.
104
- //
105
- // Checked ONCE, before the loop, and ahead of BOTH extension branches:
106
- // `absorbForward` extends the answer too, and nothing about the defect is
107
- // specific to pivoting, so a guard between them would gate one arbitrary half
108
- // of the same step. Hop 0 is also the only hop at which the question can be
109
- // answered directly at all — after a hop, `cur` is no longer the query's own
110
- // continuation, so re-testing per hop could only cost reads.
111
- //
112
- // Read from the ANSWER's side (`prevFirst`) rather than the query's
113
- // (`nextFirst`). Same relation, but a CONTEXT's fan-out is hub-sized while
114
- // this is one answer's establishing-context fan-in. Both the resolve and the
115
- // reverse read are exactly what hop 0 of the loop below would perform, so
116
- // they are computed ONCE here and handed down (`groundedId`, `groundedPrev`)
117
- // — the guard then costs nothing when it does not fire. Stated because the
118
- // naive placement does NOT: `resolve` re-folds the answer bytes on every call
119
- // (no memo) and `prevFirst` is a direct read (no memo), so a guard that
120
- // recomputed them would add one fold plus one √N-bounded read per ask.
121
- // The √N cap carries the file-wide visibility trade, and fails SAFE in the
122
- // direction that matters: a missed guard costs an over-extended answer, never
123
- // a suppressed chain.
124
- //
125
- // A genuine multi-hop query is not a deposited context at all ("What is the
126
- // capital of the country of Eiffel Tower?" resolves to nothing), so this can
127
- // never gate a real chain.
128
- const groundedId = resolve(ctx, answer);
129
- const groundedPrev = groundedId === null
130
- ? null
131
- : ctx.store.prevFirst(groundedId, bound);
132
- if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
133
- return d0;
134
- }
135
-
136
111
  const consumed = new Set<number>();
112
+ /** The windows each product of this derivation holds — what of the question
113
+ * the derivation has already restated, so a later step is NAMED only by
114
+ * question material none of them said. */
115
+ const spoken: WindowIndex[] = [];
116
+ const speak = (product: Uint8Array) =>
117
+ spoken.push(windowIndex(offsetCanon(ctx, product), ctx.space.maxGroup));
137
118
  /** `prev` lets a caller hand in an already-read reverse-edge list — hop 0
138
- * reuses the guard's, above, instead of re-reading it. */
119
+ * reuses the guard's, below, instead of re-reading it. */
139
120
  const consumeNode = (
140
121
  id: number | null,
141
122
  prev?: readonly number[],
@@ -150,27 +131,107 @@ export async function reason(
150
131
  for (const n of ctx.store.nextFirst(id, bound)) consumed.add(n);
151
132
  };
152
133
 
153
- // Pre-consume whatever the grounding stage already spoke for. The halo
154
- // sweep is one ANN query per node — cap it at haloQueryK sweeps (cover
155
- // grounding can pre-consume one node per recognised site, O(query length));
156
- // nodes past the cap are still consumed directly, they just skip the
157
- // synonym expansion.
158
- const preconsume = async () => {
159
- let haloSweeps = 0;
160
- for (const id of preConsumed) {
161
- consumeNode(id);
162
- if (haloSweeps >= ctx.cfg.haloQueryK) continue;
163
- const h = ctx.store.halo(id);
164
- if (!h) continue;
165
- haloSweeps++;
166
- for (const sib of await haloSiblings(ctx, id, h)) consumeNode(sib.id);
134
+ /** THE PREAMBLE — true when the walk may proceed. */
135
+ const begin = async (d0: DerivationState): Promise<boolean> => {
136
+ const answer = d0.product;
137
+ startedFrom = answer;
138
+ speak(answer);
139
+ // Echo guard: a query that is ITSELF a learnt continuation (some context's
140
+ // answer) is being asked back at the system — hopping forward from it would
141
+ // chain through the very fact that produced it and echo the conversation
142
+ // back. The grounded answer alone is the honest read-out. Deliberately a
143
+ // broad structural gate; pinned by test/31-audit.
144
+ const qId = pre.queryResolved;
145
+ if (qId !== null && ctx.store.prevCount(qId) > 0) {
146
+ // THE ECHO GUARD, NAMED — the audit's point 1 could not attribute two real
147
+ // walks that stopped with no note at all, and this is where one of them went:
148
+ // the query IS a learnt continuation, so the reasoner returns the grounded
149
+ // read-out and never offers anything. Without this note the walk looks like
150
+ // an unexplained stop; with it, "no note" can only mean the offer was never
151
+ // asked. No decision changes: the guard returns exactly where it did.
152
+ ctx.trace?.step(
153
+ "echoGuard",
154
+ [rItem(query, "query")],
155
+ [],
156
+ "the query is itself a learnt continuation — answering the grounded read-out without walking",
157
+ );
158
+ return false;
159
+ }
160
+
161
+ // ANSWERED DIRECTLY — the echo guard's other half, and the same principle:
162
+ // the QUERY's own position in the graph, not the answer's content, says the
163
+ // read-out is complete. Above: the query is itself a learnt CONTINUATION.
164
+ // Here: the query is a learnt CONTEXT and the grounded answer is one of ITS
165
+ // OWN continuations. Either way the question was answered directly and there
166
+ // is nothing left to chain for.
167
+ //
168
+ // Every stopping condition in the loop below judges the ANSWER (`consumed` /
169
+ // the law's `restates` / `bytesEqual`); none asks whether the QUESTION was
170
+ // satisfied. So a single-hop question whose answer happens to name another
171
+ // learnt context extends past a correct answer and REPLACES it:
172
+ //
173
+ // asked "<subj> father"
174
+ // hop 1 "The father of <subj> is Ernest I of Anhalt-Dessau." <- correct
175
+ // pivot "Ernest I of Anhalt-Dessau" <- a learnt context too
176
+ // got "The date of death of Ernest I of Anhalt-Dessau is 12 June 1516."
177
+ //
178
+ // Any store holding a bare-entity context alongside a relation fact has that
179
+ // shape; it is not exotic.
180
+ //
181
+ // Checked ONCE, before the loop, and ahead of BOTH extension branches:
182
+ // `absorbForward` extends the answer too, and nothing about the defect is
183
+ // specific to pivoting, so a guard between them would gate one arbitrary half
184
+ // of the same step. Hop 0 is also the only hop at which the question can be
185
+ // answered directly at all — after a hop, `cur` is no longer the query's own
186
+ // continuation, so re-testing per hop could only cost reads.
187
+ //
188
+ // Read from the ANSWER's side (`prevFirst`) rather than the query's
189
+ // (`nextFirst`). Same relation, but a CONTEXT's fan-out is hub-sized while
190
+ // this is one answer's establishing-context fan-in. Both the resolve and the
191
+ // reverse read are exactly what hop 0 of the loop below would perform, so
192
+ // they are computed ONCE here and handed down (`groundedId`, `groundedPrev`)
193
+ // — the guard then costs nothing when it does not fire. Stated because the
194
+ // naive placement does NOT: `resolve` re-folds the answer bytes on every call
195
+ // (no memo) and `prevFirst` is a direct read (no memo), so a guard that
196
+ // recomputed them would add one fold plus one √N-bounded read per ask.
197
+ // The √N cap carries the file-wide visibility trade, and fails SAFE in the
198
+ // direction that matters: a missed guard costs an over-extended answer, never
199
+ // a suppressed chain.
200
+ //
201
+ // A genuine multi-hop query is not a deposited context at all ("What is the
202
+ // capital of the country of Eiffel Tower?" resolves to nothing), so this can
203
+ // never gate a real chain.
204
+ groundedId = resolve(ctx, answer);
205
+ groundedPrev = groundedId === null
206
+ ? null
207
+ : ctx.store.prevFirst(groundedId, bound);
208
+ if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
209
+ return false;
210
+ }
211
+
212
+ // Pre-consume whatever the grounding stage already spoke for. The halo
213
+ // sweep is one ANN query per node — cap it at haloQueryK sweeps (cover
214
+ // grounding can pre-consume one node per recognised site, O(query length));
215
+ // nodes past the cap are still consumed directly, they just skip the
216
+ // synonym expansion.
217
+ const preconsume = async () => {
218
+ let haloSweeps = 0;
219
+ for (const id of preConsumed) {
220
+ consumeNode(id);
221
+ if (haloSweeps >= ctx.cfg.haloQueryK) continue;
222
+ const h = ctx.store.halo(id);
223
+ if (!h) continue;
224
+ haloSweeps++;
225
+ for (const sib of await haloSiblings(ctx, id, h)) consumeNode(sib.id);
226
+ }
227
+ };
228
+ if (ctx.meter) {
229
+ await ctx.meter.time("reason.preconsumeHalos", preconsume);
230
+ } else {
231
+ await preconsume();
167
232
  }
233
+ return true;
168
234
  };
169
- if (ctx.meter) {
170
- await ctx.meter.time("reason.preconsumeHalos", preconsume);
171
- } else {
172
- await preconsume();
173
- }
174
235
 
175
236
  // ── THE WALK, AS THE LAW'S ───────────────────────────────────────────
176
237
  //
@@ -194,7 +255,6 @@ export async function reason(
194
255
  // 30 s); and raising it from 12 to 200 changed neither the answer nor
195
256
  // `pivotSteps` on the chain fixtures.
196
257
  const qv = pre.guide; // the response-wide guide IS the query's gist
197
- const W = ctx.space.maxGroup;
198
258
  // WHOSE EXTENSION IS THIS? `voiced` is what the mechanism WITHHELD (the
199
259
  // pipeline sends the used anchors' CONTINUATIONS, not their bytes), so a
200
260
  // non-empty `voiced` means exactly what that note says: the grounding came
@@ -205,7 +265,6 @@ export async function reason(
205
265
  // expresses as the identity species of progress — `moves` — declared by the
206
266
  // reporter and never inferred from the mechanism's name.
207
267
  const producerOwnsShape = voiced.length > 0;
208
- const startedFrom = answer;
209
268
  let hop = 0;
210
269
  let t: ReturnType<Rationale["enter"]> | undefined;
211
270
  // WHAT THE OFFERED STEP WOULD BE. The law decides, so the accepted step is
@@ -218,6 +277,13 @@ export async function reason(
218
277
  | null = null;
219
278
 
220
279
  const offer: Offer = async (d) => {
280
+ if (!begun) {
281
+ begun = true;
282
+ if (!(await begin(d))) {
283
+ stopped = true;
284
+ return null;
285
+ }
286
+ }
221
287
  if (ctx.meter) ctx.meter.offerRuns += 1;
222
288
  const cur = d.product;
223
289
  // The first step's `cur` IS the grounding's product, so the guard above
@@ -350,79 +416,103 @@ export async function reason(
350
416
  );
351
417
  return null;
352
418
  }
419
+ // THE STEP THE QUESTION ASKS FOR. The hop `follow` took is NAMED when one
420
+ // of its establishing contexts is witnessed by what of the question no
421
+ // product has restated yet, plus the pivot itself (traverse.ts,
422
+ // `namedContinuations`): `Where was the place of death of the director of
423
+ // film Beat Girl?` names `Edmond T. Gréville place of death` once the first
424
+ // hop has restated `director` and `Beat Girl` — and `Who is the father of
425
+ // Frederick II?` names nothing past `Peter III of Aragon`, because `father`
426
+ // was restated by the hop that reached him. A named step MOVES to structure
427
+ // the question asked for, so it is admitted on that ground; an unnamed one
428
+ // must carry the question's remainder as before, and from a CLOSED
429
+ // derivation — nothing left owed — it is not offered at all: the question
430
+ // asks for no further step, and the law would otherwise admit any.
431
+ const asked = ctx._edgeAsked;
432
+ let named = false;
433
+ if (asked !== null) {
434
+ const unsaid = {
435
+ bytes: asked.bytes,
436
+ index: unspoken(asked.index, spoken),
437
+ };
438
+ const names = namedContinuations(ctx, pivot, unsaid);
439
+ const hop = guidedFirst(ctx, pivot);
440
+ named = names !== null && hop !== undefined && names.includes(hop);
441
+ }
442
+ if (!named && !producerOwnsShape && closed(d)) {
443
+ ctx.trace?.step(
444
+ "pivotUnasked",
445
+ [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")],
446
+ [rItem(fc, "withheld")],
447
+ "the derivation owes nothing and the question names no further step — not offered",
448
+ );
449
+ return null;
450
+ }
353
451
  pending = { kind: "pivot", cur, pivot, fc };
354
- // Offered with the identity species ONLY when the grounding declared what it
355
- // speaks for; otherwise the law requires this step to carry question
356
- // material the grounding left unaccounted, which is the drift the extension
357
- // tests pin — refused by the law's own measure, not by a private window
358
- // test.
452
+ // Offered with the identity species when the grounding declared what it
453
+ // speaks for, or when the question names the step; otherwise the law
454
+ // requires this step to carry question material the grounding left
455
+ // unaccounted, which is the drift the extension tests pin — refused by the
456
+ // law's own measure, not by a private window test.
359
457
  return {
360
458
  product: fc,
361
459
  contains: true,
362
- reaches: producerOwnsShape,
460
+ reaches: producerOwnsShape || named,
363
461
  cost: STEP,
364
462
  };
365
463
  };
366
464
 
367
- const closed_ = await closure(
368
- d0,
369
- query,
370
- W,
371
- offer,
372
- (before, after, witnesses) => {
373
- if (ctx.meter) {
374
- ctx.meter.reasonCarriedBytes += witnesses.reduce(
375
- (n, w) => n + (w.window ? w.window[1] - w.window[0] : 0),
376
- 0,
377
- );
378
- ctx.meter.closureDrainedBytes += unaccountedBytes(before.remainder) -
379
- unaccountedBytes(after.remainder);
380
- }
381
- const p = pending;
382
- pending = null;
383
- if (p === null) return;
384
- t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
385
- if (p.kind === "absorb") {
386
- ctx.trace?.step(
387
- "absorbForward",
388
- [rItem(p.cur, "answer", p.curId ?? undefined)],
389
- [rItem(p.fwd, "answer", resolve(ctx, p.fwd) ?? undefined)],
390
- "the answer is itself a learnt fact — follow its continuation to the fixpoint",
391
- );
392
- } else {
393
- if (ctx.meter) ctx.meter.pivotSteps++;
394
- ctx.trace?.step(
395
- "pivotStep",
396
- [rItem(p.cur, "answer"), rNode(ctx, p.pivot, "pivot")],
397
- [rItem(p.fc, "answer", resolve(ctx, p.fc) ?? undefined)],
398
- "pivot on the shared span this answer contains, then step forward across that fact",
399
- );
400
- }
401
- },
402
- (at) => {
403
- // THE LAW REFUSED — counted where it happened, before the bookkeeping
404
- // below decides whether there is a step to report.
405
- if (ctx.meter) ctx.meter.lawRejects += 1;
406
- const p = pending;
407
- pending = null;
408
- if (p === null || p.kind !== "pivot") return;
409
- // THE BRAKE, MADE VISIBLE — and it is now the LAW's refusal, reported
410
- // where it happened. The reasoner declines a step that carries none of
411
- // the material the grounding left unaccounted. A refusal that leaves no
412
- // trace is the kind of silent cut AGENTS §6 forbids: the rationale is
413
- // where a reader learns that an extension was declined for want of
414
- // question material, and where the next person sees why the chain stopped
415
- // here. Measured with the check disabled, test/110 and test/116 fail.
416
- const left = unaccountedBytes(at.remainder);
465
+ const onTaken = (
466
+ before: DerivationState,
467
+ after: DerivationState,
468
+ witnesses: ReadonlyArray<Witness>,
469
+ ): void => {
470
+ meterTransition(ctx, before, after, witnesses);
471
+ speak(after.product);
472
+ const p = pending;
473
+ pending = null;
474
+ if (p === null) return;
475
+ t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
476
+ if (p.kind === "absorb") {
417
477
  ctx.trace?.step(
418
- "pivotRefused",
419
- [rItem(p.cur, "answer"), rItem(query, "query")],
420
- at.remainder.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")),
421
- `the step carries none of the question material the grounding left ` +
422
- `unaccounted (${left} byte(s) in ${at.remainder.length} span(s)) — refused`,
478
+ "absorbForward",
479
+ [rItem(p.cur, "answer", p.curId ?? undefined)],
480
+ [rItem(p.fwd, "answer", resolve(ctx, p.fwd) ?? undefined)],
481
+ "the answer is itself a learnt fact — follow its continuation to the fixpoint",
423
482
  );
424
- },
425
- );
483
+ } else {
484
+ if (ctx.meter) ctx.meter.pivotSteps++;
485
+ ctx.trace?.step(
486
+ "pivotStep",
487
+ [rItem(p.cur, "answer"), rNode(ctx, p.pivot, "pivot")],
488
+ [rItem(p.fc, "answer", resolve(ctx, p.fc) ?? undefined)],
489
+ "pivot on the shared span this answer contains, then step forward across that fact",
490
+ );
491
+ }
492
+ };
493
+ const onRefused = (at: DerivationState): void => {
494
+ // THE LAW REFUSED — counted where it happened, before the bookkeeping
495
+ // below decides whether there is a step to report.
496
+ if (ctx.meter) ctx.meter.lawRejects += 1;
497
+ const p = pending;
498
+ pending = null;
499
+ if (p === null || p.kind !== "pivot") return;
500
+ // THE BRAKE, MADE VISIBLE — and it is now the LAW's refusal, reported
501
+ // where it happened. The reasoner declines a step that carries none of
502
+ // the material the grounding left unaccounted. A refusal that leaves no
503
+ // trace is the kind of silent cut AGENTS §6 forbids: the rationale is
504
+ // where a reader learns that an extension was declined for want of
505
+ // question material, and where the next person sees why the chain stopped
506
+ // here. Measured with the check disabled, test/110 and test/116 fail.
507
+ const left = unaccountedBytes(at.remainder);
508
+ ctx.trace?.step(
509
+ "pivotRefused",
510
+ [rItem(p.cur, "answer"), rItem(query, "query")],
511
+ at.remainder.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")),
512
+ `the step carries none of the question material the grounding left ` +
513
+ `unaccounted (${left} byte(s) in ${at.remainder.length} span(s)) — refused`,
514
+ );
515
+ };
426
516
 
427
517
  // THE WALK'S OWN ENDING, NAMED — the audit's point 1. Every stop INSIDE the
428
518
  // offer now has a note, but a walk can also end without the offer ever being
@@ -430,68 +520,148 @@ export async function reason(
430
520
  // ended as "no note at all", indistinguishable from a mechanism that never ran.
431
521
  // Two endings cover every remaining case, and together they make a silent stop
432
522
  // impossible — which is what the `Offer` contract needs, because `null` is read
433
- // as exhaustion and exhaustion must be a statement, not an absence.
434
- t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
435
- if (closed_.remainder.length === 0) {
436
- ctx.trace?.step(
437
- "walkClosed",
438
- [rItem(closed_.product, "answer")],
439
- [],
440
- "the derivation is closed — nothing is left unaccounted for",
441
- );
442
- } else {
443
- ctx.trace?.step(
444
- "walkEndedWithoutOffer",
445
- [rItem(closed_.product, "answer")],
446
- closed_.remainder.map(([a, b]) =>
447
- rItem(query.subarray(a, b), "uncovered")
448
- ),
449
- `the walk ended with ${
450
- unaccountedBytes(closed_.remainder)
451
- } byte(s) unaccounted and no further offer — no structural continuation`,
523
+ // as exhaustion and exhaustion must be a statement, not an absence. A walk a
524
+ // guard stopped reports nothing here: the guard already said why (or, for the
525
+ // answered-directly guard, the answer IS the read-out), exactly as before the
526
+ // walk became a layer.
527
+ const onEnd = (d0: DerivationState, closed_: DerivationState): void => {
528
+ if (stopped) return;
529
+ t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
530
+ if (closed_.remainder.length === 0) {
531
+ ctx.trace?.step(
532
+ "walkClosed",
533
+ [rItem(closed_.product, "answer")],
534
+ [],
535
+ "the derivation is closed — nothing is left unaccounted for",
536
+ );
537
+ } else {
538
+ ctx.trace?.step(
539
+ "walkEndedWithoutOffer",
540
+ [rItem(closed_.product, "answer")],
541
+ closed_.remainder.map(([a, b]) =>
542
+ rItem(query.subarray(a, b), "uncovered")
543
+ ),
544
+ `the walk ended with ${
545
+ unaccountedBytes(closed_.remainder)
546
+ } byte(s) unaccounted and no further offer — no structural continuation`,
547
+ );
548
+ }
549
+
550
+ // INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
551
+ // contract 1: a counter never reaches a decision). They are what a caller
552
+ // needs to PRICE the extension instead of taking it unconditionally, and both
553
+ // are now read off the state the law advanced rather than accumulated beside
554
+ // it: the work it did is the cost it accumulated, and the material it carried
555
+ // is the accounting the law's witnesses added.
556
+ const steps = closed_.cost - d0.cost;
557
+ if (ctx.meter) {
558
+ ctx.meter.reasonSteps += steps;
559
+ // The accounting the law's witnesses added, summed by the law's own function
560
+ // over the tail of the state's list — and computed ONLY when a meter is
561
+ // attached: this used to slice and MAP a fresh array on every response, for a
562
+ // counter that usually does not exist. One allocation, under the meter.
563
+ ctx.meter.reasonAccountedBytes += unaccountedBytes(
564
+ closed_.accounted.slice(d0.accounted.length),
565
+ );
566
+ }
567
+ t?.done(
568
+ [rItem(
569
+ closed_.product,
570
+ "answer",
571
+ resolve(ctx, closed_.product) ?? undefined,
572
+ )],
573
+ // A FIXPOINT: no further step was offered, or the law refused the one that
574
+ // was. There is no allowance to exhaust, so the note is true by
575
+ // construction.
576
+ "the multi-hop chain's fixpoint",
452
577
  );
578
+ };
579
+
580
+ return { name: "reason", offer, onTaken, onRefused, onEnd };
581
+ }
582
+
583
+ /** In how many separate PLACES of the query a stored context shares content at
584
+ * window scale — the query's W-byte windows the context's bytes contain,
585
+ * clustered by {@link countClusters} at the same quantum. Below one window
586
+ * byte identity is chance (identityBar), so only whole windows count.
587
+ *
588
+ * It is the dispersion question asked of the CONTEXT rather than of the votes
589
+ * that happened to land on it. A region votes once, for its top anchor, so
590
+ * where a root's votes come from is a lossy witness of where its context and
591
+ * the query agree — measured on test/24's gap-3.1 fixture: the second topic's
592
+ * second cluster was the region `ace and `, in the OTHER topic's words,
593
+ * voting through a 2-byte sub-window anchor `e ` (attention.ts
594
+ * canonicalChunkId); once that stopped voting, the genuine topic read as one
595
+ * cluster. Its context still shares the system prompt AND its own wording
596
+ * with the query — two places, the two test/37 documents — while a
597
+ * coincidental echo (one closing phrase byte-identical to an unrelated
598
+ * conversation's end, test/37) shares one. */
599
+ function sharedPlaces(
600
+ context: Uint8Array,
601
+ query: Uint8Array,
602
+ quantum: number,
603
+ ): number {
604
+ if (context.length < quantum || query.length < quantum) return 0;
605
+ const windows = new Set<string>();
606
+ for (let o = 0; o + quantum <= context.length; o++) {
607
+ windows.add(latin1(context.subarray(o, o + quantum)));
608
+ }
609
+ const spans: Array<[number, number]> = [];
610
+ for (let o = 0; o + quantum <= query.length; o++) {
611
+ if (windows.has(latin1(query.subarray(o, o + quantum)))) {
612
+ spans.push([o, o + quantum]);
613
+ }
453
614
  }
615
+ return countClusters(spans, quantum);
616
+ }
454
617
 
455
- // INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
456
- // contract 1: a counter never reaches a decision). They are what a caller
457
- // needs to PRICE the extension instead of taking it unconditionally, and both
458
- // are now read off the state the law advanced rather than accumulated beside
459
- // it: the work it did is the cost it accumulated, and the material it carried
460
- // is the accounting the law's witnesses added.
461
- const steps = closed_.cost - d0.cost;
462
- if (ctx.meter) {
463
- ctx.meter.reasonSteps += steps;
464
- // The accounting the law's witnesses added, summed by the law's own function
465
- // over the tail of the state's list — and computed ONLY when a meter is
466
- // attached: this used to slice and MAP a fresh array on every response, for a
467
- // counter that usually does not exist. One allocation, under the meter.
468
- ctx.meter.reasonAccountedBytes += unaccountedBytes(
469
- closed_.accounted.slice(d0.accounted.length),
470
- );
618
+ /** Whether any query byte lies at least `quantum` away from every span — the
619
+ * only place a root independent of those spans could stand. The spans,
620
+ * widened by the quantum on each side, either cover [0, len) or leave a gap. */
621
+ function roomBeyond(
622
+ len: number,
623
+ spans: ReadonlyArray<Span>,
624
+ quantum: number,
625
+ ): boolean {
626
+ if (spans.length === 0) return len > 0;
627
+ const widened = spans
628
+ .map(([s, e]): [number, number] => [s - quantum, e + quantum])
629
+ .sort((a, b) => a[0] - b[0]);
630
+ let reach = 0; // every byte below `reach` is within a quantum of a span
631
+ for (const [s, e] of widened) {
632
+ if (s > reach) return true;
633
+ reach = Math.max(reach, e);
634
+ if (reach >= len) return false;
471
635
  }
472
- t?.done(
473
- [rItem(
474
- closed_.product,
475
- "answer",
476
- resolve(ctx, closed_.product) ?? undefined,
477
- )],
478
- // A FIXPOINT: no further step was offered, or the law refused the one that
479
- // was. There is no allowance to exhaust, so the note is true by
480
- // construction.
481
- "the multi-hop chain's fixpoint",
482
- );
483
- return closed_;
636
+ return reach < len;
484
637
  }
485
638
 
486
639
  /** Fuse independent points of attention into one answer (multi-topic).
487
640
  * When the consensus climb finds more than one dominant point, each
488
641
  * independent point grounds its own answer; they are bridged together
489
642
  * by any learnt connector the graph holds between them. */
490
- export async function fuseAttention(
643
+ export function fuseAttention(
491
644
  ctx: MindContext,
492
645
  query: Uint8Array,
493
646
  state: DerivationState,
494
647
  pre: Precomputed,
648
+ unclimbed = false,
649
+ primarySpans: ReadonlyArray<Span> = [],
650
+ ): Promise<DerivationState> {
651
+ return closeOver(state, query, ctx.space.maxGroup, [
652
+ fusionLayer(ctx, query, pre, unclimbed, primarySpans),
653
+ ]);
654
+ }
655
+
656
+ /** Fusion as a {@link ClosureLayer}: ONE transition, offered once — the fused
657
+ * product of the independent points of attention, or nothing. The law admits
658
+ * it (`reaches`: the fusion composed structure this derivation had not stood
659
+ * on); the layer's own structural gates below decide whether there is a
660
+ * fusion to offer at all. */
661
+ export function fusionLayer(
662
+ ctx: MindContext,
663
+ query: Uint8Array,
664
+ pre: Precomputed,
495
665
  /** True when `primary` never touched the consensus climb at all — e.g. a
496
666
  * pure ALU computation, which has no anchor of its own. commitVotes
497
667
  * ALWAYS admits the dominant root regardless of its vote (attention.ts:
@@ -506,212 +676,234 @@ export async function fuseAttention(
506
676
  * fuseAttention just reads a position from it. Empty or absent preserves
507
677
  * the original behaviour exactly. */
508
678
  primarySpans: ReadonlyArray<Span> = [],
509
- ): Promise<DerivationState> {
510
- // THE STATE, NOT BARE BYTES: fusion is one more transition of the derivation
511
- // the walk returned, so it reads that state's product and hands back a state.
512
- // Its own structural gates stay here — this is the layer that can resolve
513
- // those witnesses, and the law never re-derives one.
514
- const primary = state.product;
515
- // When the answer is structurally drawn from the query itself
516
- // (extraction), it already spans all the query's pieces — fusion
517
- // would only add noise from unrelated stored contexts. The gate is
518
- // STRICT containment (resolved node in the query's tree, or a contiguous
519
- // byte run): the old sparse-subsequence test was trivially satisfied by
520
- // short answers over long queries, silently starving multi-topic queries
521
- // of fusion.
522
- if (containsSpan(ctx, query, primary)) return state;
679
+ ): ClosureLayer {
680
+ let offered = false;
681
+ const offer: Offer = async (state) => {
682
+ if (offered) return null;
683
+ offered = true;
684
+ // THE STATE, NOT BARE BYTES: fusion is one more transition of the derivation
685
+ // the walk returned, so it reads that state's product and hands back a state.
686
+ // Its own structural gates stay here — this is the layer that can resolve
687
+ // those witnesses, and the law never re-derives one.
688
+ const primary = state.product;
689
+ // When the answer is structurally drawn from the query itself
690
+ // (extraction), it already spans all the query's pieces — fusion
691
+ // would only add noise from unrelated stored contexts. The gate is
692
+ // STRICT containment (resolved node in the query's tree, or a contiguous
693
+ // byte run): the old sparse-subsequence test was trivially satisfied by
694
+ // short answers over long queries, silently starving multi-topic queries
695
+ // of fusion.
696
+ if (containsSpan(ctx, query, primary)) return null;
523
697
 
524
- // The committed points of attention ARE the shared climb's roots (same
525
- // query, same k, same DF mode) — read them from Precomputed instead of
526
- // re-climbing, so even a traced response pays for the climb once.
527
- const forest = (await pre.attention()).roots;
528
- // A LONE root is ordinarily primary's own source — nothing to fuse. But
529
- // when primary is unclimbed, the lone root was never checked against
530
- // anything: it is admitted by commitVotes unconditionally, so it may be
531
- // genuine consensus (Attention.breadth dominates — most of the query's
532
- // OWN regions corroborate it) or a coincidental echo (breadth does not
533
- // dominate — see test/35-attention-confidence). breadth is the SCALE-
534
- // INVARIANT read of exactly this question: the raw IDF vote cannot serve
535
- // here, since it is an absolute ln(N)-scaled quantity (a genuine root on
536
- // a large store can score BELOW its own floor while a coincidental echo
537
- // on a small one scores comfortably above its own, smaller, floor).
538
- //
539
- // Breadth alone is not enough when primary is a pure COMPUTATION. The ALU
540
- // answers "2+2 equals what?" with 4, and the store's own arithmetic table
541
- // then supplies a lone root — an exemplar like "1+2" — whose breadth
542
- // dominates because it is corroborated by the computation's OWN bytes.
543
- // Fusing it projected that exemplar's continuation and the bridge voiced
544
- // "4+3" (test/11 seed 99). A second point of attention must stand on
545
- // evidence that is structurally SEPARATE from primary's: at least one
546
- // perceptual quantum of query between them, the same separation
547
- // countClusters uses to tell independent evidence neighbourhoods apart.
548
- // Not a score, and not a tuned bar — the fold's own quantum.
549
- //
550
- // With no primarySpans (the caller did not resolve them) every span
551
- // vacuously qualifies, preserving the original behaviour exactly.
552
- const quantum = ctx.space.maxGroup;
553
- const independentOfPrimary = (root: Attention): boolean =>
554
- primarySpans.every(([s, e]) => {
555
- const gap = root.end <= s
556
- ? s - root.end
557
- : e <= root.start
558
- ? root.start - e
559
- : 0;
560
- return gap >= quantum;
561
- });
562
- const lonePromotes = unclimbed && forest.length === 1 &&
563
- forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
564
- if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
565
- return state;
566
- }
698
+ // A SECOND POINT OF ATTENTION MUST STAND ON EVIDENCE STRUCTURALLY SEPARATE
699
+ // FROM PRIMARY'S: at least one perceptual quantum of query between them,
700
+ // the same separation countClusters uses to tell independent evidence
701
+ // neighbourhoods apart. Not a score, and not a tuned bar — the fold's own
702
+ // quantum. A root standing on primary's own evidence is primary's topic
703
+ // read again, never a further one, so it is not fused (measured on the
704
+ // 31.7M-node store: an exact dialogue turn grounded whole, `[0,79]`, paid a
705
+ // 3.7 s consensus climb whose only root lay inside that span).
706
+ //
707
+ // With no primarySpans (the caller did not resolve them) every span
708
+ // vacuously qualifies, preserving the original behaviour exactly.
709
+ const quantum = ctx.space.maxGroup;
710
+ const independentOfPrimary = (root: Attention): boolean =>
711
+ primarySpans.every(([s, e]) => {
712
+ const gap = root.end <= s
713
+ ? s - root.end
714
+ : e <= root.start
715
+ ? root.start - e
716
+ : 0;
717
+ return gap >= quantum;
718
+ });
719
+ // Decided BEFORE the climb when it is already decided: a root spans at
720
+ // least one query byte, so where every byte lies within a quantum of
721
+ // primary's evidence no root can be independent, and there is nothing the
722
+ // climb could hand this layer.
723
+ if (!roomBeyond(query.length, primarySpans, quantum)) return null;
567
724
 
568
- // WHERE THE QUERY ASKED FOR IT. The sort below orders the fused pieces by
569
- // query position, which is the whole point of the `start` field: a
570
- // multi-topic answer should read in the order the question posed its
571
- // topics. Every ROOT carries its own start. `primary` did not — it was
572
- // given forest[0].start, the FIRST attention root's position, which is
573
- // primary's own source only when primary happens to come from that root.
574
- // When it does not, primary is sorted to a position it never occupied.
575
- //
576
- // Observed live: "What is the capital of France? And what is 2 + 2?"
577
- // answered "4The capital city of France is Paris." — the ALU result, whose
578
- // evidence is the "2 + 2" span near the END of the query, inherited the
579
- // France root's start of 0 and sorted ahead of the France answer. Both
580
- // pieces were right; only the order was.
581
- //
582
- // primary's own position is the earliest query byte its grounding stands
583
- // on. `accounted` is the cost-ladder read of that and is authoritative
584
- // when non-empty; when it is empty the grounding is a pure COMPUTATION,
585
- // whose evidence is its computed span — the same cost-ladder-vs-coverage
586
- // distinction think() already draws for the fusion remainder ("`accounted`
587
- // alone undercounts this ... cover prices its computed spans at near-zero
588
- // and deliberately leaves them out"), read here for position instead of
589
- // for coverage. With neither, nothing is known and the old behaviour
590
- // (forest[0].start) stands.
591
- const primaryStart = primarySpans.length > 0
592
- ? primarySpans.reduce((m, [s]) => Math.min(m, s), Infinity)
593
- : forest[0].start;
594
- const pieces: Array<{ start: number; bytes: Uint8Array }> = [
595
- { start: primaryStart, bytes: primary },
596
- ];
597
- const qv = pre.guide; // once, not per root
598
- const rest = lonePromotes ? forest : forest.slice(1);
599
- const t = ctx.trace?.enter("fuseAttention", [
600
- rItem(primary, "primary"),
601
- ...rest.map((r) => rNode(ctx, r.anchor, "point", r.vote)),
602
- ]);
603
- for (const root of rest) {
604
- // DISPERSION: this root's contributing regions are confined to a
605
- // single cluster (see Attention.clusters) — one local neighbourhood of
606
- // the query, not several separate places. Raw region count already
607
- // failed to discriminate a coincidental match from a genuine further
608
- // topic (test/24 gap 3.1 vs test/35's echo); dispersion is a different
609
- // question — not how MUCH evidence, but how many separate PLACES in the
610
- // query corroborate it — and a coincidental match is structurally
611
- // confined to one cluster no matter how strongly it resonates.
725
+ // The committed points of attention ARE the shared climb's roots (same
726
+ // query, same k, same DF mode) — read them from Precomputed instead of
727
+ // re-climbing, so even a traced response pays for the climb once.
728
+ const forest = (await pre.attention()).roots;
729
+ // A LONE root is ordinarily primary's own source — nothing to fuse. But
730
+ // when primary is unclimbed, the lone root was never checked against
731
+ // anything: it is admitted by commitVotes unconditionally, so it may be
732
+ // genuine consensus (Attention.breadth dominates — most of the query's
733
+ // OWN regions corroborate it) or a coincidental echo (breadth does not
734
+ // dominate — see test/35-attention-confidence). breadth is the SCALE-
735
+ // INVARIANT read of exactly this question: the raw IDF vote cannot serve
736
+ // here, since it is an absolute ln(N)-scaled quantity (a genuine root on
737
+ // a large store can score BELOW its own floor while a coincidental echo
738
+ // on a small one scores comfortably above its own, smaller, floor).
612
739
  //
613
- // EXCEPTION: crossRegionVotes' own joint conclusions (a query naming
614
- // two attributes that were only ever learnt TOGETHER — test/34's own
615
- // binding corpus) are inherently ONE fused context and are pooled from
616
- // a single synthetic region, so they always read as one cluster even
617
- // though they already, by construction, account for both original
618
- // mentions. `breadth` (dominates — the same > half-the-query bar used
619
- // everywhere else) still correctly recognises these: a genuine joint
620
- // binding explains the MAJORITY of the query's regions on its own,
621
- // which a coincidental echo never does (verified: test/35's echo tops
622
- // out at 0.40). So a root is trusted when EITHER measure alone
623
- // indicates real signal — excluded only when BOTH are weak. Cheap and
624
- // synchronous — checked before the async already-answered walk below.
625
- if (root.clusters < 2 && root.breadth <= 0.5) {
626
- ctx.trace?.step(
627
- "singleCluster",
628
- [rNode(ctx, root.anchor, "point", root.vote)],
629
- [],
630
- "this point's evidence is confined to one local neighbourhood of the query — not trusted as an independent topic",
631
- );
632
- continue;
740
+ // Breadth alone is not enough when primary is a pure COMPUTATION. The ALU
741
+ // answers "2+2 equals what?" with 4, and the store's own arithmetic table
742
+ // then supplies a lone root — an exemplar like "1+2" — whose breadth
743
+ // dominates because it is corroborated by the computation's OWN bytes.
744
+ // Fusing it projected that exemplar's continuation and the bridge voiced
745
+ // "4+3" (test/11 seed 99) — the separation above is what refuses it.
746
+ const lonePromotes = unclimbed && forest.length === 1 &&
747
+ forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
748
+ if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
749
+ return null;
633
750
  }
634
- // ALREADY ANSWERED: this root's own learnt continuation — the same
635
- // content-addressed walk reason()'s echo guard already trusts
636
- // (`ctx.store.prevCount(qId) > 0`), here applied per-candidate instead
637
- // of to the whole query — is VERBATIM present later in the query. A
638
- // query that embeds both an exchange's ask and its own already-given
639
- // reply (a conversation's turn plus its own prior answer, concatenated
640
- // raw by addTurn — or any caller pasting a transcript into one
641
- // respond() call; the check is Mind-bookkeeping-free, so it treats both
642
- // identically) has already spoken this root's answer — fusing it in
643
- // again would only restate it. Deliberately NOT a magnitude measure:
644
- // it fires on exact content-addressed recurrence, not on how strongly
645
- // the root resonates.
646
- const cont = await follow(ctx, root.anchor, qv);
647
- if (
648
- cont !== null && cont.length > 0 &&
649
- // The law's positional reading: the caller knows the material it is
650
- // looking for lies AT OR AFTER this root, which is the witness; the law
651
- // owns the containment test itself. The `cont.length > 0` guard stays
652
- // here because an EMPTY continuation indexes at every offset — the law
653
- // has no opinion about whether "nothing" counts as said.
654
- restates(query, cont, 0, { from: root.end })
655
- ) {
656
- ctx.trace?.step(
657
- "alreadyAnswered",
658
- [rNode(ctx, root.anchor, "point", root.vote)],
659
- [rItem(cont, "continuation")],
660
- "this point's own learnt continuation already appears later in the query — already answered, not fused",
751
+
752
+ // WHERE THE QUERY ASKED FOR IT. The sort below orders the fused pieces by
753
+ // query position, which is the whole point of the `start` field: a
754
+ // multi-topic answer should read in the order the question posed its
755
+ // topics. Every ROOT carries its own start. `primary` did not — it was
756
+ // given forest[0].start, the FIRST attention root's position, which is
757
+ // primary's own source only when primary happens to come from that root.
758
+ // When it does not, primary is sorted to a position it never occupied.
759
+ //
760
+ // Observed live: "What is the capital of France? And what is 2 + 2?"
761
+ // answered "4The capital city of France is Paris." — the ALU result, whose
762
+ // evidence is the "2 + 2" span near the END of the query, inherited the
763
+ // France root's start of 0 and sorted ahead of the France answer. Both
764
+ // pieces were right; only the order was.
765
+ //
766
+ // primary's own position is the earliest query byte its grounding stands
767
+ // on. `accounted` is the cost-ladder read of that and is authoritative
768
+ // when non-empty; when it is empty the grounding is a pure COMPUTATION,
769
+ // whose evidence is its computed span — the same cost-ladder-vs-coverage
770
+ // distinction think() already draws for the fusion remainder ("`accounted`
771
+ // alone undercounts this ... cover prices its computed spans at near-zero
772
+ // and deliberately leaves them out"), read here for position instead of
773
+ // for coverage. With neither, nothing is known and the old behaviour
774
+ // (forest[0].start) stands.
775
+ const primaryStart = primarySpans.length > 0
776
+ ? primarySpans.reduce((m, [s]) => Math.min(m, s), Infinity)
777
+ : forest[0].start;
778
+ const pieces: Array<{ start: number; bytes: Uint8Array }> = [
779
+ { start: primaryStart, bytes: primary },
780
+ ];
781
+ const qv = pre.guide; // once, not per root
782
+ const rest = (lonePromotes ? forest : forest.slice(1)).filter(
783
+ independentOfPrimary,
784
+ );
785
+ if (rest.length === 0) return null;
786
+ const t = ctx.trace?.enter("fuseAttention", [
787
+ rItem(primary, "primary"),
788
+ ...rest.map((r) => rNode(ctx, r.anchor, "point", r.vote)),
789
+ ]);
790
+ for (const root of rest) {
791
+ // DISPERSION: this root's contributing regions are confined to a
792
+ // single cluster (see Attention.clusters) — one local neighbourhood of
793
+ // the query, not several separate places. Raw region count already
794
+ // failed to discriminate a coincidental match from a genuine further
795
+ // topic (test/24 gap 3.1 vs test/35's echo); dispersion is a different
796
+ // question — not how MUCH evidence, but how many separate PLACES in the
797
+ // query corroborate it — and a coincidental match is structurally
798
+ // confined to one cluster no matter how strongly it resonates.
799
+ //
800
+ // EXCEPTION: crossRegionVotes' own joint conclusions (a query naming
801
+ // two attributes that were only ever learnt TOGETHER — test/34's own
802
+ // binding corpus) are inherently ONE fused context and are pooled from
803
+ // a single synthetic region, so they always read as one cluster even
804
+ // though they already, by construction, account for both original
805
+ // mentions. `breadth` (dominates — the same > half-the-query bar used
806
+ // everywhere else) still correctly recognises these: a genuine joint
807
+ // binding explains the MAJORITY of the query's regions on its own,
808
+ // which a coincidental echo never does (verified: test/35's echo tops
809
+ // out at 0.40). So a root is trusted when EITHER measure alone
810
+ // indicates real signal — excluded only when BOTH are weak. Cheap and
811
+ // synchronous — checked before the async already-answered walk below.
812
+ if (
813
+ root.clusters < 2 && root.breadth <= 0.5 &&
814
+ sharedPlaces(read(ctx, root.anchor), query, quantum) < 2
815
+ ) {
816
+ ctx.trace?.step(
817
+ "singleCluster",
818
+ [rNode(ctx, root.anchor, "point", root.vote)],
819
+ [],
820
+ "this point's evidence is confined to one local neighbourhood of the query — not trusted as an independent topic",
821
+ );
822
+ continue;
823
+ }
824
+ // ALREADY ANSWERED: this root's own learnt continuation — the same
825
+ // content-addressed walk reason()'s echo guard already trusts
826
+ // (`ctx.store.prevCount(qId) > 0`), here applied per-candidate instead
827
+ // of to the whole query — is VERBATIM present later in the query. A
828
+ // query that embeds both an exchange's ask and its own already-given
829
+ // reply (a conversation's turn plus its own prior answer, concatenated
830
+ // raw by addTurn — or any caller pasting a transcript into one
831
+ // respond() call; the check is Mind-bookkeeping-free, so it treats both
832
+ // identically) has already spoken this root's answer — fusing it in
833
+ // again would only restate it. Deliberately NOT a magnitude measure:
834
+ // it fires on exact content-addressed recurrence, not on how strongly
835
+ // the root resonates.
836
+ const cont = await follow(ctx, root.anchor, qv);
837
+ if (
838
+ cont !== null && cont.length > 0 &&
839
+ // The law's positional reading: the caller knows the material it is
840
+ // looking for lies AT OR AFTER this root, which is the witness; the law
841
+ // owns the containment test itself. The `cont.length > 0` guard stays
842
+ // here because an EMPTY continuation indexes at every offset — the law
843
+ // has no opinion about whether "nothing" counts as said.
844
+ restates(query, cont, 0, { from: root.end })
845
+ ) {
846
+ ctx.trace?.step(
847
+ "alreadyAnswered",
848
+ [rNode(ctx, root.anchor, "point", root.vote)],
849
+ [rItem(cont, "continuation")],
850
+ "this point's own learnt continuation already appears later in the query — already answered, not fused",
851
+ );
852
+ continue;
853
+ }
854
+ const g = await project(ctx, root.anchor, qv);
855
+ if (g === null || g.length === 0) continue;
856
+ if (pieces.some((p) => indexOf(p.bytes, g, 0) >= 0)) continue;
857
+ pieces.push({ start: root.start, bytes: g });
858
+ }
859
+ if (pieces.length === 1) {
860
+ t?.done(
861
+ [rItem(primary, "answer")],
862
+ "no further independent point grounded",
661
863
  );
662
- continue;
864
+ return null;
865
+ }
866
+
867
+ pieces.sort((a, b) => a.start - b.start);
868
+ let out = pieces[0].bytes;
869
+ for (let i = 1; i < pieces.length; i++) {
870
+ // An approximate-resonance miss (or a genuinely unlearnt junction) joins
871
+ // the pieces bare — joinWithBridge surfaces it as a bridgeMiss step.
872
+ out = await joinWithBridge(ctx, out, pieces[i].bytes);
663
873
  }
664
- const g = await project(ctx, root.anchor, qv);
665
- if (g === null || g.length === 0) continue;
666
- if (pieces.some((p) => indexOf(p.bytes, g, 0) >= 0)) continue;
667
- pieces.push({ start: root.start, bytes: g });
668
- }
669
- if (pieces.length === 1) {
670
874
  t?.done(
671
- [rItem(primary, "answer")],
672
- "no further independent point grounded",
875
+ [rItem(out, "answer", resolve(ctx, out) ?? undefined)],
876
+ `fused ${pieces.length} independent points of attention into one answer`,
673
877
  );
674
- return state;
675
- }
676
-
677
- pieces.sort((a, b) => a.start - b.start);
678
- let out = pieces[0].bytes;
679
- for (let i = 1; i < pieces.length; i++) {
680
- // An approximate-resonance miss (or a genuinely unlearnt junction) joins
681
- // the pieces bare — joinWithBridge surfaces it as a bridgeMiss step.
682
- out = await joinWithBridge(ctx, out, pieces[i].bytes);
683
- }
684
- t?.done(
685
- [rItem(out, "answer", resolve(ctx, out) ?? undefined)],
686
- `fused ${pieces.length} independent points of attention into one answer`,
687
- );
688
- // THE FACT IS THE FUSED ANSWER, not the call: every early return above hands
689
- // back `primary` untouched. Untraced on purpose (meter.ts contract 1).
690
- if (ctx.meter) ctx.meter.fuseRuns++;
691
- // A FUSION IS A TRANSITION, and the only one in the engine that can splice the
692
- // QUESTION'S OWN material into the product. So it is OFFERED to the law rather
693
- // than built by hand: the law reads the window the fused product holds — the
694
- // same reading the carries admission uses, one definition — accounts for the
695
- // span it carried, and lets the question's remainder drain on EVIDENCE. A
696
- // fusion that carries nothing consumes nothing, because the reading finds no
697
- // window; and `reaches` is declared only when the fusion composed another root,
698
- // which is structure this derivation had not stood on.
699
- const fusedT: Continuation = {
700
- product: out,
701
- contains: true,
702
- reaches: rest.length > 0,
703
- cost: STEP,
878
+ // THE FACT IS THE FUSED ANSWER, not the call: every early return above hands
879
+ // back `primary` untouched. Untraced on purpose (meter.ts contract 1).
880
+ if (ctx.meter) ctx.meter.fuseRuns++;
881
+ // A FUSION IS A TRANSITION, and the only one in the engine that can splice the
882
+ // QUESTION'S OWN material into the product. So it is OFFERED to the law rather
883
+ // than built by hand: the law reads the window the fused product holds — the
884
+ // same reading the carries admission uses, one definition — accounts for the
885
+ // span it carried, and lets the question's remainder drain on EVIDENCE. A
886
+ // fusion that carries nothing consumes nothing, because the reading finds no
887
+ // window; and `reaches` is declared only when the fusion composed another root,
888
+ // which is structure this derivation had not stood on.
889
+ //
890
+ // THE LAW'S REFUSAL IS FINAL. The only state it refuses a fusion against is a
891
+ // supplied fixed point (`reaches` is always declared here), and the engine
892
+ // enters no layer for one — so there is no hand-built fallback: a fusion the
893
+ // law refuses is not taken (measured: no response in the suite reaches the
894
+ // refusal).
895
+ const fusedT: Continuation = {
896
+ product: out,
897
+ contains: true,
898
+ reaches: rest.length > 0,
899
+ cost: STEP,
900
+ };
901
+ return fusedT;
902
+ };
903
+ return {
904
+ name: "fuse",
905
+ offer,
906
+ onTaken: (before, after, witnesses) =>
907
+ meterTransition(ctx, before, after, witnesses),
704
908
  };
705
- const fusedWitness = admissible(state, fusedT, query, ctx.space.maxGroup);
706
- if (fusedWitness === null) return { ...state, product: out };
707
- const fused = advance(state, fusedT, fusedWitness);
708
- if (ctx.meter) {
709
- ctx.meter.reasonCarriedBytes += fusedWitness.reduce(
710
- (n, w) => n + (w.window ? w.window[1] - w.window[0] : 0),
711
- 0,
712
- );
713
- ctx.meter.closureDrainedBytes += unaccountedBytes(state.remainder) -
714
- unaccountedBytes(fused.remainder);
715
- }
716
- return fused;
717
909
  }