@hviana/sema 0.8.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/AGENTS.md +11 -10
  2. package/README.md +17 -38
  3. package/dist/example/demo.js +85 -34
  4. package/dist/src/geometry.d.ts +0 -10
  5. package/dist/src/geometry.js +0 -12
  6. package/dist/src/meter.d.ts +11 -0
  7. package/dist/src/meter.js +11 -0
  8. package/dist/src/mind/articulation.js +1 -1
  9. package/dist/src/mind/attention.js +2 -1
  10. package/dist/src/mind/derivation.d.ts +201 -0
  11. package/dist/src/mind/derivation.js +327 -0
  12. package/dist/src/mind/graph-search.d.ts +2 -1
  13. package/dist/src/mind/graph-search.js +37 -15
  14. package/dist/src/mind/match.d.ts +2 -0
  15. package/dist/src/mind/match.js +2 -0
  16. package/dist/src/mind/mechanisms/alu.js +0 -2
  17. package/dist/src/mind/mechanisms/cast.d.ts +1 -5
  18. package/dist/src/mind/mechanisms/cast.js +15 -18
  19. package/dist/src/mind/mechanisms/confluence.d.ts +0 -3
  20. package/dist/src/mind/mechanisms/confluence.js +3 -9
  21. package/dist/src/mind/mechanisms/cover.js +17 -20
  22. package/dist/src/mind/mechanisms/extraction.d.ts +0 -1
  23. package/dist/src/mind/mechanisms/extraction.js +13 -8
  24. package/dist/src/mind/mechanisms/prefix-completion.js +0 -1
  25. package/dist/src/mind/mechanisms/recall.d.ts +0 -1
  26. package/dist/src/mind/mechanisms/recall.js +8 -9
  27. package/dist/src/mind/mechanisms/reference.js +3 -4
  28. package/dist/src/mind/pipeline-mechanism.d.ts +1 -4
  29. package/dist/src/mind/pipeline.js +106 -41
  30. package/dist/src/mind/rationale.d.ts +0 -11
  31. package/dist/src/mind/rationale.js +6 -32
  32. package/dist/src/mind/reasoning.d.ts +4 -30
  33. package/dist/src/mind/reasoning.js +183 -151
  34. package/dist/src/mind/types.js +6 -3
  35. package/docs/INDEX.md +23 -24
  36. package/docs/INVARIANTS.md +16 -17
  37. package/docs/architecture/bounded-reads.md +4 -4
  38. package/docs/architecture/closure.md +65 -0
  39. package/docs/architecture/commonality.md +27 -18
  40. package/docs/architecture/cost-model.md +5 -5
  41. package/docs/architecture/exact-vs-approximate.md +4 -4
  42. package/docs/architecture/factored-machinery.md +14 -14
  43. package/docs/architecture/mechanism-market.md +9 -9
  44. package/docs/architecture/meter.md +4 -5
  45. package/docs/architecture/store.md +2 -2
  46. package/docs/architecture/thresholds.md +1 -1
  47. package/docs/failures/tempting-but-wrong.md +11 -1
  48. package/docs/harness/gates.md +6 -6
  49. package/docs/mechanisms/cover.md +2 -2
  50. package/example/demo.ts +90 -37
  51. package/jsr.json +1 -1
  52. package/package.json +1 -1
  53. package/src/geometry.ts +0 -13
  54. package/src/meter.ts +11 -0
  55. package/src/mind/articulation.ts +0 -1
  56. package/src/mind/attention.ts +2 -1
  57. package/src/mind/derivation.ts +473 -0
  58. package/src/mind/graph-search.ts +37 -20
  59. package/src/mind/match.ts +2 -0
  60. package/src/mind/mechanisms/alu.ts +0 -2
  61. package/src/mind/mechanisms/cast.ts +17 -21
  62. package/src/mind/mechanisms/confluence.ts +3 -13
  63. package/src/mind/mechanisms/cover.ts +17 -20
  64. package/src/mind/mechanisms/extraction.ts +13 -9
  65. package/src/mind/mechanisms/prefix-completion.ts +0 -1
  66. package/src/mind/mechanisms/recall.ts +7 -9
  67. package/src/mind/mechanisms/reference.ts +2 -3
  68. package/src/mind/pipeline-mechanism.ts +1 -4
  69. package/src/mind/pipeline.ts +121 -46
  70. package/src/mind/rationale.ts +6 -36
  71. package/src/mind/reasoning.ts +208 -178
  72. package/src/mind/types.ts +5 -2
  73. package/test/133-the-decision-point-renders-the-state.test.mjs +204 -0
  74. package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
  75. package/test/135-one-law-any-producer.test.mjs +289 -0
  76. package/test/136-the-two-named-limits.test.mjs +205 -0
  77. package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
  78. package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
  79. package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
  80. package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
  81. package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
  82. package/test/36-already-answered-fusion.test.mjs +20 -2
  83. package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
  84. package/test/38-reason-restate-guard.test.mjs +22 -2
  85. package/test/55-cost-meter.test.mjs +4 -1
@@ -10,22 +10,18 @@ import { resolve } from "./primitives.js";
10
10
  import { corpusN, hubBound } from "./traverse.js";
11
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, unaccountedBytes } 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,
@@ -42,31 +38,19 @@ export function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean {
42
38
  * when it declared one — see the pivot's own containment rule. `pre` is the
43
39
  * response's shared pre-computation — the post-grounding stages read the
44
40
  * same container the mechanisms did. */
45
- /** What the multi-hop extension produced, and what it cost: the bytes (the
46
- * answer), the spans of the grounding's UNCOVERED material that each step was
47
- * justified by, and how many steps were taken. The last two exist so the
48
- * caller can price the extension in the ladder's own currency — `steps · STEP`
49
- * against `PASS · unaccounted` — instead of taking it unconditionally. Both
50
- * are FACTS, not verdicts: nothing here says whether the extension was worth
51
- * it; that is the comparison's job, one layer up. */
52
- export interface ReasonedAnswer {
53
- bytes: Uint8Array;
54
- carried: Array<[number, number]>;
55
- steps: number;
56
- }
57
-
58
41
  export async function reason(
59
42
  ctx: MindContext,
60
43
  query: Uint8Array,
61
- answer: Uint8Array,
44
+ d0: DerivationState,
62
45
  preConsumed: ReadonlySet<number>,
63
46
  pre: Precomputed,
64
47
  voiced: readonly Uint8Array[] = [],
65
- /** The query material the GROUNDING left uncovered — the cost ladder's own
66
- * `unaccounted` spans. Only the reasoner's OWN extensions are judged
67
- * against it; a mechanism carrying its own `used` set owns its shape. */
68
- uncovered: readonly (readonly [number, number])[] = [],
69
- ): Promise<ReasonedAnswer> {
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;
70
54
  // Echo guard: a query that is ITSELF a learnt continuation (some context's
71
55
  // answer) is being asked back at the system — hopping forward from it would
72
56
  // chain through the very fact that produced it and echo the conversation
@@ -74,7 +58,7 @@ export async function reason(
74
58
  // broad structural gate; pinned by test/31-audit.
75
59
  const qId = pre.queryResolved;
76
60
  if (qId !== null && ctx.store.prevCount(qId) > 0) {
77
- return { bytes: answer, carried: [], steps: 0 };
61
+ return d0;
78
62
  }
79
63
 
80
64
  // Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
@@ -94,7 +78,7 @@ export async function reason(
94
78
  // is nothing left to chain for.
95
79
  //
96
80
  // Every stopping condition in the loop below judges the ANSWER (`consumed` /
97
- // `restatesQuery` / `bytesEqual`); none asks whether the QUESTION was
81
+ // the law's `restates` / `bytesEqual`); none asks whether the QUESTION was
98
82
  // satisfied. So a single-hop question whose answer happens to name another
99
83
  // learnt context extends past a correct answer and REPLACES it:
100
84
  //
@@ -134,7 +118,7 @@ export async function reason(
134
118
  ? null
135
119
  : ctx.store.prevFirst(groundedId, bound);
136
120
  if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
137
- return { bytes: answer, carried: [], steps: 0 };
121
+ return d0;
138
122
  }
139
123
 
140
124
  const consumed = new Set<number>();
@@ -176,41 +160,66 @@ export async function reason(
176
160
  await preconsume();
177
161
  }
178
162
 
179
- let cur = answer;
180
- const qv = pre.guide; // the response-wide guide IS the query's gist
181
- let t: ReturnType<Rationale["enter"]> | undefined;
182
- const startedFrom = answer;
183
- // INSTRUMENTATION ONLY — the two facts the extension's own decision already
184
- // used and threw away: the spans of uncovered material each step was
185
- // JUSTIFIED by (the gate below computes which span carries it and kept only a
186
- // boolean), and how many steps were taken. Nothing here decides anything:
187
- // both are read after the loop, to bump counters and to let the caller compare
188
- // the extension's cost against what it explains, in the ladder's own currency.
189
- const carried: Array<[number, number]> = [];
190
- let steps = 0;
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
+ //
191
172
  // NO ALLOWANCE: THE CHAIN ENDS WHEN IT STOPS. Every exit below is the law —
192
173
  // no pivot, no forward step, no question material carried — and the walk is
193
- // bounded by the material and the graph rather than by a count: each taken
194
- // step must carry a W-window of the uncovered material (finite), and
195
- // `consumed` refuses to revisit a node. `recallQueryK` no longer bounds the
196
- // reasoner here; it keeps its other roles (the bridge's candidate reads, the
197
- // pivot's probe budget, the resonance limits).
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).
198
179
  //
199
- // Measured before removing it: test/89 — the corpus-cost guard, the heaviest
200
- // case in the suite — is green and no slower without the allowance (27 s
201
- // against 30 s); and raising it from 12 to 200 changed neither the answer nor
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
202
183
  // `pivotSteps` on the chain fixtures.
203
- for (let hop = 0;; hop++) {
204
- // Hop 0's `cur` IS `answer`, so the guard above already resolved it and
205
- // read its reverse edges — reuse both rather than repeat them.
184
+ const qv = pre.guide; // the response-wide guide IS the query's gist
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;
196
+ const startedFrom = answer;
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.
206
212
  const curId = hop === 0 ? groundedId : resolve(ctx, cur);
207
213
  consumeNode(curId, hop === 0 ? groundedPrev ?? undefined : undefined);
214
+ hop++;
208
215
 
209
216
  // Forward-absorb: follow only UNCONSUMED continuations. The gate below
210
- // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows
211
- // nothing of `consumed` and may still walk to a consumed fixpoint —
212
- // absorbing it would repeat content the grounding stage already spoke
213
- // 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.
214
223
  if (
215
224
  curId !== null &&
216
225
  ctx.store.nextFirst(curId, bound).some((n) => !consumed.has(n))
@@ -220,133 +229,122 @@ export async function reason(
220
229
  if (
221
230
  fwd !== null && !bytesEqual(fwd, cur) &&
222
231
  (fwdId === null || !consumed.has(fwdId)) &&
223
- !restatesQuery(query, fwd)
232
+ !restates(query, fwd, 0, { proper: true })
224
233
  ) {
225
234
  consumeAll(curId);
226
- t ??= ctx.trace?.enter("reason", [
227
- rItem(startedFrom, "grounded"),
228
- ]);
229
- ctx.trace?.step(
230
- "absorbForward",
231
- [rItem(cur, "answer", curId)],
232
- [rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)],
233
- "the answer is itself a learnt fact — follow its continuation to the fixpoint",
234
- );
235
- cur = fwd;
236
- steps++;
237
- continue;
235
+ pending = { kind: "absorb", cur, curId, fwd };
236
+ return { product: fwd, contains: true, reaches: true, cost: STEP };
238
237
  }
239
238
  }
240
239
 
241
- // Pivot: find the longest unconsumed learnt context the answer contains.
240
+ // Pivot: the longest unconsumed learnt context the answer contains.
242
241
  consumeAll(curId);
243
242
  const pivot = await pivotInto(ctx, cur, consumed, voiced);
244
- if (pivot === null) break;
245
-
243
+ if (pivot === null) return null;
246
244
  const fc = await follow(ctx, pivot, qv);
247
245
  consumeAll(pivot);
248
- if (fc === null || bytesEqual(fc, cur) || restatesQuery(query, fc)) break;
249
- // WHOSE EXTENSION IS THIS?
250
- //
251
- // `voiced` is what the mechanism WITHHELD (the pipeline sends the used
252
- // anchors' CONTINUATIONS, not their bytes — see pipeline's own note), so a
253
- // non-empty `voiced` means exactly what that note says: the grounding came
254
- // from a mechanism that carries its own short `used` set (cast/join) and
255
- // therefore owns the shape of its answer. The further terms inside such a
256
- // seat are legitimately followable — test/29 C3's `Mona Lisa` lives inside
257
- // the voiced seat and leads on to a fact about neither analog.
258
- //
259
- // Every other grounding is ordinary, and an extension of it is the
260
- // reasoner's own inference: it is taken only while question material the
261
- // grounding left uncovered remains AND the step carries some of it, judged
262
- // by the mind's own line between chance and evidence — one W-byte window,
263
- // no word notion, no character class, no threshold. Measured: the drift's
264
- // second step (`the Eiffel Tower is in Paris` after `Paris is famous for
265
- // the Eiffel Tower`) carries no window of `" famous for"` and is refused,
266
- // while the first carries it. Terminates by a real argument: the uncovered
267
- // material is finite and each taken extension must carry some of it.
268
- const producerOwnsShape = voiced.length > 0;
269
- if (!producerOwnsShape && uncovered.length > 0) {
270
- const W = ctx.space.maxGroup;
271
- let progress = false;
272
- let justified: [number, number] | undefined;
273
- for (const [a, b] of uncovered) {
274
- for (let i = a; i + W <= b && !progress; i++) {
275
- if (indexOf(fc, query.subarray(i, i + W), 0) >= 0) {
276
- progress = true;
277
- justified = [a, b];
278
- }
279
- }
280
- 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);
281
275
  }
282
- if (progress && justified !== undefined) carried.push(justified);
283
- if (!progress) {
284
- // THE BRAKE, MADE VISIBLE. The reasoner declines a step that carries
285
- // none of the material the grounding left uncovered — the drift the
286
- // extension tests pin. A refusal that leaves no trace is the kind of
287
- // silent cut AGENTS §6 forbids: the rationale is where a reader learns
288
- // that an extension was declined for want of question material, and
289
- // where the next person sees why the chain stopped here. Measured with
290
- // the check disabled, test/110 and test/116 fail — so this brake is the
291
- // only thing keeping the extension honest until the pivot reports its
292
- // own accounted spans and the ladder can judge it instead.
293
- //
294
- // THE PROMISE IS NOW KEPT, AND THE BRAKE TURNS OUT TO BE THE LADDER'S
295
- // OWN CONSEQUENCE. The extension reports what it carried (`carried`,
296
- // the span each step was justified by) and what it cost (`steps`), both
297
- // counted in the meter (`reasonCarriedBytes`, `reasonSteps`), so the
298
- // ladder CAN judge it: it accepts while
299
- //
300
- // steps · STEP < PASS · carried
301
- //
302
- // and this brake accepts whenever the step carries a `W`-window, i.e.
303
- // whenever `carried ≥ W ≥ 1`. With `PASS/STEP = 1000` the two therefore
304
- // agree on every extension with `steps ≤ 1000 · carried` — and every
305
- // extension this repository produces takes 0 or 1 steps (measured on
306
- // chains of 3, 8, 20 and 40 links). Above that bound the ladder would
307
- // refuse what this brake accepts, which is the corner named in the
308
- // closure report's limits: a chain of thousands of links explaining a
309
- // handful of bytes. No guard is added for it — a limit without a
310
- // derivation is exactly what the brake must not become.
311
- const left = unaccountedBytes(uncovered);
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") {
312
281
  ctx.trace?.step(
313
- "pivotRefused",
314
- [rItem(cur, "answer"), rItem(query, "query")],
315
- uncovered.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")),
316
- `the step carries none of the question material the grounding left ` +
317
- `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",
318
294
  );
319
- break;
320
295
  }
321
- }
322
- if (ctx.meter) ctx.meter.pivotSteps++;
323
- t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
324
- ctx.trace?.step(
325
- "pivotStep",
326
- [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")],
327
- [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)],
328
- "pivot on the shared span this answer contains, then step forward across that fact",
329
- );
330
- cur = fc;
331
- steps++;
332
- }
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
+
333
319
  // INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
334
320
  // contract 1: a counter never reaches a decision). They are what a caller
335
- // needs to PRICE the extension instead of taking it unconditionally: the work
336
- // it did (`steps · STEP`) and the uncovered material it carried.
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;
337
326
  if (ctx.meter) {
338
327
  ctx.meter.reasonSteps += steps;
339
- ctx.meter.reasonCarriedBytes += unaccountedBytes(carried);
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),
334
+ );
340
335
  }
341
336
  t?.done(
342
- [rItem(cur, "answer", resolve(ctx, cur) ?? undefined)],
343
- // A FIXPOINT: no further step was possible. This note used to also cover an
344
- // exhausted hop allowance — a different fact, and the reason F1 added a
345
- // counter for it. The allowance is gone, so the only way out of the loop is
346
- // a refusal, and the note is true again by construction.
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.
347
345
  "the multi-hop chain's fixpoint",
348
346
  );
349
- return { bytes: cur, carried, steps };
347
+ return closed_;
350
348
  }
351
349
 
352
350
  /** Fuse independent points of attention into one answer (multi-topic).
@@ -356,7 +354,7 @@ export async function reason(
356
354
  export async function fuseAttention(
357
355
  ctx: MindContext,
358
356
  query: Uint8Array,
359
- primary: Uint8Array,
357
+ state: DerivationState,
360
358
  pre: Precomputed,
361
359
  /** True when `primary` never touched the consensus climb at all — e.g. a
362
360
  * pure ALU computation, which has no anchor of its own. commitVotes
@@ -371,8 +369,13 @@ export async function fuseAttention(
371
369
  * which is the layer that knows how a given grounding records its evidence;
372
370
  * fuseAttention just reads a position from it. Empty or absent preserves
373
371
  * the original behaviour exactly. */
374
- primarySpans: ReadonlyArray<readonly [number, number]> = [],
375
- ): 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;
376
379
  // When the answer is structurally drawn from the query itself
377
380
  // (extraction), it already spans all the query's pieces — fusion
378
381
  // would only add noise from unrelated stored contexts. The gate is
@@ -380,7 +383,7 @@ export async function fuseAttention(
380
383
  // byte run): the old sparse-subsequence test was trivially satisfied by
381
384
  // short answers over long queries, silently starving multi-topic queries
382
385
  // of fusion.
383
- if (containsSpan(ctx, query, primary)) return primary;
386
+ if (containsSpan(ctx, query, primary)) return state;
384
387
 
385
388
  // The committed points of attention ARE the shared climb's roots (same
386
389
  // query, same k, same DF mode) — read them from Precomputed instead of
@@ -423,7 +426,7 @@ export async function fuseAttention(
423
426
  const lonePromotes = unclimbed && forest.length === 1 &&
424
427
  forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
425
428
  if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
426
- return primary;
429
+ return state;
427
430
  }
428
431
 
429
432
  // WHERE THE QUERY ASKED FOR IT. The sort below orders the fused pieces by
@@ -506,7 +509,13 @@ export async function fuseAttention(
506
509
  // the root resonates.
507
510
  const cont = await follow(ctx, root.anchor, qv);
508
511
  if (
509
- 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 })
510
519
  ) {
511
520
  ctx.trace?.step(
512
521
  "alreadyAnswered",
@@ -526,7 +535,7 @@ export async function fuseAttention(
526
535
  [rItem(primary, "answer")],
527
536
  "no further independent point grounded",
528
537
  );
529
- return primary;
538
+ return state;
530
539
  }
531
540
 
532
541
  pieces.sort((a, b) => a.start - b.start);
@@ -543,5 +552,26 @@ export async function fuseAttention(
543
552
  // THE FACT IS THE FUSED ANSWER, not the call: every early return above hands
544
553
  // back `primary` untouched. Untraced on purpose (meter.ts contract 1).
545
554
  if (ctx.meter) ctx.meter.fuseRuns++;
546
- return out;
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;
547
577
  }
package/src/mind/types.ts CHANGED
@@ -34,6 +34,7 @@ export interface DepositCacheEntry {
34
34
  content: ContentFold;
35
35
  }
36
36
  import { bytesEqual, concatBytes, indexOf } from "../bytes.js";
37
+ import { restates } from "./derivation.js";
37
38
  import { dominates } from "../geometry.js";
38
39
 
39
40
  // ═══════════════════════════════════════════════════════════════════════════
@@ -486,11 +487,13 @@ export function segRestatesQuery(
486
487
  W: number,
487
488
  ): boolean {
488
489
  if (!s.rec) return false;
490
+ // THE LITERAL EXEMPTION IS THE CALLER'S. A span that IS the site's own bytes
491
+ // at its own position is naming what is already there, not substituting for
492
+ // it — and only this caller knows that, so it says so by not asking.
489
493
  const literal = s.j - s.i === s.bytes.length &&
490
494
  bytesEqual(s.bytes, query.subarray(s.i, s.j));
491
495
  if (literal) return false;
492
- return s.bytes.length >= W && s.bytes.length < queryLen &&
493
- indexOf(query, s.bytes, 0) >= 0;
496
+ return restates(query, s.bytes, W, { proper: true });
494
497
  }
495
498
 
496
499
  /** Lift the answer out of the cover for think: the recognised region, free of