@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
@@ -8,29 +8,31 @@ import { bytesEqual, indexOf } from "../bytes.js";
8
8
  import type { Attention, MindContext } from "./types.js";
9
9
  import { resolve } from "./primitives.js";
10
10
  import { corpusN, hubBound } from "./traverse.js";
11
- import { follow, haloSiblings, project } from "./match.js";
11
+ import { containsSpan, follow, haloSiblings, project } from "./match.js";
12
12
  import { joinWithBridge, pivotInto } from "./resonance.js";
13
+ import { admissible, advance, type Continuation } from "./derivation.js";
13
14
  import type { Precomputed } from "./pipeline-mechanism.js";
14
- import type { Rationale } from "./rationale.js";
15
-
16
- /** Whether `bytes` is a proper byte-subspan of `query` — already present in
17
- * the question, so voicing it back only restates part of what was asked,
18
- * never answers it. The exact guard recallByResonance already applies to
19
- * its OWN grounding candidates (tier 1's `restates`, tier 2's subspan
20
- * check, tier 0b's argument-binding subspan check) — every mechanism that
21
- * walks a LEARNT CONTINUATION EDGE past an already-vetted grounding
22
- * (reason()'s own hops below, and CAST's `projectCounterfactual` seat
23
- * substitution — see cast.ts) needs the same guard applied to what the
24
- * walk turns up, since `follow()`/`chooseNext`/`pivotInto` know nothing of
25
- * the query at all — only of what structurally continues what. */
26
- export function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean {
27
- return bytes.length < query.length && indexOf(query, bytes, 0) >= 0;
28
- }
15
+ import { type Rationale } from "./rationale.js";
16
+ import {
17
+ closure,
18
+ type DerivationState,
19
+ type Offer,
20
+ restates,
21
+ type Span,
22
+ unaccountedBytes,
23
+ } from "./derivation.js";
24
+ import { STEP } from "./graph-search.js";
29
25
 
30
26
  /** Extend a grounded answer forward across facts (multi-hop reasoning).
31
27
  * Pivots on the longest unconsumed learnt context each answer contains,
32
- * then follows the pivot's continuation to the next fact. Repeats up
33
- * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
28
+ * then follows the pivot's continuation to the next fact. **The chain ends
29
+ * when it STOPS, never when a count runs out**: every exit is a refusal (no
30
+ * pivot, no forward step, no question material carried) and the walk is bounded
31
+ * by the material and the graph — `consumed` refuses to revisit a node. There
32
+ * is no hop allowance, so this doc deliberately names no `cfg` capacity: the
33
+ * cover prices every hop at `STEP` and lets the search decide the depth, and a
34
+ * second count here would be a second decision about the same thing.
35
+ * `preConsumed` carries node ids already
34
36
  * spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
35
37
  * the BYTES of the anchors a mechanism declared it voiced (its `used` set),
36
38
  * when it declared one — see the pivot's own containment rule. `pre` is the
@@ -39,22 +41,25 @@ export function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean {
39
41
  export async function reason(
40
42
  ctx: MindContext,
41
43
  query: Uint8Array,
42
- answer: Uint8Array,
44
+ d0: DerivationState,
43
45
  preConsumed: ReadonlySet<number>,
44
46
  pre: Precomputed,
45
47
  voiced: readonly Uint8Array[] = [],
46
- /** The query material the GROUNDING left uncovered — the cost ladder's own
47
- * `unaccounted` spans. Only the reasoner's OWN extensions are judged
48
- * against it; a mechanism carrying its own `used` set owns its shape. */
49
- uncovered: readonly (readonly [number, number])[] = [],
50
- ): Promise<Uint8Array> {
48
+ ): Promise<DerivationState> {
49
+ const answer = d0.product;
50
+ /** The query material the GROUNDING left uncovered — the state's own
51
+ * remainder. Only the reasoner's OWN extensions are judged against it; a
52
+ * mechanism carrying its own `used` set owns its shape. */
53
+ const uncovered = d0.remainder;
51
54
  // Echo guard: a query that is ITSELF a learnt continuation (some context's
52
55
  // answer) is being asked back at the system — hopping forward from it would
53
56
  // chain through the very fact that produced it and echo the conversation
54
57
  // back. The grounded answer alone is the honest read-out. Deliberately a
55
58
  // broad structural gate; pinned by test/31-audit.
56
59
  const qId = pre.queryResolved;
57
- if (qId !== null && ctx.store.prevCount(qId) > 0) return answer;
60
+ if (qId !== null && ctx.store.prevCount(qId) > 0) {
61
+ return d0;
62
+ }
58
63
 
59
64
  // Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
60
65
  // the hub bound, via the store's LIMITed edge reads: a common continuation's
@@ -73,7 +78,7 @@ export async function reason(
73
78
  // is nothing left to chain for.
74
79
  //
75
80
  // Every stopping condition in the loop below judges the ANSWER (`consumed` /
76
- // `restatesQuery` / `bytesEqual`); none asks whether the QUESTION was
81
+ // the law's `restates` / `bytesEqual`); none asks whether the QUESTION was
77
82
  // satisfied. So a single-hop question whose answer happens to name another
78
83
  // learnt context extends past a correct answer and REPLACES it:
79
84
  //
@@ -113,7 +118,7 @@ export async function reason(
113
118
  ? null
114
119
  : ctx.store.prevFirst(groundedId, bound);
115
120
  if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
116
- return answer;
121
+ return d0;
117
122
  }
118
123
 
119
124
  const consumed = new Set<number>();
@@ -155,21 +160,66 @@ export async function reason(
155
160
  await preconsume();
156
161
  }
157
162
 
158
- let cur = answer;
163
+ // ── THE WALK, AS THE LAW'S ───────────────────────────────────────────
164
+ //
165
+ // This function no longer decides whether a step may be taken: it OFFERS a
166
+ // continuation — a forward step from the answer's own learnt edge, or a pivot
167
+ // on a learnt context the answer contains — and `derivation.ts`'s law admits
168
+ // or refuses it. The state it offers against is the pipeline's own: the
169
+ // grounding's remainder (what the question still owes), its accounting, its
170
+ // cost.
171
+ //
172
+ // NO ALLOWANCE: THE CHAIN ENDS WHEN IT STOPS. Every exit below is the law —
173
+ // no pivot, no forward step, no question material carried — and the walk is
174
+ // bounded by the material and the graph rather than by a count: the law
175
+ // requires each step to engage what is left (or to move to new structure),
176
+ // and `consumed` refuses to revisit a node. `recallQueryK` no longer bounds
177
+ // the reasoner here; it keeps its other roles (the bridge's candidate reads,
178
+ // the pivot's probe budget, the resonance limits).
179
+ //
180
+ // Measured before removing the allowance: test/89 — the corpus-cost guard, the
181
+ // heaviest case in the suite — is green and no slower without it (27 s against
182
+ // 30 s); and raising it from 12 to 200 changed neither the answer nor
183
+ // `pivotSteps` on the chain fixtures.
159
184
  const qv = pre.guide; // the response-wide guide IS the query's gist
160
- let t: ReturnType<Rationale["enter"]> | undefined;
185
+ const W = ctx.space.maxGroup;
186
+ // WHOSE EXTENSION IS THIS? `voiced` is what the mechanism WITHHELD (the
187
+ // pipeline sends the used anchors' CONTINUATIONS, not their bytes), so a
188
+ // non-empty `voiced` means exactly what that note says: the grounding came
189
+ // from a mechanism that carries its own short `used` set (cast/join) and
190
+ // therefore OWNS the shape of its answer. The further terms inside such a
191
+ // seat are legitimately followable (test/29 C3's `Mona Lisa` lives inside the
192
+ // voiced seat and leads on to a fact about neither analog), which the law
193
+ // expresses as the identity species of progress — `moves` — declared by the
194
+ // reporter and never inferred from the mechanism's name.
195
+ const producerOwnsShape = voiced.length > 0;
161
196
  const startedFrom = answer;
162
- for (let hop = 0; hop < ctx.cfg.recallQueryK; hop++) {
163
- // Hop 0's `cur` IS `answer`, so the guard above already resolved it and
164
- // read its reverse edges — reuse both rather than repeat them.
197
+ let hop = 0;
198
+ let t: ReturnType<Rationale["enter"]> | undefined;
199
+ // WHAT THE OFFERED STEP WOULD BE. The law decides, so the accepted step is
200
+ // traced where the decision went (`onTaken`) and a refused one is reported as
201
+ // the law's refusal (`onRefused`) — a step is never reported before it is
202
+ // admitted.
203
+ let pending:
204
+ | { kind: "absorb"; cur: Uint8Array; curId: number | null; fwd: Uint8Array }
205
+ | { kind: "pivot"; cur: Uint8Array; pivot: number; fc: Uint8Array }
206
+ | null = null;
207
+
208
+ const offer: Offer = async (d) => {
209
+ const cur = d.product;
210
+ // The first step's `cur` IS the grounding's product, so the guard above
211
+ // already resolved it and read its reverse edges — reuse both.
165
212
  const curId = hop === 0 ? groundedId : resolve(ctx, cur);
166
213
  consumeNode(curId, hop === 0 ? groundedPrev ?? undefined : undefined);
214
+ hop++;
167
215
 
168
216
  // Forward-absorb: follow only UNCONSUMED continuations. The gate below
169
- // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows
170
- // nothing of `consumed` and may still walk to a consumed fixpoint —
171
- // absorbing it would repeat content the grounding stage already spoke
172
- // for, so a consumed fixpoint falls through to the pivot step instead.
217
+ // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows nothing
218
+ // of `consumed` and may still walk to a consumed fixpoint — absorbing it
219
+ // would repeat content the grounding stage already spoke for, so a consumed
220
+ // fixpoint falls through to the pivot step instead. Offered with `moves`:
221
+ // completing the answer's OWN learnt form is an identity step, not a claim
222
+ // about the asker's material.
173
223
  if (
174
224
  curId !== null &&
175
225
  ctx.store.nextFirst(curId, bound).some((n) => !consumed.has(n))
@@ -179,96 +229,122 @@ export async function reason(
179
229
  if (
180
230
  fwd !== null && !bytesEqual(fwd, cur) &&
181
231
  (fwdId === null || !consumed.has(fwdId)) &&
182
- !restatesQuery(query, fwd)
232
+ !restates(query, fwd, 0, { proper: true })
183
233
  ) {
184
234
  consumeAll(curId);
185
- t ??= ctx.trace?.enter("reason", [
186
- rItem(startedFrom, "grounded"),
187
- ]);
188
- ctx.trace?.step(
189
- "absorbForward",
190
- [rItem(cur, "answer", curId)],
191
- [rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)],
192
- "the answer is itself a learnt fact — follow its continuation to the fixpoint",
193
- );
194
- cur = fwd;
195
- continue;
235
+ pending = { kind: "absorb", cur, curId, fwd };
236
+ return { product: fwd, contains: true, reaches: true, cost: STEP };
196
237
  }
197
238
  }
198
239
 
199
- // Pivot: find the longest unconsumed learnt context the answer contains.
240
+ // Pivot: the longest unconsumed learnt context the answer contains.
200
241
  consumeAll(curId);
201
242
  const pivot = await pivotInto(ctx, cur, consumed, voiced);
202
- if (pivot === null) break;
203
-
243
+ if (pivot === null) return null;
204
244
  const fc = await follow(ctx, pivot, qv);
205
245
  consumeAll(pivot);
206
- if (fc === null || bytesEqual(fc, cur) || restatesQuery(query, fc)) break;
207
- // WHOSE EXTENSION IS THIS?
208
- //
209
- // `voiced` is what the mechanism WITHHELD (the pipeline sends the used
210
- // anchors' CONTINUATIONS, not their bytes — see pipeline's own note), so a
211
- // non-empty `voiced` means exactly what that note says: the grounding came
212
- // from a mechanism that carries its own short `used` set (cast/join) and
213
- // therefore owns the shape of its answer. The further terms inside such a
214
- // seat are legitimately followable — test/29 C3's `Mona Lisa` lives inside
215
- // the voiced seat and leads on to a fact about neither analog.
216
- //
217
- // Every other grounding is ordinary, and an extension of it is the
218
- // reasoner's own inference: it is taken only while question material the
219
- // grounding left uncovered remains AND the step carries some of it, judged
220
- // by the mind's own line between chance and evidence — one W-byte window,
221
- // no word notion, no character class, no threshold. Measured: the drift's
222
- // second step (`the Eiffel Tower is in Paris` after `Paris is famous for
223
- // the Eiffel Tower`) carries no window of `" famous for"` and is refused,
224
- // while the first carries it. Terminates by a real argument: the uncovered
225
- // material is finite and each taken extension must carry some of it.
226
- const producerOwnsShape = voiced.length > 0;
227
- if (!producerOwnsShape && uncovered.length > 0) {
228
- const W = ctx.space.maxGroup;
229
- let progress = false;
230
- for (const [a, b] of uncovered) {
231
- for (let i = a; i + W <= b && !progress; i++) {
232
- if (indexOf(fc, query.subarray(i, i + W), 0) >= 0) progress = true;
233
- }
234
- if (progress) break;
246
+ if (
247
+ fc === null || bytesEqual(fc, cur) ||
248
+ restates(query, fc, 0, { proper: true })
249
+ ) {
250
+ return null;
251
+ }
252
+ pending = { kind: "pivot", cur, pivot, fc };
253
+ // Offered with the identity species ONLY when the grounding declared what it
254
+ // speaks for; otherwise the law requires this step to carry question
255
+ // material the grounding left unaccounted, which is the drift the extension
256
+ // tests pin — refused by the law's own measure, not by a private window
257
+ // test.
258
+ return {
259
+ product: fc,
260
+ contains: true,
261
+ reaches: producerOwnsShape,
262
+ cost: STEP,
263
+ };
264
+ };
265
+
266
+ const closed_ = await closure(
267
+ d0,
268
+ query,
269
+ W,
270
+ offer,
271
+ (before, after) => {
272
+ if (ctx.meter) {
273
+ ctx.meter.closureDrainedBytes += unaccountedBytes(before.remainder) -
274
+ unaccountedBytes(after.remainder);
235
275
  }
236
- if (!progress) {
237
- // THE BRAKE, MADE VISIBLE. The reasoner declines a step that carries
238
- // none of the material the grounding left uncovered — the drift the
239
- // extension tests pin. A refusal that leaves no trace is the kind of
240
- // silent cut AGENTS §6 forbids: the rationale is where a reader learns
241
- // that an extension was declined for want of question material, and
242
- // where the next person sees why the chain stopped here. Measured with
243
- // the check disabled, test/110 and test/116 fail — so this brake is the
244
- // only thing keeping the extension honest until the pivot reports its
245
- // own accounted spans and the ladder can judge it instead.
246
- const left = uncovered.reduce((n, [a, b]) => n + (b - a), 0);
276
+ const p = pending;
277
+ pending = null;
278
+ if (p === null) return;
279
+ t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
280
+ if (p.kind === "absorb") {
247
281
  ctx.trace?.step(
248
- "pivotRefused",
249
- [rItem(cur, "answer"), rItem(query, "query")],
250
- uncovered.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")),
251
- `the step carries none of the question material the grounding left ` +
252
- `uncovered (${left} byte(s) in ${uncovered.length} span(s)) — refused`,
282
+ "absorbForward",
283
+ [rItem(p.cur, "answer", p.curId ?? undefined)],
284
+ [rItem(p.fwd, "answer", resolve(ctx, p.fwd) ?? undefined)],
285
+ "the answer is itself a learnt fact — follow its continuation to the fixpoint",
286
+ );
287
+ } else {
288
+ if (ctx.meter) ctx.meter.pivotSteps++;
289
+ ctx.trace?.step(
290
+ "pivotStep",
291
+ [rItem(p.cur, "answer"), rNode(ctx, p.pivot, "pivot")],
292
+ [rItem(p.fc, "answer", resolve(ctx, p.fc) ?? undefined)],
293
+ "pivot on the shared span this answer contains, then step forward across that fact",
253
294
  );
254
- break;
255
295
  }
256
- }
257
- if (ctx.meter) ctx.meter.pivotSteps++;
258
- t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
259
- ctx.trace?.step(
260
- "pivotStep",
261
- [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")],
262
- [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)],
263
- "pivot on the shared span this answer contains, then step forward across that fact",
296
+ },
297
+ (at) => {
298
+ const p = pending;
299
+ pending = null;
300
+ if (p === null || p.kind !== "pivot") return;
301
+ // THE BRAKE, MADE VISIBLE — and it is now the LAW's refusal, reported
302
+ // where it happened. The reasoner declines a step that carries none of
303
+ // the material the grounding left unaccounted. A refusal that leaves no
304
+ // trace is the kind of silent cut AGENTS §6 forbids: the rationale is
305
+ // where a reader learns that an extension was declined for want of
306
+ // question material, and where the next person sees why the chain stopped
307
+ // here. Measured with the check disabled, test/110 and test/116 fail.
308
+ const left = unaccountedBytes(at.remainder);
309
+ ctx.trace?.step(
310
+ "pivotRefused",
311
+ [rItem(p.cur, "answer"), rItem(query, "query")],
312
+ at.remainder.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")),
313
+ `the step carries none of the question material the grounding left ` +
314
+ `unaccounted (${left} byte(s) in ${at.remainder.length} span(s)) — refused`,
315
+ );
316
+ },
317
+ );
318
+
319
+ // INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
320
+ // contract 1: a counter never reaches a decision). They are what a caller
321
+ // needs to PRICE the extension instead of taking it unconditionally, and both
322
+ // are now read off the state the law advanced rather than accumulated beside
323
+ // it: the work it did is the cost it accumulated, and the material it carried
324
+ // is the accounting the law's witnesses added.
325
+ const steps = closed_.cost - d0.cost;
326
+ if (ctx.meter) {
327
+ ctx.meter.reasonSteps += steps;
328
+ // The accounting the law's witnesses added, summed by the law's own function
329
+ // over the tail of the state's list — and computed ONLY when a meter is
330
+ // attached: this used to slice and MAP a fresh array on every response, for a
331
+ // counter that usually does not exist. One allocation, under the meter.
332
+ ctx.meter.reasonCarriedBytes += unaccountedBytes(
333
+ closed_.accounted.slice(d0.accounted.length),
264
334
  );
265
- cur = fc;
266
335
  }
267
336
  t?.done(
268
- [rItem(cur, "answer", resolve(ctx, cur) ?? undefined)],
337
+ [rItem(
338
+ closed_.product,
339
+ "answer",
340
+ resolve(ctx, closed_.product) ?? undefined,
341
+ )],
342
+ // A FIXPOINT: no further step was offered, or the law refused the one that
343
+ // was. There is no allowance to exhaust, so the note is true by
344
+ // construction.
269
345
  "the multi-hop chain's fixpoint",
270
346
  );
271
- return cur;
347
+ return closed_;
272
348
  }
273
349
 
274
350
  /** Fuse independent points of attention into one answer (multi-topic).
@@ -278,7 +354,7 @@ export async function reason(
278
354
  export async function fuseAttention(
279
355
  ctx: MindContext,
280
356
  query: Uint8Array,
281
- primary: Uint8Array,
357
+ state: DerivationState,
282
358
  pre: Precomputed,
283
359
  /** True when `primary` never touched the consensus climb at all — e.g. a
284
360
  * pure ALU computation, which has no anchor of its own. commitVotes
@@ -293,8 +369,13 @@ export async function fuseAttention(
293
369
  * which is the layer that knows how a given grounding records its evidence;
294
370
  * fuseAttention just reads a position from it. Empty or absent preserves
295
371
  * the original behaviour exactly. */
296
- primarySpans: ReadonlyArray<readonly [number, number]> = [],
297
- ): Promise<Uint8Array> {
372
+ primarySpans: ReadonlyArray<Span> = [],
373
+ ): Promise<DerivationState> {
374
+ // THE STATE, NOT BARE BYTES: fusion is one more transition of the derivation
375
+ // the walk returned, so it reads that state's product and hands back a state.
376
+ // Its own structural gates stay here — this is the layer that can resolve
377
+ // those witnesses, and the law never re-derives one.
378
+ const primary = state.product;
298
379
  // When the answer is structurally drawn from the query itself
299
380
  // (extraction), it already spans all the query's pieces — fusion
300
381
  // would only add noise from unrelated stored contexts. The gate is
@@ -302,7 +383,7 @@ export async function fuseAttention(
302
383
  // byte run): the old sparse-subsequence test was trivially satisfied by
303
384
  // short answers over long queries, silently starving multi-topic queries
304
385
  // of fusion.
305
- if (containsSpan(ctx, query, primary)) return primary;
386
+ if (containsSpan(ctx, query, primary)) return state;
306
387
 
307
388
  // The committed points of attention ARE the shared climb's roots (same
308
389
  // query, same k, same DF mode) — read them from Precomputed instead of
@@ -345,7 +426,7 @@ export async function fuseAttention(
345
426
  const lonePromotes = unclimbed && forest.length === 1 &&
346
427
  forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
347
428
  if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
348
- return primary;
429
+ return state;
349
430
  }
350
431
 
351
432
  // WHERE THE QUERY ASKED FOR IT. The sort below orders the fused pieces by
@@ -428,7 +509,13 @@ export async function fuseAttention(
428
509
  // the root resonates.
429
510
  const cont = await follow(ctx, root.anchor, qv);
430
511
  if (
431
- cont !== null && cont.length > 0 && indexOf(query, cont, root.end) >= 0
512
+ cont !== null && cont.length > 0 &&
513
+ // The law's positional reading: the caller knows the material it is
514
+ // looking for lies AT OR AFTER this root, which is the witness; the law
515
+ // owns the containment test itself. The `cont.length > 0` guard stays
516
+ // here because an EMPTY continuation indexes at every offset — the law
517
+ // has no opinion about whether "nothing" counts as said.
518
+ restates(query, cont, 0, { from: root.end })
432
519
  ) {
433
520
  ctx.trace?.step(
434
521
  "alreadyAnswered",
@@ -448,7 +535,7 @@ export async function fuseAttention(
448
535
  [rItem(primary, "answer")],
449
536
  "no further independent point grounded",
450
537
  );
451
- return primary;
538
+ return state;
452
539
  }
453
540
 
454
541
  pieces.sort((a, b) => a.start - b.start);
@@ -462,9 +549,29 @@ export async function fuseAttention(
462
549
  [rItem(out, "answer", resolve(ctx, out) ?? undefined)],
463
550
  `fused ${pieces.length} independent points of attention into one answer`,
464
551
  );
465
- return out;
552
+ // THE FACT IS THE FUSED ANSWER, not the call: every early return above hands
553
+ // back `primary` untouched. Untraced on purpose (meter.ts contract 1).
554
+ if (ctx.meter) ctx.meter.fuseRuns++;
555
+ // A FUSION IS A TRANSITION, and the only one in the engine that can splice the
556
+ // QUESTION'S OWN material into the product. So it is OFFERED to the law rather
557
+ // than built by hand: the law reads the window the fused product holds — the
558
+ // same reading the carries admission uses, one definition — accounts for the
559
+ // span it carried, and lets the question's remainder drain on EVIDENCE. A
560
+ // fusion that carries nothing consumes nothing, because the reading finds no
561
+ // window; and `reaches` is declared only when the fusion composed another root,
562
+ // which is structure this derivation had not stood on.
563
+ const fusedT: Continuation = {
564
+ product: out,
565
+ contains: true,
566
+ reaches: rest.length > 0,
567
+ cost: STEP,
568
+ };
569
+ const fusedWitness = admissible(state, fusedT, query, ctx.space.maxGroup);
570
+ if (fusedWitness === null) return { ...state, product: out };
571
+ const fused = advance(state, fusedT, fusedWitness);
572
+ if (ctx.meter) {
573
+ ctx.meter.closureDrainedBytes += unaccountedBytes(state.remainder) -
574
+ unaccountedBytes(fused.remainder);
575
+ }
576
+ return fused;
466
577
  }
467
-
468
- // (resonance.js is already a static dependency above — `bridge` — so the old
469
- // dynamic import of pivotInto guarded against a cycle that does not exist.)
470
- import { containsSpan } from "./match.js";
@@ -535,7 +535,10 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
535
535
  // emitted, so a caller can retry a trimmed edge on the miss path only.
536
536
  if (end - start < W) return false;
537
537
  if (flatProbe(start, end) === null) {
538
- if (!canonBudget) return false;
538
+ if (!canonBudget) {
539
+ if (ctx.meter) ctx.meter.canonProbesDenied++;
540
+ return false;
541
+ }
539
542
  if (!canonAdmits(start, end)) return false;
540
543
  }
541
544
  const id = resolveSpan(start, end);
@@ -556,13 +559,6 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
556
559
  // endpoints in order and running out partway along the query. A form
557
560
  // longer than that is out of this tier's reach — but so is a form the
558
561
  // chain cannot span, and that is exactly the trade the budget prices.
559
- // The factor is chainReach(W), the same W² scale the chain already
560
- // trusts; no new constant.
561
- // The factor is chainReach(W) — the same W² scale the chain itself
562
- // trusts — so the cap is derived from the fold's geometry, never tuned.
563
- // (It was briefly an environment variable while the cost was being
564
- // measured; an env-read here would make inference non-reproducible,
565
- // which the determinism contract forbids outright.)
566
562
  // The factor is chainReach(W) — the same W² scale the chain itself
567
563
  // trusts — so the cap is derived from the fold's geometry, never tuned.
568
564
  // (It was briefly an environment variable while the cost was being
@@ -329,7 +329,14 @@ export async function pivotInto(
329
329
  consumed: ReadonlySet<number>,
330
330
  voiced: readonly Uint8Array[] = [],
331
331
  ): Promise<number | null> {
332
- const k = ctx.cfg.recallQueryK;
332
+ // The pivot's OWN shortlist capacity — not `recallQueryK`: they are different
333
+ // quantities (a mechanical sweep's probe budget vs the bridge's candidate-read
334
+ // allowance), and one number serving both means tightening either silently
335
+ // starves the other. The reason is the duplication, NOT a measured strangle:
336
+ // an earlier version of this comment claimed "at recallQueryK 1 the pivot finds
337
+ // no pivot", and that was probed and is FALSE on test/23's fixture — the
338
+ // strangle is fixture-specific, so it is not the evidence for this split.
339
+ const k = ctx.cfg.pivotProbeK;
333
340
  // ONE perception of the answer, shared by the probe budget and the walk —
334
341
  // this used to fold the same bytes twice, back to back, on every hop.
335
342
  const tree = perceive(ctx, answer);
@@ -365,6 +372,17 @@ export async function pivotInto(
365
372
  }
366
373
  for (const c of n.kids) queue.push(c); // breadth-first: larger regions first
367
374
  }
375
+ // The sweep's two FACTS, untraced (meter.ts contract 1: a counter never
376
+ // reaches a decision). `probes` is the work done; the shortfall is the
377
+ // capacity the cap withheld — the branches the breadth-first order never got
378
+ // to. Whether that withheld anything that mattered is NOT said here: the
379
+ // order spends the largest regions first, and recognition below still
380
+ // contributes every exact containment candidate regardless of the budget.
381
+ if (ctx.meter) {
382
+ ctx.meter.pivotProbes += probes;
383
+ const unprobed = branchCount - probeCap;
384
+ if (unprobed > 0) ctx.meter.pivotBranchesUnprobed += unprobed;
385
+ }
368
386
  // THE FULL recognition, memo-shared with every other reader of these bytes.
369
387
  // A "skip the edge trims here" variant was refuted (see recognise's own
370
388
  // note): those trims are what find a WHOLE trained form embedded at an
package/src/mind/trace.ts CHANGED
@@ -18,6 +18,7 @@ export function rItem(
18
18
  ): RationaleItem {
19
19
  return {
20
20
  text: decodeText(bytes),
21
+ bytes,
21
22
  role,
22
23
  node: node ?? undefined,
23
24
  span,
@@ -10,6 +10,13 @@ import { cosine, Vec } from "../vec.js";
10
10
  import type { AncestorReach, MindContext, SaturationStop } from "./types.js";
11
11
  import { gistOf, read } from "./primitives.js";
12
12
  import { canonicalWindows, leafIdPrefix, leafIdRun } from "./canonical.js";
13
+ // Imported at the TOP, where every other import is. They used to sit 800 lines
14
+ // down under a note claiming the position mattered ("before trace module is
15
+ // loaded") — it does not: an ES module's static imports are HOISTED, so the
16
+ // file's line order never decides load order. The note described an intention
17
+ // the runtime does not honour; the imports move and the claim goes.
18
+ import { decodeText } from "./rationale.js";
19
+ import type { RationaleItem } from "./rationale.js";
13
20
 
14
21
  // ── Session structural memo ─────────────────────────────────────────────
15
22
  //
@@ -824,11 +831,6 @@ export function chooseAmong(
824
831
  : { id: candidates[0], score: -Infinity };
825
832
  }
826
833
 
827
- // ── Trace shim (used by chooseNext before trace module is loaded) ────────
828
-
829
- import { decodeText } from "./rationale.js";
830
- import type { RationaleItem } from "./rationale.js";
831
-
832
834
  function rItemShort(
833
835
  ctx: MindContext,
834
836
  id: number,