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