@hviana/sema 0.8.2 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/AGENTS.md +38 -37
  2. package/README.md +17 -38
  3. package/TRADEMARKS.md +0 -1
  4. package/dist/example/demo.js +85 -34
  5. package/dist/src/config.d.ts +11 -0
  6. package/dist/src/config.js +2 -0
  7. package/dist/src/geometry.d.ts +21 -10
  8. package/dist/src/geometry.js +21 -12
  9. package/dist/src/meter.d.ts +62 -0
  10. package/dist/src/meter.js +62 -0
  11. package/dist/src/mind/articulation.js +1 -1
  12. package/dist/src/mind/attention.d.ts +4 -0
  13. package/dist/src/mind/attention.js +167 -17
  14. package/dist/src/mind/canonical.d.ts +16 -0
  15. package/dist/src/mind/canonical.js +41 -0
  16. package/dist/src/mind/derivation.d.ts +201 -0
  17. package/dist/src/mind/derivation.js +327 -0
  18. package/dist/src/mind/graph-search.d.ts +2 -1
  19. package/dist/src/mind/graph-search.js +70 -29
  20. package/dist/src/mind/match.d.ts +3 -1
  21. package/dist/src/mind/match.js +7 -3
  22. package/dist/src/mind/mechanisms/alu.js +0 -2
  23. package/dist/src/mind/mechanisms/cast.d.ts +1 -5
  24. package/dist/src/mind/mechanisms/cast.js +16 -19
  25. package/dist/src/mind/mechanisms/confluence.d.ts +0 -3
  26. package/dist/src/mind/mechanisms/confluence.js +27 -9
  27. package/dist/src/mind/mechanisms/cover.js +17 -20
  28. package/dist/src/mind/mechanisms/extraction.d.ts +0 -1
  29. package/dist/src/mind/mechanisms/extraction.js +13 -8
  30. package/dist/src/mind/mechanisms/prefix-completion.js +0 -1
  31. package/dist/src/mind/mechanisms/recall.d.ts +0 -1
  32. package/dist/src/mind/mechanisms/recall.js +40 -13
  33. package/dist/src/mind/mechanisms/reference.js +3 -4
  34. package/dist/src/mind/mind.d.ts +4 -2
  35. package/dist/src/mind/mind.js +5 -4
  36. package/dist/src/mind/pipeline-mechanism.d.ts +7 -3
  37. package/dist/src/mind/pipeline.js +136 -44
  38. package/dist/src/mind/primitives.js +9 -1
  39. package/dist/src/mind/rationale.d.ts +21 -5
  40. package/dist/src/mind/rationale.js +16 -21
  41. package/dist/src/mind/reasoning.d.ts +12 -20
  42. package/dist/src/mind/reasoning.js +190 -106
  43. package/dist/src/mind/recognition.js +4 -8
  44. package/dist/src/mind/resonance.js +20 -1
  45. package/dist/src/mind/trace.js +1 -0
  46. package/dist/src/mind/traverse.js +6 -2
  47. package/dist/src/mind/types.d.ts +36 -13
  48. package/dist/src/mind/types.js +6 -3
  49. package/docs/INDEX.md +23 -24
  50. package/docs/INVARIANTS.md +16 -17
  51. package/docs/architecture/bounded-reads.md +5 -5
  52. package/docs/architecture/closure.md +65 -0
  53. package/docs/architecture/commonality.md +29 -20
  54. package/docs/architecture/cost-model.md +7 -7
  55. package/docs/architecture/determinism.md +7 -7
  56. package/docs/architecture/exact-vs-approximate.md +4 -4
  57. package/docs/architecture/factored-machinery.md +14 -14
  58. package/docs/architecture/match-project.md +2 -3
  59. package/docs/architecture/mechanism-market.md +16 -16
  60. package/docs/architecture/meter.md +10 -11
  61. package/docs/architecture/store.md +4 -4
  62. package/docs/architecture/thresholds.md +1 -1
  63. package/docs/failures/tempting-but-wrong.md +14 -5
  64. package/docs/harness/gates.md +7 -7
  65. package/docs/mechanisms/cast.md +2 -2
  66. package/docs/mechanisms/cover.md +4 -5
  67. package/docs/mechanisms/extraction.md +7 -7
  68. package/docs/mechanisms/recall.md +8 -9
  69. package/example/demo.ts +90 -37
  70. package/jsr.json +1 -1
  71. package/package.json +1 -1
  72. package/src/alu/README.md +11 -12
  73. package/src/config.ts +13 -0
  74. package/src/geometry.ts +21 -13
  75. package/src/meter.ts +62 -0
  76. package/src/mind/articulation.ts +0 -1
  77. package/src/mind/attention.ts +169 -17
  78. package/src/mind/canonical.ts +43 -0
  79. package/src/mind/derivation.ts +473 -0
  80. package/src/mind/graph-search.ts +76 -34
  81. package/src/mind/match.ts +7 -3
  82. package/src/mind/mechanisms/alu.ts +0 -2
  83. package/src/mind/mechanisms/cast.ts +20 -22
  84. package/src/mind/mechanisms/confluence.ts +27 -13
  85. package/src/mind/mechanisms/cover.ts +17 -20
  86. package/src/mind/mechanisms/extraction.ts +13 -9
  87. package/src/mind/mechanisms/prefix-completion.ts +0 -1
  88. package/src/mind/mechanisms/recall.ts +39 -13
  89. package/src/mind/mechanisms/reference.ts +2 -3
  90. package/src/mind/mind.ts +6 -4
  91. package/src/mind/pipeline-mechanism.ts +7 -3
  92. package/src/mind/pipeline.ts +160 -52
  93. package/src/mind/primitives.ts +9 -1
  94. package/src/mind/rationale.ts +27 -23
  95. package/src/mind/reasoning.ts +227 -120
  96. package/src/mind/recognition.ts +4 -8
  97. package/src/mind/resonance.ts +19 -1
  98. package/src/mind/trace.ts +1 -0
  99. package/src/mind/traverse.ts +7 -5
  100. package/src/mind/types.ts +41 -15
  101. package/test/105-derive-through-reports-its-refusal.test.mjs +24 -0
  102. package/test/118-the-join-reaches-a-key-off-the-cut.test.mjs +74 -0
  103. package/test/119-the-work-does-not-grow-with-the-corpus.test.mjs +122 -0
  104. package/test/120-composition-is-consequence.test.mjs +132 -0
  105. package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +128 -0
  106. package/test/122-the-climb-search-does-not-grow-with-the-corpus.test.mjs +117 -0
  107. package/test/123-the-paired-formulas-agree.test.mjs +90 -0
  108. package/test/125-the-post-grounding-branch-publishes-its-operand.test.mjs +51 -0
  109. package/test/126-the-pipeline-does-not-name-mechanisms.test.mjs +42 -0
  110. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +83 -0
  111. package/test/129-the-trace-payload-shape.test.mjs +164 -0
  112. package/test/133-the-decision-point-renders-the-state.test.mjs +204 -0
  113. package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
  114. package/test/135-one-law-any-producer.test.mjs +289 -0
  115. package/test/136-the-two-named-limits.test.mjs +205 -0
  116. package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
  117. package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
  118. package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
  119. package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
  120. package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
  121. package/test/32-confluence.test.mjs +68 -0
  122. package/test/36-already-answered-fusion.test.mjs +20 -2
  123. package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
  124. package/test/38-reason-restate-guard.test.mjs +28 -2
  125. package/test/43-cast-analog-seat.test.mjs +10 -0
  126. package/test/55-cost-meter.test.mjs +862 -0
@@ -6,43 +6,41 @@ import { rItem, rNode } from "./trace.js";
6
6
  import { bytesEqual, indexOf } from "../bytes.js";
7
7
  import { resolve } from "./primitives.js";
8
8
  import { hubBound } from "./traverse.js";
9
- import { follow, haloSiblings, project } from "./match.js";
9
+ import { containsSpan, follow, haloSiblings, project } from "./match.js";
10
10
  import { joinWithBridge, pivotInto } from "./resonance.js";
11
- /** Whether `bytes` is a proper byte-subspan of `query` — already present in
12
- * the question, so voicing it back only restates part of what was asked,
13
- * never answers it. The exact guard recallByResonance already applies to
14
- * its OWN grounding candidates (tier 1's `restates`, tier 2's subspan
15
- * check, tier 0b's argument-binding subspan check) — every mechanism that
16
- * walks a LEARNT CONTINUATION EDGE past an already-vetted grounding
17
- * (reason()'s own hops below, and CAST's `projectCounterfactual` seat
18
- * substitution — see cast.ts) needs the same guard applied to what the
19
- * walk turns up, since `follow()`/`chooseNext`/`pivotInto` know nothing of
20
- * the query at all — only of what structurally continues what. */
21
- export function restatesQuery(query, bytes) {
22
- return bytes.length < query.length && indexOf(query, bytes, 0) >= 0;
23
- }
11
+ import { admissible, advance } from "./derivation.js";
12
+ import { closure, restates, unaccountedBytes, } from "./derivation.js";
13
+ import { STEP } from "./graph-search.js";
24
14
  /** Extend a grounded answer forward across facts (multi-hop reasoning).
25
15
  * Pivots on the longest unconsumed learnt context each answer contains,
26
- * then follows the pivot's continuation to the next fact. Repeats up
27
- * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
16
+ * then follows the pivot's continuation to the next fact. **The chain ends
17
+ * when it STOPS, never when a count runs out**: every exit is a refusal (no
18
+ * pivot, no forward step, no question material carried) and the walk is bounded
19
+ * by the material and the graph — `consumed` refuses to revisit a node. There
20
+ * is no hop allowance, so this doc deliberately names no `cfg` capacity: the
21
+ * cover prices every hop at `STEP` and lets the search decide the depth, and a
22
+ * second count here would be a second decision about the same thing.
23
+ * `preConsumed` carries node ids already
28
24
  * spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
29
25
  * the BYTES of the anchors a mechanism declared it voiced (its `used` set),
30
26
  * when it declared one — see the pivot's own containment rule. `pre` is the
31
27
  * response's shared pre-computation — the post-grounding stages read the
32
28
  * same container the mechanisms did. */
33
- export async function reason(ctx, query, answer, preConsumed, pre, voiced = [],
34
- /** The query material the GROUNDING left uncovered — the cost ladder's own
35
- * `unaccounted` spans. Only the reasoner's OWN extensions are judged
36
- * against it; a mechanism carrying its own `used` set owns its shape. */
37
- uncovered = []) {
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;
38
35
  // Echo guard: a query that is ITSELF a learnt continuation (some context's
39
36
  // answer) is being asked back at the system — hopping forward from it would
40
37
  // chain through the very fact that produced it and echo the conversation
41
38
  // back. The grounded answer alone is the honest read-out. Deliberately a
42
39
  // broad structural gate; pinned by test/31-audit.
43
40
  const qId = pre.queryResolved;
44
- if (qId !== null && ctx.store.prevCount(qId) > 0)
45
- return answer;
41
+ if (qId !== null && ctx.store.prevCount(qId) > 0) {
42
+ return d0;
43
+ }
46
44
  // Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
47
45
  // the hub bound, via the store's LIMITed edge reads: a common continuation's
48
46
  // reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
@@ -59,7 +57,7 @@ uncovered = []) {
59
57
  // is nothing left to chain for.
60
58
  //
61
59
  // Every stopping condition in the loop below judges the ANSWER (`consumed` /
62
- // `restatesQuery` / `bytesEqual`); none asks whether the QUESTION was
60
+ // the law's `restates` / `bytesEqual`); none asks whether the QUESTION was
63
61
  // satisfied. So a single-hop question whose answer happens to name another
64
62
  // learnt context extends past a correct answer and REPLACES it:
65
63
  //
@@ -99,7 +97,7 @@ uncovered = []) {
99
97
  ? null
100
98
  : ctx.store.prevFirst(groundedId, bound);
101
99
  if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
102
- return answer;
100
+ return d0;
103
101
  }
104
102
  const consumed = new Set();
105
103
  /** `prev` lets a caller hand in an already-read reverse-edge list — hop 0
@@ -143,106 +141,158 @@ uncovered = []) {
143
141
  else {
144
142
  await preconsume();
145
143
  }
146
- let cur = answer;
144
+ // ── THE WALK, AS THE LAW'S ───────────────────────────────────────────
145
+ //
146
+ // This function no longer decides whether a step may be taken: it OFFERS a
147
+ // continuation — a forward step from the answer's own learnt edge, or a pivot
148
+ // on a learnt context the answer contains — and `derivation.ts`'s law admits
149
+ // or refuses it. The state it offers against is the pipeline's own: the
150
+ // grounding's remainder (what the question still owes), its accounting, its
151
+ // cost.
152
+ //
153
+ // NO ALLOWANCE: THE CHAIN ENDS WHEN IT STOPS. Every exit below is the law —
154
+ // no pivot, no forward step, no question material carried — and the walk is
155
+ // bounded by the material and the graph rather than by a count: the law
156
+ // requires each step to engage what is left (or to move to new structure),
157
+ // and `consumed` refuses to revisit a node. `recallQueryK` no longer bounds
158
+ // the reasoner here; it keeps its other roles (the bridge's candidate reads,
159
+ // the pivot's probe budget, the resonance limits).
160
+ //
161
+ // Measured before removing the allowance: test/89 — the corpus-cost guard, the
162
+ // heaviest case in the suite — is green and no slower without it (27 s against
163
+ // 30 s); and raising it from 12 to 200 changed neither the answer nor
164
+ // `pivotSteps` on the chain fixtures.
147
165
  const qv = pre.guide; // the response-wide guide IS the query's gist
148
- let t;
166
+ const W = ctx.space.maxGroup;
167
+ // WHOSE EXTENSION IS THIS? `voiced` is what the mechanism WITHHELD (the
168
+ // pipeline sends the used anchors' CONTINUATIONS, not their bytes), so a
169
+ // non-empty `voiced` means exactly what that note says: the grounding came
170
+ // from a mechanism that carries its own short `used` set (cast/join) and
171
+ // therefore OWNS the shape of its answer. The further terms inside such a
172
+ // seat are legitimately followable (test/29 C3's `Mona Lisa` lives inside the
173
+ // voiced seat and leads on to a fact about neither analog), which the law
174
+ // expresses as the identity species of progress — `moves` — declared by the
175
+ // reporter and never inferred from the mechanism's name.
176
+ const producerOwnsShape = voiced.length > 0;
149
177
  const startedFrom = answer;
150
- for (let hop = 0; hop < ctx.cfg.recallQueryK; hop++) {
151
- // Hop 0's `cur` IS `answer`, so the guard above already resolved it and
152
- // read its reverse edges — reuse both rather than repeat them.
178
+ let hop = 0;
179
+ let t;
180
+ // WHAT THE OFFERED STEP WOULD BE. The law decides, so the accepted step is
181
+ // traced where the decision went (`onTaken`) and a refused one is reported as
182
+ // the law's refusal (`onRefused`) — a step is never reported before it is
183
+ // admitted.
184
+ let pending = null;
185
+ const offer = async (d) => {
186
+ const cur = d.product;
187
+ // The first step's `cur` IS the grounding's product, so the guard above
188
+ // already resolved it and read its reverse edges — reuse both.
153
189
  const curId = hop === 0 ? groundedId : resolve(ctx, cur);
154
190
  consumeNode(curId, hop === 0 ? groundedPrev ?? undefined : undefined);
191
+ hop++;
155
192
  // Forward-absorb: follow only UNCONSUMED continuations. The gate below
156
- // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows
157
- // nothing of `consumed` and may still walk to a consumed fixpoint —
158
- // absorbing it would repeat content the grounding stage already spoke
159
- // for, so a consumed fixpoint falls through to the pivot step instead.
193
+ // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows nothing
194
+ // of `consumed` and may still walk to a consumed fixpoint — absorbing it
195
+ // would repeat content the grounding stage already spoke for, so a consumed
196
+ // fixpoint falls through to the pivot step instead. Offered with `moves`:
197
+ // completing the answer's OWN learnt form is an identity step, not a claim
198
+ // about the asker's material.
160
199
  if (curId !== null &&
161
200
  ctx.store.nextFirst(curId, bound).some((n) => !consumed.has(n))) {
162
201
  const fwd = await follow(ctx, curId, qv);
163
202
  const fwdId = fwd !== null ? resolve(ctx, fwd) : null;
164
203
  if (fwd !== null && !bytesEqual(fwd, cur) &&
165
204
  (fwdId === null || !consumed.has(fwdId)) &&
166
- !restatesQuery(query, fwd)) {
205
+ !restates(query, fwd, 0, { proper: true })) {
167
206
  consumeAll(curId);
168
- t ??= ctx.trace?.enter("reason", [
169
- rItem(startedFrom, "grounded"),
170
- ]);
171
- ctx.trace?.step("absorbForward", [rItem(cur, "answer", curId)], [rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)], "the answer is itself a learnt fact — follow its continuation to the fixpoint");
172
- cur = fwd;
173
- continue;
207
+ pending = { kind: "absorb", cur, curId, fwd };
208
+ return { product: fwd, contains: true, reaches: true, cost: STEP };
174
209
  }
175
210
  }
176
- // Pivot: find the longest unconsumed learnt context the answer contains.
211
+ // Pivot: the longest unconsumed learnt context the answer contains.
177
212
  consumeAll(curId);
178
213
  const pivot = await pivotInto(ctx, cur, consumed, voiced);
179
214
  if (pivot === null)
180
- break;
215
+ return null;
181
216
  const fc = await follow(ctx, pivot, qv);
182
217
  consumeAll(pivot);
183
- if (fc === null || bytesEqual(fc, cur) || restatesQuery(query, fc))
184
- break;
185
- // WHOSE EXTENSION IS THIS?
186
- //
187
- // `voiced` is what the mechanism WITHHELD (the pipeline sends the used
188
- // anchors' CONTINUATIONS, not their bytes — see pipeline's own note), so a
189
- // non-empty `voiced` means exactly what that note says: the grounding came
190
- // from a mechanism that carries its own short `used` set (cast/join) and
191
- // therefore owns the shape of its answer. The further terms inside such a
192
- // seat are legitimately followable — test/29 C3's `Mona Lisa` lives inside
193
- // the voiced seat and leads on to a fact about neither analog.
194
- //
195
- // Every other grounding is ordinary, and an extension of it is the
196
- // reasoner's own inference: it is taken only while question material the
197
- // grounding left uncovered remains AND the step carries some of it, judged
198
- // by the mind's own line between chance and evidence — one W-byte window,
199
- // no word notion, no character class, no threshold. Measured: the drift's
200
- // second step (`the Eiffel Tower is in Paris` after `Paris is famous for
201
- // the Eiffel Tower`) carries no window of `" famous for"` and is refused,
202
- // while the first carries it. Terminates by a real argument: the uncovered
203
- // material is finite and each taken extension must carry some of it.
204
- const producerOwnsShape = voiced.length > 0;
205
- if (!producerOwnsShape && uncovered.length > 0) {
206
- const W = ctx.space.maxGroup;
207
- let progress = false;
208
- for (const [a, b] of uncovered) {
209
- for (let i = a; i + W <= b && !progress; i++) {
210
- if (indexOf(fc, query.subarray(i, i + W), 0) >= 0)
211
- progress = true;
212
- }
213
- if (progress)
214
- break;
215
- }
216
- if (!progress) {
217
- // THE BRAKE, MADE VISIBLE. The reasoner declines a step that carries
218
- // none of the material the grounding left uncovered — the drift the
219
- // extension tests pin. A refusal that leaves no trace is the kind of
220
- // silent cut AGENTS §6 forbids: the rationale is where a reader learns
221
- // that an extension was declined for want of question material, and
222
- // where the next person sees why the chain stopped here. Measured with
223
- // the check disabled, test/110 and test/116 fail — so this brake is the
224
- // only thing keeping the extension honest until the pivot reports its
225
- // own accounted spans and the ladder can judge it instead.
226
- const left = uncovered.reduce((n, [a, b]) => n + (b - a), 0);
227
- ctx.trace?.step("pivotRefused", [rItem(cur, "answer"), rItem(query, "query")], uncovered.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")), `the step carries none of the question material the grounding left ` +
228
- `uncovered (${left} byte(s) in ${uncovered.length} span(s)) — refused`);
229
- break;
230
- }
218
+ if (fc === null || bytesEqual(fc, cur) ||
219
+ restates(query, fc, 0, { proper: true })) {
220
+ return null;
231
221
  }
232
- if (ctx.meter)
233
- ctx.meter.pivotSteps++;
222
+ pending = { kind: "pivot", cur, pivot, fc };
223
+ // Offered with the identity species ONLY when the grounding declared what it
224
+ // speaks for; otherwise the law requires this step to carry question
225
+ // material the grounding left unaccounted, which is the drift the extension
226
+ // tests pin — refused by the law's own measure, not by a private window
227
+ // test.
228
+ return {
229
+ product: fc,
230
+ contains: true,
231
+ reaches: producerOwnsShape,
232
+ cost: STEP,
233
+ };
234
+ };
235
+ const closed_ = await closure(d0, query, W, offer, (before, after) => {
236
+ if (ctx.meter) {
237
+ ctx.meter.closureDrainedBytes += unaccountedBytes(before.remainder) -
238
+ unaccountedBytes(after.remainder);
239
+ }
240
+ const p = pending;
241
+ pending = null;
242
+ if (p === null)
243
+ return;
234
244
  t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
235
- ctx.trace?.step("pivotStep", [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")], [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)], "pivot on the shared span this answer contains, then step forward across that fact");
236
- cur = fc;
245
+ if (p.kind === "absorb") {
246
+ ctx.trace?.step("absorbForward", [rItem(p.cur, "answer", p.curId ?? undefined)], [rItem(p.fwd, "answer", resolve(ctx, p.fwd) ?? undefined)], "the answer is itself a learnt fact — follow its continuation to the fixpoint");
247
+ }
248
+ else {
249
+ if (ctx.meter)
250
+ ctx.meter.pivotSteps++;
251
+ 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");
252
+ }
253
+ }, (at) => {
254
+ const p = pending;
255
+ pending = null;
256
+ if (p === null || p.kind !== "pivot")
257
+ return;
258
+ // THE BRAKE, MADE VISIBLE — and it is now the LAW's refusal, reported
259
+ // where it happened. The reasoner declines a step that carries none of
260
+ // the material the grounding left unaccounted. A refusal that leaves no
261
+ // trace is the kind of silent cut AGENTS §6 forbids: the rationale is
262
+ // where a reader learns that an extension was declined for want of
263
+ // question material, and where the next person sees why the chain stopped
264
+ // here. Measured with the check disabled, test/110 and test/116 fail.
265
+ const left = unaccountedBytes(at.remainder);
266
+ 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 ` +
267
+ `unaccounted (${left} byte(s) in ${at.remainder.length} span(s)) — refused`);
268
+ });
269
+ // INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
270
+ // contract 1: a counter never reaches a decision). They are what a caller
271
+ // needs to PRICE the extension instead of taking it unconditionally, and both
272
+ // are now read off the state the law advanced rather than accumulated beside
273
+ // it: the work it did is the cost it accumulated, and the material it carried
274
+ // is the accounting the law's witnesses added.
275
+ const steps = closed_.cost - d0.cost;
276
+ if (ctx.meter) {
277
+ ctx.meter.reasonSteps += steps;
278
+ // The accounting the law's witnesses added, summed by the law's own function
279
+ // over the tail of the state's list — and computed ONLY when a meter is
280
+ // attached: this used to slice and MAP a fresh array on every response, for a
281
+ // counter that usually does not exist. One allocation, under the meter.
282
+ ctx.meter.reasonCarriedBytes += unaccountedBytes(closed_.accounted.slice(d0.accounted.length));
237
283
  }
238
- t?.done([rItem(cur, "answer", resolve(ctx, cur) ?? undefined)], "the multi-hop chain's fixpoint");
239
- return cur;
284
+ t?.done([rItem(closed_.product, "answer", resolve(ctx, closed_.product) ?? undefined)],
285
+ // A FIXPOINT: no further step was offered, or the law refused the one that
286
+ // was. There is no allowance to exhaust, so the note is true by
287
+ // construction.
288
+ "the multi-hop chain's fixpoint");
289
+ return closed_;
240
290
  }
241
291
  /** Fuse independent points of attention into one answer (multi-topic).
242
292
  * When the consensus climb finds more than one dominant point, each
243
293
  * independent point grounds its own answer; they are bridged together
244
294
  * by any learnt connector the graph holds between them. */
245
- export async function fuseAttention(ctx, query, primary, pre,
295
+ export async function fuseAttention(ctx, query, state, pre,
246
296
  /** True when `primary` never touched the consensus climb at all — e.g. a
247
297
  * pure ALU computation, which has no anchor of its own. commitVotes
248
298
  * ALWAYS admits the dominant root regardless of its vote (attention.ts:
@@ -257,6 +307,11 @@ unclimbed = false,
257
307
  * fuseAttention just reads a position from it. Empty or absent preserves
258
308
  * the original behaviour exactly. */
259
309
  primarySpans = []) {
310
+ // THE STATE, NOT BARE BYTES: fusion is one more transition of the derivation
311
+ // the walk returned, so it reads that state's product and hands back a state.
312
+ // Its own structural gates stay here — this is the layer that can resolve
313
+ // those witnesses, and the law never re-derives one.
314
+ const primary = state.product;
260
315
  // When the answer is structurally drawn from the query itself
261
316
  // (extraction), it already spans all the query's pieces — fusion
262
317
  // would only add noise from unrelated stored contexts. The gate is
@@ -265,7 +320,7 @@ primarySpans = []) {
265
320
  // short answers over long queries, silently starving multi-topic queries
266
321
  // of fusion.
267
322
  if (containsSpan(ctx, query, primary))
268
- return primary;
323
+ return state;
269
324
  // The committed points of attention ARE the shared climb's roots (same
270
325
  // query, same k, same DF mode) — read them from Precomputed instead of
271
326
  // re-climbing, so even a traced response pays for the climb once.
@@ -306,7 +361,7 @@ primarySpans = []) {
306
361
  const lonePromotes = unclimbed && forest.length === 1 &&
307
362
  forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
308
363
  if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
309
- return primary;
364
+ return state;
310
365
  }
311
366
  // WHERE THE QUERY ASKED FOR IT. The sort below orders the fused pieces by
312
367
  // query position, which is the whole point of the `start` field: a
@@ -382,7 +437,13 @@ primarySpans = []) {
382
437
  // it fires on exact content-addressed recurrence, not on how strongly
383
438
  // the root resonates.
384
439
  const cont = await follow(ctx, root.anchor, qv);
385
- if (cont !== null && cont.length > 0 && indexOf(query, cont, root.end) >= 0) {
440
+ if (cont !== null && cont.length > 0 &&
441
+ // The law's positional reading: the caller knows the material it is
442
+ // looking for lies AT OR AFTER this root, which is the witness; the law
443
+ // owns the containment test itself. The `cont.length > 0` guard stays
444
+ // here because an EMPTY continuation indexes at every offset — the law
445
+ // has no opinion about whether "nothing" counts as said.
446
+ restates(query, cont, 0, { from: root.end })) {
386
447
  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");
387
448
  continue;
388
449
  }
@@ -395,7 +456,7 @@ primarySpans = []) {
395
456
  }
396
457
  if (pieces.length === 1) {
397
458
  t?.done([rItem(primary, "answer")], "no further independent point grounded");
398
- return primary;
459
+ return state;
399
460
  }
400
461
  pieces.sort((a, b) => a.start - b.start);
401
462
  let out = pieces[0].bytes;
@@ -405,8 +466,31 @@ primarySpans = []) {
405
466
  out = await joinWithBridge(ctx, out, pieces[i].bytes);
406
467
  }
407
468
  t?.done([rItem(out, "answer", resolve(ctx, out) ?? undefined)], `fused ${pieces.length} independent points of attention into one answer`);
408
- return out;
469
+ // THE FACT IS THE FUSED ANSWER, not the call: every early return above hands
470
+ // back `primary` untouched. Untraced on purpose (meter.ts contract 1).
471
+ if (ctx.meter)
472
+ ctx.meter.fuseRuns++;
473
+ // A FUSION IS A TRANSITION, and the only one in the engine that can splice the
474
+ // QUESTION'S OWN material into the product. So it is OFFERED to the law rather
475
+ // than built by hand: the law reads the window the fused product holds — the
476
+ // same reading the carries admission uses, one definition — accounts for the
477
+ // span it carried, and lets the question's remainder drain on EVIDENCE. A
478
+ // fusion that carries nothing consumes nothing, because the reading finds no
479
+ // window; and `reaches` is declared only when the fusion composed another root,
480
+ // which is structure this derivation had not stood on.
481
+ const fusedT = {
482
+ product: out,
483
+ contains: true,
484
+ reaches: rest.length > 0,
485
+ cost: STEP,
486
+ };
487
+ const fusedWitness = admissible(state, fusedT, query, ctx.space.maxGroup);
488
+ if (fusedWitness === null)
489
+ return { ...state, product: out };
490
+ const fused = advance(state, fusedT, fusedWitness);
491
+ if (ctx.meter) {
492
+ ctx.meter.closureDrainedBytes += unaccountedBytes(state.remainder) -
493
+ unaccountedBytes(fused.remainder);
494
+ }
495
+ return fused;
409
496
  }
410
- // (resonance.js is already a static dependency above — `bridge` — so the old
411
- // dynamic import of pivotInto guarded against a cycle that does not exist.)
412
- import { containsSpan } from "./match.js";
@@ -519,8 +519,11 @@ function recogniseImpl(ctx, bytes) {
519
519
  if (end - start < W)
520
520
  return false;
521
521
  if (flatProbe(start, end) === null) {
522
- if (!canonBudget)
522
+ if (!canonBudget) {
523
+ if (ctx.meter)
524
+ ctx.meter.canonProbesDenied++;
523
525
  return false;
526
+ }
524
527
  if (!canonAdmits(start, end))
525
528
  return false;
526
529
  }
@@ -543,13 +546,6 @@ function recogniseImpl(ctx, bytes) {
543
546
  // endpoints in order and running out partway along the query. A form
544
547
  // longer than that is out of this tier's reach — but so is a form the
545
548
  // chain cannot span, and that is exactly the trade the budget prices.
546
- // The factor is chainReach(W), the same W² scale the chain already
547
- // trusts; no new constant.
548
- // The factor is chainReach(W) — the same W² scale the chain itself
549
- // trusts — so the cap is derived from the fold's geometry, never tuned.
550
- // (It was briefly an environment variable while the cost was being
551
- // measured; an env-read here would make inference non-reproducible,
552
- // which the determinism contract forbids outright.)
553
549
  // The factor is chainReach(W) — the same W² scale the chain itself
554
550
  // trusts — so the cap is derived from the fold's geometry, never tuned.
555
551
  // (It was briefly an environment variable while the cost was being
@@ -249,7 +249,14 @@ export async function joinWithBridge(ctx, left, right) {
249
249
  * overlap, so C3's genuine further hop — "Mona Lisa", a term inside the seat
250
250
  * sentence but part of NEITHER analog — still fires. */
251
251
  export async function pivotInto(ctx, answer, consumed, voiced = []) {
252
- const k = ctx.cfg.recallQueryK;
252
+ // The pivot's OWN shortlist capacity — not `recallQueryK`: they are different
253
+ // quantities (a mechanical sweep's probe budget vs the bridge's candidate-read
254
+ // allowance), and one number serving both means tightening either silently
255
+ // starves the other. The reason is the duplication, NOT a measured strangle:
256
+ // an earlier version of this comment claimed "at recallQueryK 1 the pivot finds
257
+ // no pivot", and that was probed and is FALSE on test/23's fixture — the
258
+ // strangle is fixture-specific, so it is not the evidence for this split.
259
+ const k = ctx.cfg.pivotProbeK;
253
260
  // ONE perception of the answer, shared by the probe budget and the walk —
254
261
  // this used to fold the same bytes twice, back to back, on every hop.
255
262
  const tree = perceive(ctx, answer);
@@ -288,6 +295,18 @@ export async function pivotInto(ctx, answer, consumed, voiced = []) {
288
295
  for (const c of n.kids)
289
296
  queue.push(c); // breadth-first: larger regions first
290
297
  }
298
+ // The sweep's two FACTS, untraced (meter.ts contract 1: a counter never
299
+ // reaches a decision). `probes` is the work done; the shortfall is the
300
+ // capacity the cap withheld — the branches the breadth-first order never got
301
+ // to. Whether that withheld anything that mattered is NOT said here: the
302
+ // order spends the largest regions first, and recognition below still
303
+ // contributes every exact containment candidate regardless of the budget.
304
+ if (ctx.meter) {
305
+ ctx.meter.pivotProbes += probes;
306
+ const unprobed = branchCount - probeCap;
307
+ if (unprobed > 0)
308
+ ctx.meter.pivotBranchesUnprobed += unprobed;
309
+ }
291
310
  // THE FULL recognition, memo-shared with every other reader of these bytes.
292
311
  // A "skip the edge trims here" variant was refuted (see recognise's own
293
312
  // note): those trims are what find a WHOLE trained form embedded at an
@@ -8,6 +8,7 @@ import { decodeText } from "./rationale.js";
8
8
  export function rItem(bytes, role, node, span) {
9
9
  return {
10
10
  text: decodeText(bytes),
11
+ bytes,
11
12
  role,
12
13
  node: node ?? undefined,
13
14
  span,
@@ -8,6 +8,12 @@
8
8
  import { cosine } from "../vec.js";
9
9
  import { gistOf, read } from "./primitives.js";
10
10
  import { canonicalWindows, leafIdPrefix, leafIdRun } from "./canonical.js";
11
+ // Imported at the TOP, where every other import is. They used to sit 800 lines
12
+ // down under a note claiming the position mattered ("before trace module is
13
+ // loaded") — it does not: an ES module's static imports are HOISTED, so the
14
+ // file's line order never decides load order. The note described an intention
15
+ // the runtime does not honour; the imports move and the claim goes.
16
+ import { decodeText } from "./rationale.js";
11
17
  //
12
18
  // Budgeted on the same terms as the reach memo below (caches.md): these three
13
19
  // maps are cleared on every write, but a long read-only session over a large
@@ -704,8 +710,6 @@ export function chooseAmong(ctx, candidates, guide) {
704
710
  ? { id: found.item, score: found.score }
705
711
  : { id: candidates[0], score: -Infinity };
706
712
  }
707
- // ── Trace shim (used by chooseNext before trace module is loaded) ────────
708
- import { decodeText } from "./rationale.js";
709
713
  function rItemShort(ctx, id, role, score) {
710
714
  return {
711
715
  text: decodeText(read(ctx, id)),
@@ -35,15 +35,21 @@ export interface GraphSearchHost {
35
35
  starts: ReadonlySet<number>;
36
36
  };
37
37
  chooseNext?(node: number): number | undefined;
38
- /** The boundary positions of `bytes` under the engine's ONE boundary rule
39
- * (geometry.ts's `contentBoundaries`), or undefined when the host has no
40
- * space to ask. The join's key is an entity plus a prefix of the tail, and
41
- * the prefix that names a stored relation ENDS on one of these boundaries —
42
- * measured, 5 of 5 accepted keys over four join-firing queries, where the
43
- * byte-by-byte scan spent 153 probes for 14 boundaries. Boundaries are
44
- * content-defined and STABLE under prefix extension, which is why a corpus
45
- * key's end is a boundary of the query's own fold of the same bytes. */
46
- contentCuts?(bytes: Uint8Array): readonly number[];
38
+ /** The lengths `p` for which `prefix ‖ tail[0..p]` IS A STORED NODE, ascending
39
+ * — the join's candidate set, in the tail's own coordinates. Optional: a host
40
+ * that cannot answer makes the join fall back to every prefix, which is exact
41
+ * and complete but pays a `resolve` per offset.
42
+ *
43
+ * WHY NOT THE FOLD'S CUTS. A key names a relation exactly when the
44
+ * concatenation is a node, and a node's end is the end of ITS OWN stream —
45
+ * where the fold never emits a cut (geometry's `emit` guards `at >= n`). So a
46
+ * key can end strictly inside the tail with no boundary anywhere near it:
47
+ * measured, "stockholm mayor" exists, leads on to the mayor fact, and its
48
+ * boundary 6 is in neither the tail's cuts ([4,7]) nor the concatenation's.
49
+ * The fold's boundaries are a SUBSET of the real ends, not a proxy for them,
50
+ * and using them skipped the shortest names first — which is a semantic law,
51
+ * not an optimisation (test/106, test/108 pin it). */
52
+ contentKeyEnds?(prefix: Uint8Array, tail: Uint8Array): readonly number[];
47
53
  /** The admission predicate — `traverse.ts`'s `leadsSomewhere`, its ONE
48
54
  * definition: does this node bear an edge or a halo? Optional, so a bare
49
55
  * host (a raw Store and nothing else) still works; when present, the search
@@ -104,11 +110,28 @@ export interface Attention {
104
110
  * strength and its place.
105
111
  * `vote` is a sum over every region that agreed, so it grows with how many
106
112
  * places corroborated; `peak` is what the strongest one of them said on its
107
- * own. A consumer holding this point to consensusFloor(N) — a bar that
108
- * prices ONE region's maximally-discriminative evidence — must read `peak`,
109
- * not `vote`: six scaffolding regions summing past the floor is not the
110
- * same claim as one region clearing it. */
113
+ * own. THIS USED TO PRESCRIBE THE WRONG OPERAND. It read: "a consumer
114
+ * holding this point to consensusFloor(N) — a bar that prices ONE region's
115
+ * maximally-discriminative evidence — must read `peak`, not `vote`." The
116
+ * engine reads the POOLED vote, and thresholds.md §2 derives the floor for
117
+ * exactly that ("Pooled-vote significance floor": one maximally-specific
118
+ * region contributes at most ln N, and ln(N)+1/2 demands corroboration
119
+ * BEYOND one region). MEASURED across 27 anchors on 6 queries: all 11
120
+ * admissions cleared the floor by the sum and NONE by `peak` alone — a gate
121
+ * reading `peak` would refuse every root the engine elects. `peak` remains
122
+ * what it is: the strongest SINGLE region's contribution. */
111
123
  peak: number;
124
+ /** The IDF-WEIGHTED sum behind this point — the quantity `consensusFloor` is
125
+ * derived for, and therefore the one the floor gates must read. It is
126
+ * MODE-INDEPENDENT by construction (its per-region weight is
127
+ * `mutual · idf / roots`, never the mode-dependent `wf`), so gating on it
128
+ * makes an anchor's admission the same in `inverse`, `direct` and `combined`.
129
+ * In `inverse` — the only mode the engine runs — it equals `vote` exactly
130
+ * (measured, test/55 test 17), so nothing about today's verdicts changes.
131
+ * MEASURED before this field existed: gating on `vote` DID flip a verdict,
132
+ * anchor 87 of test/55's query (inverse 2.682 admitted, direct 1.468
133
+ * refused, floor 2.292). */
134
+ idfVote: number;
112
135
  /** SCALE-INVARIANT confidence: the fraction of the query's OWN regions
113
136
  * whose evidence this point accounts for (Σ RegionVote.absorbed among
114
137
  * its contributors, over the query's total region count) — read PER-
@@ -2,7 +2,8 @@
2
2
  //
3
3
  // GraphSearchHost is defined first (minimal imports) so GraphSearch can import
4
4
  // it without pulling in the full MindContext.
5
- import { bytesEqual, concatBytes, indexOf } from "../bytes.js";
5
+ import { bytesEqual, concatBytes } from "../bytes.js";
6
+ import { restates } from "./derivation.js";
6
7
  import { dominates } from "../geometry.js";
7
8
  // ═══════════════════════════════════════════════════════════════════════════
8
9
  // FREE FUNCTIONS (pure, no state)
@@ -32,12 +33,14 @@ export function spliceAll(segs) {
32
33
  export function segRestatesQuery(s, query, queryLen, W) {
33
34
  if (!s.rec)
34
35
  return false;
36
+ // THE LITERAL EXEMPTION IS THE CALLER'S. A span that IS the site's own bytes
37
+ // at its own position is naming what is already there, not substituting for
38
+ // it — and only this caller knows that, so it says so by not asking.
35
39
  const literal = s.j - s.i === s.bytes.length &&
36
40
  bytesEqual(s.bytes, query.subarray(s.i, s.j));
37
41
  if (literal)
38
42
  return false;
39
- return s.bytes.length >= W && s.bytes.length < queryLen &&
40
- indexOf(query, s.bytes, 0) >= 0;
43
+ return restates(query, s.bytes, W, { proper: true });
41
44
  }
42
45
  /** Lift the answer out of the cover for think: the recognised region, free of
43
46
  * the asker's surrounding (unrecognised) framing — and free of any chosen