@hviana/sema 0.8.3 → 0.8.6

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 (88) 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 +31 -5
  7. package/dist/src/meter.js +31 -5
  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 +191 -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 +78 -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 +31 -5
  55. package/src/mind/articulation.ts +0 -1
  56. package/src/mind/attention.ts +2 -1
  57. package/src/mind/derivation.ts +477 -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 +220 -178
  72. package/src/mind/types.ts +5 -2
  73. package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +3 -3
  74. package/test/133-the-decision-point-renders-the-state.test.mjs +204 -0
  75. package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
  76. package/test/135-one-law-any-producer.test.mjs +289 -0
  77. package/test/136-the-two-named-limits.test.mjs +205 -0
  78. package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
  79. package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
  80. package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
  81. package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
  82. package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
  83. package/test/142-the-layer-offers-only-what-the-law-admits.test.mjs +93 -0
  84. package/test/143-cycles-terminate-and-are-not-closure.test.mjs +62 -0
  85. package/test/36-already-answered-fusion.test.mjs +20 -2
  86. package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
  87. package/test/38-reason-restate-guard.test.mjs +22 -2
  88. package/test/55-cost-meter.test.mjs +6 -3
@@ -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,67 @@ 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
+ if (ctx.meter) ctx.meter.offerRuns += 1;
210
+ const cur = d.product;
211
+ // The first step's `cur` IS the grounding's product, so the guard above
212
+ // already resolved it and read its reverse edges — reuse both.
206
213
  const curId = hop === 0 ? groundedId : resolve(ctx, cur);
207
214
  consumeNode(curId, hop === 0 ? groundedPrev ?? undefined : undefined);
215
+ hop++;
208
216
 
209
217
  // 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.
218
+ // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows nothing
219
+ // of `consumed` and may still walk to a consumed fixpoint — absorbing it
220
+ // would repeat content the grounding stage already spoke for, so a consumed
221
+ // fixpoint falls through to the pivot step instead. Offered with `moves`:
222
+ // completing the answer's OWN learnt form is an identity step, not a claim
223
+ // about the asker's material.
214
224
  if (
215
225
  curId !== null &&
216
226
  ctx.store.nextFirst(curId, bound).some((n) => !consumed.has(n))
@@ -220,133 +230,129 @@ export async function reason(
220
230
  if (
221
231
  fwd !== null && !bytesEqual(fwd, cur) &&
222
232
  (fwdId === null || !consumed.has(fwdId)) &&
223
- !restatesQuery(query, fwd)
233
+ !restates(query, fwd, 0, { proper: true })
224
234
  ) {
225
235
  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;
236
+ pending = { kind: "absorb", cur, curId, fwd };
237
+ return { product: fwd, contains: true, reaches: true, cost: STEP };
238
238
  }
239
239
  }
240
240
 
241
- // Pivot: find the longest unconsumed learnt context the answer contains.
241
+ // Pivot: the longest unconsumed learnt context the answer contains.
242
242
  consumeAll(curId);
243
243
  const pivot = await pivotInto(ctx, cur, consumed, voiced);
244
- if (pivot === null) break;
245
-
244
+ if (pivot === null) return null;
246
245
  const fc = await follow(ctx, pivot, qv);
247
246
  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;
247
+ if (
248
+ fc === null || bytesEqual(fc, cur) ||
249
+ restates(query, fc, 0, { proper: true })
250
+ ) {
251
+ return null;
252
+ }
253
+ pending = { kind: "pivot", cur, pivot, fc };
254
+ // Offered with the identity species ONLY when the grounding declared what it
255
+ // speaks for; otherwise the law requires this step to carry question
256
+ // material the grounding left unaccounted, which is the drift the extension
257
+ // tests pin — refused by the law's own measure, not by a private window
258
+ // test.
259
+ return {
260
+ product: fc,
261
+ contains: true,
262
+ reaches: producerOwnsShape,
263
+ cost: STEP,
264
+ };
265
+ };
266
+
267
+ const closed_ = await closure(
268
+ d0,
269
+ query,
270
+ W,
271
+ offer,
272
+ (before, after, witnesses) => {
273
+ if (ctx.meter) {
274
+ ctx.meter.reasonCarriedBytes += witnesses.reduce(
275
+ (n, w) => n + (w.window ? w.window[1] - w.window[0] : 0),
276
+ 0,
277
+ );
278
+ ctx.meter.closureDrainedBytes += unaccountedBytes(before.remainder) -
279
+ unaccountedBytes(after.remainder);
281
280
  }
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);
281
+ const p = pending;
282
+ pending = null;
283
+ if (p === null) return;
284
+ t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
285
+ if (p.kind === "absorb") {
312
286
  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`,
287
+ "absorbForward",
288
+ [rItem(p.cur, "answer", p.curId ?? undefined)],
289
+ [rItem(p.fwd, "answer", resolve(ctx, p.fwd) ?? undefined)],
290
+ "the answer is itself a learnt fact — follow its continuation to the fixpoint",
291
+ );
292
+ } else {
293
+ if (ctx.meter) ctx.meter.pivotSteps++;
294
+ ctx.trace?.step(
295
+ "pivotStep",
296
+ [rItem(p.cur, "answer"), rNode(ctx, p.pivot, "pivot")],
297
+ [rItem(p.fc, "answer", resolve(ctx, p.fc) ?? undefined)],
298
+ "pivot on the shared span this answer contains, then step forward across that fact",
318
299
  );
319
- break;
320
300
  }
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
- }
301
+ },
302
+ (at) => {
303
+ // THE LAW REFUSED — counted where it happened, before the bookkeeping
304
+ // below decides whether there is a step to report.
305
+ if (ctx.meter) ctx.meter.lawRejects += 1;
306
+ const p = pending;
307
+ pending = null;
308
+ if (p === null || p.kind !== "pivot") return;
309
+ // THE BRAKE, MADE VISIBLE — and it is now the LAW's refusal, reported
310
+ // where it happened. The reasoner declines a step that carries none of
311
+ // the material the grounding left unaccounted. A refusal that leaves no
312
+ // trace is the kind of silent cut AGENTS §6 forbids: the rationale is
313
+ // where a reader learns that an extension was declined for want of
314
+ // question material, and where the next person sees why the chain stopped
315
+ // here. Measured with the check disabled, test/110 and test/116 fail.
316
+ const left = unaccountedBytes(at.remainder);
317
+ ctx.trace?.step(
318
+ "pivotRefused",
319
+ [rItem(p.cur, "answer"), rItem(query, "query")],
320
+ at.remainder.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")),
321
+ `the step carries none of the question material the grounding left ` +
322
+ `unaccounted (${left} byte(s) in ${at.remainder.length} span(s)) — refused`,
323
+ );
324
+ },
325
+ );
326
+
333
327
  // INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
334
328
  // 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.
329
+ // needs to PRICE the extension instead of taking it unconditionally, and both
330
+ // are now read off the state the law advanced rather than accumulated beside
331
+ // it: the work it did is the cost it accumulated, and the material it carried
332
+ // is the accounting the law's witnesses added.
333
+ const steps = closed_.cost - d0.cost;
337
334
  if (ctx.meter) {
338
335
  ctx.meter.reasonSteps += steps;
339
- ctx.meter.reasonCarriedBytes += unaccountedBytes(carried);
336
+ // The accounting the law's witnesses added, summed by the law's own function
337
+ // over the tail of the state's list — and computed ONLY when a meter is
338
+ // attached: this used to slice and MAP a fresh array on every response, for a
339
+ // counter that usually does not exist. One allocation, under the meter.
340
+ ctx.meter.reasonAccountedBytes += unaccountedBytes(
341
+ closed_.accounted.slice(d0.accounted.length),
342
+ );
340
343
  }
341
344
  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.
345
+ [rItem(
346
+ closed_.product,
347
+ "answer",
348
+ resolve(ctx, closed_.product) ?? undefined,
349
+ )],
350
+ // A FIXPOINT: no further step was offered, or the law refused the one that
351
+ // was. There is no allowance to exhaust, so the note is true by
352
+ // construction.
347
353
  "the multi-hop chain's fixpoint",
348
354
  );
349
- return { bytes: cur, carried, steps };
355
+ return closed_;
350
356
  }
351
357
 
352
358
  /** Fuse independent points of attention into one answer (multi-topic).
@@ -356,7 +362,7 @@ export async function reason(
356
362
  export async function fuseAttention(
357
363
  ctx: MindContext,
358
364
  query: Uint8Array,
359
- primary: Uint8Array,
365
+ state: DerivationState,
360
366
  pre: Precomputed,
361
367
  /** True when `primary` never touched the consensus climb at all — e.g. a
362
368
  * pure ALU computation, which has no anchor of its own. commitVotes
@@ -371,8 +377,13 @@ export async function fuseAttention(
371
377
  * which is the layer that knows how a given grounding records its evidence;
372
378
  * fuseAttention just reads a position from it. Empty or absent preserves
373
379
  * the original behaviour exactly. */
374
- primarySpans: ReadonlyArray<readonly [number, number]> = [],
375
- ): Promise<Uint8Array> {
380
+ primarySpans: ReadonlyArray<Span> = [],
381
+ ): Promise<DerivationState> {
382
+ // THE STATE, NOT BARE BYTES: fusion is one more transition of the derivation
383
+ // the walk returned, so it reads that state's product and hands back a state.
384
+ // Its own structural gates stay here — this is the layer that can resolve
385
+ // those witnesses, and the law never re-derives one.
386
+ const primary = state.product;
376
387
  // When the answer is structurally drawn from the query itself
377
388
  // (extraction), it already spans all the query's pieces — fusion
378
389
  // would only add noise from unrelated stored contexts. The gate is
@@ -380,7 +391,7 @@ export async function fuseAttention(
380
391
  // byte run): the old sparse-subsequence test was trivially satisfied by
381
392
  // short answers over long queries, silently starving multi-topic queries
382
393
  // of fusion.
383
- if (containsSpan(ctx, query, primary)) return primary;
394
+ if (containsSpan(ctx, query, primary)) return state;
384
395
 
385
396
  // The committed points of attention ARE the shared climb's roots (same
386
397
  // query, same k, same DF mode) — read them from Precomputed instead of
@@ -423,7 +434,7 @@ export async function fuseAttention(
423
434
  const lonePromotes = unclimbed && forest.length === 1 &&
424
435
  forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
425
436
  if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
426
- return primary;
437
+ return state;
427
438
  }
428
439
 
429
440
  // WHERE THE QUERY ASKED FOR IT. The sort below orders the fused pieces by
@@ -506,7 +517,13 @@ export async function fuseAttention(
506
517
  // the root resonates.
507
518
  const cont = await follow(ctx, root.anchor, qv);
508
519
  if (
509
- cont !== null && cont.length > 0 && indexOf(query, cont, root.end) >= 0
520
+ cont !== null && cont.length > 0 &&
521
+ // The law's positional reading: the caller knows the material it is
522
+ // looking for lies AT OR AFTER this root, which is the witness; the law
523
+ // owns the containment test itself. The `cont.length > 0` guard stays
524
+ // here because an EMPTY continuation indexes at every offset — the law
525
+ // has no opinion about whether "nothing" counts as said.
526
+ restates(query, cont, 0, { from: root.end })
510
527
  ) {
511
528
  ctx.trace?.step(
512
529
  "alreadyAnswered",
@@ -526,7 +543,7 @@ export async function fuseAttention(
526
543
  [rItem(primary, "answer")],
527
544
  "no further independent point grounded",
528
545
  );
529
- return primary;
546
+ return state;
530
547
  }
531
548
 
532
549
  pieces.sort((a, b) => a.start - b.start);
@@ -543,5 +560,30 @@ export async function fuseAttention(
543
560
  // THE FACT IS THE FUSED ANSWER, not the call: every early return above hands
544
561
  // back `primary` untouched. Untraced on purpose (meter.ts contract 1).
545
562
  if (ctx.meter) ctx.meter.fuseRuns++;
546
- return out;
563
+ // A FUSION IS A TRANSITION, and the only one in the engine that can splice the
564
+ // QUESTION'S OWN material into the product. So it is OFFERED to the law rather
565
+ // than built by hand: the law reads the window the fused product holds — the
566
+ // same reading the carries admission uses, one definition — accounts for the
567
+ // span it carried, and lets the question's remainder drain on EVIDENCE. A
568
+ // fusion that carries nothing consumes nothing, because the reading finds no
569
+ // window; and `reaches` is declared only when the fusion composed another root,
570
+ // which is structure this derivation had not stood on.
571
+ const fusedT: Continuation = {
572
+ product: out,
573
+ contains: true,
574
+ reaches: rest.length > 0,
575
+ cost: STEP,
576
+ };
577
+ const fusedWitness = admissible(state, fusedT, query, ctx.space.maxGroup);
578
+ if (fusedWitness === null) return { ...state, product: out };
579
+ const fused = advance(state, fusedT, fusedWitness);
580
+ if (ctx.meter) {
581
+ ctx.meter.reasonCarriedBytes += fusedWitness.reduce(
582
+ (n, w) => n + (w.window ? w.window[1] - w.window[0] : 0),
583
+ 0,
584
+ );
585
+ ctx.meter.closureDrainedBytes += unaccountedBytes(state.remainder) -
586
+ unaccountedBytes(fused.remainder);
587
+ }
588
+ return fused;
547
589
  }
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
@@ -9,7 +9,7 @@
9
9
  //
10
10
  // MEASURED BEFORE THIS TEST EXISTED, one call per size:
11
11
  //
12
- // N= 250 / 500 / 1000 / 2000 ⇒ reasonSteps 1, reasonCarriedBytes 11,
12
+ // N= 250 / 500 / 1000 / 2000 ⇒ reasonSteps 1, reasonAccountedBytes 11,
13
13
  // pops 338 (k = 0.000), same answer
14
14
  //
15
15
  // THE BARS ARE THE REPO'S OWN (trap 4 — no new numbers): test/89 asserts k < 1 for
@@ -72,7 +72,7 @@ async function measure(n) {
72
72
  return {
73
73
  answer,
74
74
  steps: c.reasonSteps ?? 0,
75
- carried: c.reasonCarriedBytes ?? 0,
75
+ carried: c.reasonAccountedBytes ?? 0,
76
76
  pops: c.searchPops ?? 0,
77
77
  };
78
78
  }
@@ -91,7 +91,7 @@ test("a bigger corpus does not buy more extension work for the same answer", asy
91
91
  steps.push(r.steps);
92
92
  answers.push(r.answer);
93
93
  console.log(
94
- ` N=${n} → reasonSteps ${r.steps}, reasonCarriedBytes ${r.carried}, ` +
94
+ ` N=${n} → reasonSteps ${r.steps}, reasonAccountedBytes ${r.carried}, ` +
95
95
  `pops ${r.pops}`,
96
96
  );
97
97
  }