@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
@@ -1,16 +1,6 @@
1
1
  import type { MindContext } from "./types.js";
2
2
  import type { Precomputed } from "./pipeline-mechanism.js";
3
- /** Whether `bytes` is a proper byte-subspan of `query` — already present in
4
- * the question, so voicing it back only restates part of what was asked,
5
- * never answers it. The exact guard recallByResonance already applies to
6
- * its OWN grounding candidates (tier 1's `restates`, tier 2's subspan
7
- * check, tier 0b's argument-binding subspan check) — every mechanism that
8
- * walks a LEARNT CONTINUATION EDGE past an already-vetted grounding
9
- * (reason()'s own hops below, and CAST's `projectCounterfactual` seat
10
- * substitution — see cast.ts) needs the same guard applied to what the
11
- * walk turns up, since `follow()`/`chooseNext`/`pivotInto` know nothing of
12
- * the query at all — only of what structurally continues what. */
13
- export declare function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean;
3
+ import { type DerivationState, type Span } from "./derivation.js";
14
4
  /** Extend a grounded answer forward across facts (multi-hop reasoning).
15
5
  * Pivots on the longest unconsumed learnt context each answer contains,
16
6
  * then follows the pivot's continuation to the next fact. **The chain ends
@@ -26,28 +16,12 @@ export declare function restatesQuery(query: Uint8Array, bytes: Uint8Array): boo
26
16
  * when it declared one — see the pivot's own containment rule. `pre` is the
27
17
  * response's shared pre-computation — the post-grounding stages read the
28
18
  * same container the mechanisms did. */
29
- /** What the multi-hop extension produced, and what it cost: the bytes (the
30
- * answer), the spans of the grounding's UNCOVERED material that each step was
31
- * justified by, and how many steps were taken. The last two exist so the
32
- * caller can price the extension in the ladder's own currency — `steps · STEP`
33
- * against `PASS · unaccounted` — instead of taking it unconditionally. Both
34
- * are FACTS, not verdicts: nothing here says whether the extension was worth
35
- * it; that is the comparison's job, one layer up. */
36
- export interface ReasonedAnswer {
37
- bytes: Uint8Array;
38
- carried: Array<[number, number]>;
39
- steps: number;
40
- }
41
- export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint8Array, preConsumed: ReadonlySet<number>, pre: Precomputed, voiced?: readonly Uint8Array[],
42
- /** The query material the GROUNDING left uncovered — the cost ladder's own
43
- * `unaccounted` spans. Only the reasoner's OWN extensions are judged
44
- * against it; a mechanism carrying its own `used` set owns its shape. */
45
- uncovered?: readonly (readonly [number, number])[]): Promise<ReasonedAnswer>;
19
+ export declare function reason(ctx: MindContext, query: Uint8Array, d0: DerivationState, preConsumed: ReadonlySet<number>, pre: Precomputed, voiced?: readonly Uint8Array[]): Promise<DerivationState>;
46
20
  /** Fuse independent points of attention into one answer (multi-topic).
47
21
  * When the consensus climb finds more than one dominant point, each
48
22
  * independent point grounds its own answer; they are bridged together
49
23
  * by any learnt connector the graph holds between them. */
50
- export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed,
24
+ export declare function fuseAttention(ctx: MindContext, query: Uint8Array, state: DerivationState, pre: Precomputed,
51
25
  /** True when `primary` never touched the consensus climb at all — e.g. a
52
26
  * pure ALU computation, which has no anchor of its own. commitVotes
53
27
  * ALWAYS admits the dominant root regardless of its vote (attention.ts:
@@ -61,4 +35,4 @@ unclimbed?: boolean,
61
35
  * which is the layer that knows how a given grounding records its evidence;
62
36
  * fuseAttention just reads a position from it. Empty or absent preserves
63
37
  * the original behaviour exactly. */
64
- primarySpans?: ReadonlyArray<readonly [number, number]>): Promise<Uint8Array>;
38
+ primarySpans?: ReadonlyArray<Span>): Promise<DerivationState>;
@@ -8,25 +8,30 @@ import { resolve } from "./primitives.js";
8
8
  import { hubBound } from "./traverse.js";
9
9
  import { containsSpan, follow, haloSiblings, project } from "./match.js";
10
10
  import { joinWithBridge, pivotInto } from "./resonance.js";
11
- import { unaccountedBytes } from "./rationale.js";
12
- /** Whether `bytes` is a proper byte-subspan of `query` — already present in
13
- * the question, so voicing it back only restates part of what was asked,
14
- * never answers it. The exact guard recallByResonance already applies to
15
- * its OWN grounding candidates (tier 1's `restates`, tier 2's subspan
16
- * check, tier 0b's argument-binding subspan check) — every mechanism that
17
- * walks a LEARNT CONTINUATION EDGE past an already-vetted grounding
18
- * (reason()'s own hops below, and CAST's `projectCounterfactual` seat
19
- * substitution — see cast.ts) needs the same guard applied to what the
20
- * walk turns up, since `follow()`/`chooseNext`/`pivotInto` know nothing of
21
- * the query at all — only of what structurally continues what. */
22
- export function restatesQuery(query, bytes) {
23
- return bytes.length < query.length && indexOf(query, bytes, 0) >= 0;
24
- }
25
- export async function reason(ctx, query, answer, preConsumed, pre, voiced = [],
26
- /** The query material the GROUNDING left uncovered — the cost ladder's own
27
- * `unaccounted` spans. Only the reasoner's OWN extensions are judged
28
- * against it; a mechanism carrying its own `used` set owns its shape. */
29
- uncovered = []) {
11
+ import { admissible, advance } from "./derivation.js";
12
+ import { closure, restates, unaccountedBytes, } from "./derivation.js";
13
+ import { STEP } from "./graph-search.js";
14
+ /** Extend a grounded answer forward across facts (multi-hop reasoning).
15
+ * Pivots on the longest unconsumed learnt context each answer contains,
16
+ * then follows the pivot's continuation to the next fact. **The chain ends
17
+ * when it STOPS, never when a count runs out**: every exit is a refusal (no
18
+ * pivot, no forward step, no question material carried) and the walk is bounded
19
+ * by the material and the graph — `consumed` refuses to revisit a node. There
20
+ * is no hop allowance, so this doc deliberately names no `cfg` capacity: the
21
+ * cover prices every hop at `STEP` and lets the search decide the depth, and a
22
+ * second count here would be a second decision about the same thing.
23
+ * `preConsumed` carries node ids already
24
+ * spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
25
+ * the BYTES of the anchors a mechanism declared it voiced (its `used` set),
26
+ * when it declared one — see the pivot's own containment rule. `pre` is the
27
+ * response's shared pre-computation — the post-grounding stages read the
28
+ * same container the mechanisms did. */
29
+ export async function reason(ctx, query, d0, preConsumed, pre, voiced = []) {
30
+ const answer = d0.product;
31
+ /** The query material the GROUNDING left uncovered — the state's own
32
+ * remainder. Only the reasoner's OWN extensions are judged against it; a
33
+ * mechanism carrying its own `used` set owns its shape. */
34
+ const uncovered = d0.remainder;
30
35
  // Echo guard: a query that is ITSELF a learnt continuation (some context's
31
36
  // answer) is being asked back at the system — hopping forward from it would
32
37
  // chain through the very fact that produced it and echo the conversation
@@ -34,7 +39,7 @@ uncovered = []) {
34
39
  // broad structural gate; pinned by test/31-audit.
35
40
  const qId = pre.queryResolved;
36
41
  if (qId !== null && ctx.store.prevCount(qId) > 0) {
37
- return { bytes: answer, carried: [], steps: 0 };
42
+ return d0;
38
43
  }
39
44
  // Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
40
45
  // the hub bound, via the store's LIMITed edge reads: a common continuation's
@@ -52,7 +57,7 @@ uncovered = []) {
52
57
  // is nothing left to chain for.
53
58
  //
54
59
  // Every stopping condition in the loop below judges the ANSWER (`consumed` /
55
- // `restatesQuery` / `bytesEqual`); none asks whether the QUESTION was
60
+ // the law's `restates` / `bytesEqual`); none asks whether the QUESTION was
56
61
  // satisfied. So a single-hop question whose answer happens to name another
57
62
  // learnt context extends past a correct answer and REPLACES it:
58
63
  //
@@ -92,7 +97,7 @@ uncovered = []) {
92
97
  ? null
93
98
  : ctx.store.prevFirst(groundedId, bound);
94
99
  if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
95
- return { bytes: answer, carried: [], steps: 0 };
100
+ return d0;
96
101
  }
97
102
  const consumed = new Set();
98
103
  /** `prev` lets a caller hand in an already-read reverse-edge list — hop 0
@@ -136,164 +141,158 @@ uncovered = []) {
136
141
  else {
137
142
  await preconsume();
138
143
  }
139
- let cur = answer;
140
- const qv = pre.guide; // the response-wide guide IS the query's gist
141
- let t;
142
- const startedFrom = answer;
143
- // INSTRUMENTATION ONLY — the two facts the extension's own decision already
144
- // used and threw away: the spans of uncovered material each step was
145
- // JUSTIFIED by (the gate below computes which span carries it and kept only a
146
- // boolean), and how many steps were taken. Nothing here decides anything:
147
- // both are read after the loop, to bump counters and to let the caller compare
148
- // the extension's cost against what it explains, in the ladder's own currency.
149
- const carried = [];
150
- let steps = 0;
144
+ // ── THE WALK, AS THE LAW'S ───────────────────────────────────────────
145
+ //
146
+ // This function no longer decides whether a step may be taken: it OFFERS a
147
+ // continuation — a forward step from the answer's own learnt edge, or a pivot
148
+ // on a learnt context the answer contains — and `derivation.ts`'s law admits
149
+ // or refuses it. The state it offers against is the pipeline's own: the
150
+ // grounding's remainder (what the question still owes), its accounting, its
151
+ // cost.
152
+ //
151
153
  // NO ALLOWANCE: THE CHAIN ENDS WHEN IT STOPS. Every exit below is the law —
152
154
  // no pivot, no forward step, no question material carried — and the walk is
153
- // bounded by the material and the graph rather than by a count: each taken
154
- // step must carry a W-window of the uncovered material (finite), and
155
- // `consumed` refuses to revisit a node. `recallQueryK` no longer bounds the
156
- // reasoner here; it keeps its other roles (the bridge's candidate reads, the
157
- // pivot's probe budget, the resonance limits).
155
+ // bounded by the material and the graph rather than by a count: the law
156
+ // requires each step to engage what is left (or to move to new structure),
157
+ // and `consumed` refuses to revisit a node. `recallQueryK` no longer bounds
158
+ // the reasoner here; it keeps its other roles (the bridge's candidate reads,
159
+ // the pivot's probe budget, the resonance limits).
158
160
  //
159
- // Measured before removing it: test/89 — the corpus-cost guard, the heaviest
160
- // case in the suite — is green and no slower without the allowance (27 s
161
- // against 30 s); and raising it from 12 to 200 changed neither the answer nor
161
+ // Measured before removing the allowance: test/89 — the corpus-cost guard, the
162
+ // heaviest case in the suite — is green and no slower without it (27 s against
163
+ // 30 s); and raising it from 12 to 200 changed neither the answer nor
162
164
  // `pivotSteps` on the chain fixtures.
163
- for (let hop = 0;; hop++) {
164
- // Hop 0's `cur` IS `answer`, so the guard above already resolved it and
165
- // read its reverse edges — reuse both rather than repeat them.
165
+ const qv = pre.guide; // the response-wide guide IS the query's gist
166
+ const W = ctx.space.maxGroup;
167
+ // WHOSE EXTENSION IS THIS? `voiced` is what the mechanism WITHHELD (the
168
+ // pipeline sends the used anchors' CONTINUATIONS, not their bytes), so a
169
+ // non-empty `voiced` means exactly what that note says: the grounding came
170
+ // from a mechanism that carries its own short `used` set (cast/join) and
171
+ // therefore OWNS the shape of its answer. The further terms inside such a
172
+ // seat are legitimately followable (test/29 C3's `Mona Lisa` lives inside the
173
+ // voiced seat and leads on to a fact about neither analog), which the law
174
+ // expresses as the identity species of progress — `moves` — declared by the
175
+ // reporter and never inferred from the mechanism's name.
176
+ const producerOwnsShape = voiced.length > 0;
177
+ const startedFrom = answer;
178
+ let hop = 0;
179
+ let t;
180
+ // WHAT THE OFFERED STEP WOULD BE. The law decides, so the accepted step is
181
+ // traced where the decision went (`onTaken`) and a refused one is reported as
182
+ // the law's refusal (`onRefused`) — a step is never reported before it is
183
+ // admitted.
184
+ let pending = null;
185
+ const offer = async (d) => {
186
+ const cur = d.product;
187
+ // The first step's `cur` IS the grounding's product, so the guard above
188
+ // already resolved it and read its reverse edges — reuse both.
166
189
  const curId = hop === 0 ? groundedId : resolve(ctx, cur);
167
190
  consumeNode(curId, hop === 0 ? groundedPrev ?? undefined : undefined);
191
+ hop++;
168
192
  // 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.
193
+ // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows nothing
194
+ // of `consumed` and may still walk to a consumed fixpoint — absorbing it
195
+ // would repeat content the grounding stage already spoke for, so a consumed
196
+ // fixpoint falls through to the pivot step instead. Offered with `moves`:
197
+ // completing the answer's OWN learnt form is an identity step, not a claim
198
+ // about the asker's material.
173
199
  if (curId !== null &&
174
200
  ctx.store.nextFirst(curId, bound).some((n) => !consumed.has(n))) {
175
201
  const fwd = await follow(ctx, curId, qv);
176
202
  const fwdId = fwd !== null ? resolve(ctx, fwd) : null;
177
203
  if (fwd !== null && !bytesEqual(fwd, cur) &&
178
204
  (fwdId === null || !consumed.has(fwdId)) &&
179
- !restatesQuery(query, fwd)) {
205
+ !restates(query, fwd, 0, { proper: true })) {
180
206
  consumeAll(curId);
181
- t ??= ctx.trace?.enter("reason", [
182
- rItem(startedFrom, "grounded"),
183
- ]);
184
- ctx.trace?.step("absorbForward", [rItem(cur, "answer", curId)], [rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)], "the answer is itself a learnt fact — follow its continuation to the fixpoint");
185
- cur = fwd;
186
- steps++;
187
- continue;
207
+ pending = { kind: "absorb", cur, curId, fwd };
208
+ return { product: fwd, contains: true, reaches: true, cost: STEP };
188
209
  }
189
210
  }
190
- // Pivot: find the longest unconsumed learnt context the answer contains.
211
+ // Pivot: the longest unconsumed learnt context the answer contains.
191
212
  consumeAll(curId);
192
213
  const pivot = await pivotInto(ctx, cur, consumed, voiced);
193
214
  if (pivot === null)
194
- break;
215
+ return null;
195
216
  const fc = await follow(ctx, pivot, qv);
196
217
  consumeAll(pivot);
197
- if (fc === null || bytesEqual(fc, cur) || restatesQuery(query, fc))
198
- break;
199
- // WHOSE EXTENSION IS THIS?
200
- //
201
- // `voiced` is what the mechanism WITHHELD (the pipeline sends the used
202
- // anchors' CONTINUATIONS, not their bytes — see pipeline's own note), so a
203
- // non-empty `voiced` means exactly what that note says: the grounding came
204
- // from a mechanism that carries its own short `used` set (cast/join) and
205
- // therefore owns the shape of its answer. The further terms inside such a
206
- // seat are legitimately followable — test/29 C3's `Mona Lisa` lives inside
207
- // the voiced seat and leads on to a fact about neither analog.
208
- //
209
- // Every other grounding is ordinary, and an extension of it is the
210
- // reasoner's own inference: it is taken only while question material the
211
- // grounding left uncovered remains AND the step carries some of it, judged
212
- // by the mind's own line between chance and evidence — one W-byte window,
213
- // no word notion, no character class, no threshold. Measured: the drift's
214
- // second step (`the Eiffel Tower is in Paris` after `Paris is famous for
215
- // the Eiffel Tower`) carries no window of `" famous for"` and is refused,
216
- // while the first carries it. Terminates by a real argument: the uncovered
217
- // material is finite and each taken extension must carry some of it.
218
- const producerOwnsShape = voiced.length > 0;
219
- if (!producerOwnsShape && uncovered.length > 0) {
220
- const W = ctx.space.maxGroup;
221
- let progress = false;
222
- let justified;
223
- for (const [a, b] of uncovered) {
224
- for (let i = a; i + W <= b && !progress; i++) {
225
- if (indexOf(fc, query.subarray(i, i + W), 0) >= 0) {
226
- progress = true;
227
- justified = [a, b];
228
- }
229
- }
230
- if (progress)
231
- break;
232
- }
233
- if (progress && justified !== undefined)
234
- carried.push(justified);
235
- if (!progress) {
236
- // THE BRAKE, MADE VISIBLE. The reasoner declines a step that carries
237
- // none of the material the grounding left uncovered — the drift the
238
- // extension tests pin. A refusal that leaves no trace is the kind of
239
- // silent cut AGENTS §6 forbids: the rationale is where a reader learns
240
- // that an extension was declined for want of question material, and
241
- // where the next person sees why the chain stopped here. Measured with
242
- // the check disabled, test/110 and test/116 fail — so this brake is the
243
- // only thing keeping the extension honest until the pivot reports its
244
- // own accounted spans and the ladder can judge it instead.
245
- //
246
- // THE PROMISE IS NOW KEPT, AND THE BRAKE TURNS OUT TO BE THE LADDER'S
247
- // OWN CONSEQUENCE. The extension reports what it carried (`carried`,
248
- // the span each step was justified by) and what it cost (`steps`), both
249
- // counted in the meter (`reasonCarriedBytes`, `reasonSteps`), so the
250
- // ladder CAN judge it: it accepts while
251
- //
252
- // steps · STEP < PASS · carried
253
- //
254
- // and this brake accepts whenever the step carries a `W`-window, i.e.
255
- // whenever `carried ≥ W ≥ 1`. With `PASS/STEP = 1000` the two therefore
256
- // agree on every extension with `steps ≤ 1000 · carried` — and every
257
- // extension this repository produces takes 0 or 1 steps (measured on
258
- // chains of 3, 8, 20 and 40 links). Above that bound the ladder would
259
- // refuse what this brake accepts, which is the corner named in the
260
- // closure report's limits: a chain of thousands of links explaining a
261
- // handful of bytes. No guard is added for it — a limit without a
262
- // derivation is exactly what the brake must not become.
263
- const left = unaccountedBytes(uncovered);
264
- ctx.trace?.step("pivotRefused", [rItem(cur, "answer"), rItem(query, "query")], uncovered.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")), `the step carries none of the question material the grounding left ` +
265
- `uncovered (${left} byte(s) in ${uncovered.length} span(s)) — refused`);
266
- break;
267
- }
218
+ if (fc === null || bytesEqual(fc, cur) ||
219
+ restates(query, fc, 0, { proper: true })) {
220
+ return null;
221
+ }
222
+ pending = { kind: "pivot", cur, pivot, fc };
223
+ // Offered with the identity species ONLY when the grounding declared what it
224
+ // speaks for; otherwise the law requires this step to carry question
225
+ // material the grounding left unaccounted, which is the drift the extension
226
+ // tests pin — refused by the law's own measure, not by a private window
227
+ // test.
228
+ return {
229
+ product: fc,
230
+ contains: true,
231
+ reaches: producerOwnsShape,
232
+ cost: STEP,
233
+ };
234
+ };
235
+ const closed_ = await closure(d0, query, W, offer, (before, after) => {
236
+ if (ctx.meter) {
237
+ ctx.meter.closureDrainedBytes += unaccountedBytes(before.remainder) -
238
+ unaccountedBytes(after.remainder);
268
239
  }
269
- if (ctx.meter)
270
- ctx.meter.pivotSteps++;
240
+ const p = pending;
241
+ pending = null;
242
+ if (p === null)
243
+ return;
271
244
  t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
272
- ctx.trace?.step("pivotStep", [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")], [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)], "pivot on the shared span this answer contains, then step forward across that fact");
273
- cur = fc;
274
- steps++;
275
- }
245
+ if (p.kind === "absorb") {
246
+ ctx.trace?.step("absorbForward", [rItem(p.cur, "answer", p.curId ?? undefined)], [rItem(p.fwd, "answer", resolve(ctx, p.fwd) ?? undefined)], "the answer is itself a learnt fact — follow its continuation to the fixpoint");
247
+ }
248
+ else {
249
+ if (ctx.meter)
250
+ ctx.meter.pivotSteps++;
251
+ ctx.trace?.step("pivotStep", [rItem(p.cur, "answer"), rNode(ctx, p.pivot, "pivot")], [rItem(p.fc, "answer", resolve(ctx, p.fc) ?? undefined)], "pivot on the shared span this answer contains, then step forward across that fact");
252
+ }
253
+ }, (at) => {
254
+ const p = pending;
255
+ pending = null;
256
+ if (p === null || p.kind !== "pivot")
257
+ return;
258
+ // THE BRAKE, MADE VISIBLE — and it is now the LAW's refusal, reported
259
+ // where it happened. The reasoner declines a step that carries none of
260
+ // the material the grounding left unaccounted. A refusal that leaves no
261
+ // trace is the kind of silent cut AGENTS §6 forbids: the rationale is
262
+ // where a reader learns that an extension was declined for want of
263
+ // question material, and where the next person sees why the chain stopped
264
+ // here. Measured with the check disabled, test/110 and test/116 fail.
265
+ const left = unaccountedBytes(at.remainder);
266
+ ctx.trace?.step("pivotRefused", [rItem(p.cur, "answer"), rItem(query, "query")], at.remainder.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")), `the step carries none of the question material the grounding left ` +
267
+ `unaccounted (${left} byte(s) in ${at.remainder.length} span(s)) — refused`);
268
+ });
276
269
  // INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
277
270
  // contract 1: a counter never reaches a decision). They are what a caller
278
- // needs to PRICE the extension instead of taking it unconditionally: the work
279
- // it did (`steps · STEP`) and the uncovered material it carried.
271
+ // needs to PRICE the extension instead of taking it unconditionally, and both
272
+ // are now read off the state the law advanced rather than accumulated beside
273
+ // it: the work it did is the cost it accumulated, and the material it carried
274
+ // is the accounting the law's witnesses added.
275
+ const steps = closed_.cost - d0.cost;
280
276
  if (ctx.meter) {
281
277
  ctx.meter.reasonSteps += steps;
282
- ctx.meter.reasonCarriedBytes += unaccountedBytes(carried);
278
+ // The accounting the law's witnesses added, summed by the law's own function
279
+ // over the tail of the state's list — and computed ONLY when a meter is
280
+ // attached: this used to slice and MAP a fresh array on every response, for a
281
+ // counter that usually does not exist. One allocation, under the meter.
282
+ ctx.meter.reasonCarriedBytes += unaccountedBytes(closed_.accounted.slice(d0.accounted.length));
283
283
  }
284
- t?.done([rItem(cur, "answer", resolve(ctx, cur) ?? undefined)],
285
- // A FIXPOINT: no further step was possible. This note used to also cover an
286
- // exhausted hop allowance — a different fact, and the reason F1 added a
287
- // counter for it. The allowance is gone, so the only way out of the loop is
288
- // a refusal, and the note is true again by construction.
284
+ t?.done([rItem(closed_.product, "answer", resolve(ctx, closed_.product) ?? undefined)],
285
+ // A FIXPOINT: no further step was offered, or the law refused the one that
286
+ // was. There is no allowance to exhaust, so the note is true by
287
+ // construction.
289
288
  "the multi-hop chain's fixpoint");
290
- return { bytes: cur, carried, steps };
289
+ return closed_;
291
290
  }
292
291
  /** Fuse independent points of attention into one answer (multi-topic).
293
292
  * When the consensus climb finds more than one dominant point, each
294
293
  * independent point grounds its own answer; they are bridged together
295
294
  * by any learnt connector the graph holds between them. */
296
- export async function fuseAttention(ctx, query, primary, pre,
295
+ export async function fuseAttention(ctx, query, state, pre,
297
296
  /** True when `primary` never touched the consensus climb at all — e.g. a
298
297
  * pure ALU computation, which has no anchor of its own. commitVotes
299
298
  * ALWAYS admits the dominant root regardless of its vote (attention.ts:
@@ -308,6 +307,11 @@ unclimbed = false,
308
307
  * fuseAttention just reads a position from it. Empty or absent preserves
309
308
  * the original behaviour exactly. */
310
309
  primarySpans = []) {
310
+ // THE STATE, NOT BARE BYTES: fusion is one more transition of the derivation
311
+ // the walk returned, so it reads that state's product and hands back a state.
312
+ // Its own structural gates stay here — this is the layer that can resolve
313
+ // those witnesses, and the law never re-derives one.
314
+ const primary = state.product;
311
315
  // When the answer is structurally drawn from the query itself
312
316
  // (extraction), it already spans all the query's pieces — fusion
313
317
  // would only add noise from unrelated stored contexts. The gate is
@@ -316,7 +320,7 @@ primarySpans = []) {
316
320
  // short answers over long queries, silently starving multi-topic queries
317
321
  // of fusion.
318
322
  if (containsSpan(ctx, query, primary))
319
- return primary;
323
+ return state;
320
324
  // The committed points of attention ARE the shared climb's roots (same
321
325
  // query, same k, same DF mode) — read them from Precomputed instead of
322
326
  // re-climbing, so even a traced response pays for the climb once.
@@ -357,7 +361,7 @@ primarySpans = []) {
357
361
  const lonePromotes = unclimbed && forest.length === 1 &&
358
362
  forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
359
363
  if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
360
- return primary;
364
+ return state;
361
365
  }
362
366
  // WHERE THE QUERY ASKED FOR IT. The sort below orders the fused pieces by
363
367
  // query position, which is the whole point of the `start` field: a
@@ -433,7 +437,13 @@ primarySpans = []) {
433
437
  // it fires on exact content-addressed recurrence, not on how strongly
434
438
  // the root resonates.
435
439
  const cont = await follow(ctx, root.anchor, qv);
436
- if (cont !== null && cont.length > 0 && indexOf(query, cont, root.end) >= 0) {
440
+ if (cont !== null && cont.length > 0 &&
441
+ // The law's positional reading: the caller knows the material it is
442
+ // looking for lies AT OR AFTER this root, which is the witness; the law
443
+ // owns the containment test itself. The `cont.length > 0` guard stays
444
+ // here because an EMPTY continuation indexes at every offset — the law
445
+ // has no opinion about whether "nothing" counts as said.
446
+ restates(query, cont, 0, { from: root.end })) {
437
447
  ctx.trace?.step("alreadyAnswered", [rNode(ctx, root.anchor, "point", root.vote)], [rItem(cont, "continuation")], "this point's own learnt continuation already appears later in the query — already answered, not fused");
438
448
  continue;
439
449
  }
@@ -446,7 +456,7 @@ primarySpans = []) {
446
456
  }
447
457
  if (pieces.length === 1) {
448
458
  t?.done([rItem(primary, "answer")], "no further independent point grounded");
449
- return primary;
459
+ return state;
450
460
  }
451
461
  pieces.sort((a, b) => a.start - b.start);
452
462
  let out = pieces[0].bytes;
@@ -460,5 +470,27 @@ primarySpans = []) {
460
470
  // back `primary` untouched. Untraced on purpose (meter.ts contract 1).
461
471
  if (ctx.meter)
462
472
  ctx.meter.fuseRuns++;
463
- return out;
473
+ // A FUSION IS A TRANSITION, and the only one in the engine that can splice the
474
+ // QUESTION'S OWN material into the product. So it is OFFERED to the law rather
475
+ // than built by hand: the law reads the window the fused product holds — the
476
+ // same reading the carries admission uses, one definition — accounts for the
477
+ // span it carried, and lets the question's remainder drain on EVIDENCE. A
478
+ // fusion that carries nothing consumes nothing, because the reading finds no
479
+ // window; and `reaches` is declared only when the fusion composed another root,
480
+ // which is structure this derivation had not stood on.
481
+ const fusedT = {
482
+ product: out,
483
+ contains: true,
484
+ reaches: rest.length > 0,
485
+ cost: STEP,
486
+ };
487
+ const fusedWitness = admissible(state, fusedT, query, ctx.space.maxGroup);
488
+ if (fusedWitness === null)
489
+ return { ...state, product: out };
490
+ const fused = advance(state, fusedT, fusedWitness);
491
+ if (ctx.meter) {
492
+ ctx.meter.closureDrainedBytes += unaccountedBytes(state.remainder) -
493
+ unaccountedBytes(fused.remainder);
494
+ }
495
+ return fused;
464
496
  }
@@ -2,7 +2,8 @@
2
2
  //
3
3
  // GraphSearchHost is defined first (minimal imports) so GraphSearch can import
4
4
  // it without pulling in the full MindContext.
5
- import { bytesEqual, concatBytes, indexOf } from "../bytes.js";
5
+ import { bytesEqual, concatBytes } from "../bytes.js";
6
+ import { restates } from "./derivation.js";
6
7
  import { dominates } from "../geometry.js";
7
8
  // ═══════════════════════════════════════════════════════════════════════════
8
9
  // FREE FUNCTIONS (pure, no state)
@@ -32,12 +33,14 @@ export function spliceAll(segs) {
32
33
  export function segRestatesQuery(s, query, queryLen, W) {
33
34
  if (!s.rec)
34
35
  return false;
36
+ // THE LITERAL EXEMPTION IS THE CALLER'S. A span that IS the site's own bytes
37
+ // at its own position is naming what is already there, not substituting for
38
+ // it — and only this caller knows that, so it says so by not asking.
35
39
  const literal = s.j - s.i === s.bytes.length &&
36
40
  bytesEqual(s.bytes, query.subarray(s.i, s.j));
37
41
  if (literal)
38
42
  return false;
39
- return s.bytes.length >= W && s.bytes.length < queryLen &&
40
- indexOf(query, s.bytes, 0) >= 0;
43
+ return restates(query, s.bytes, W, { proper: true });
41
44
  }
42
45
  /** Lift the answer out of the cover for think: the recognised region, free of
43
46
  * the asker's surrounding (unrecognised) framing — and free of any chosen
package/docs/INDEX.md CHANGED
@@ -8,10 +8,10 @@ proof in `test/` (pins that fail when the law is broken).
8
8
 
9
9
  | Task | Read | Why |
10
10
  | --------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
11
- | Add a mechanism | `docs/architecture/mechanism-market.md` + `docs/mechanisms/*.md` | Market contract: decoupled, declared competence, visible budget, evidence travels |
12
- | Add a threshold | `docs/architecture/thresholds.md` | All cutoffs are formulas over D/W/N in `geometry.ts`; `config.ts` holds only budgets |
13
- | Debug an answer | `docs/architecture/cost-model.md` + `src/meter.ts` | One cost ladder (`MICRO`/`STEP`/`CONCEPT`/`PASS`) decides every grounding choice |
14
- | Understand the fold | `docs/architecture/fold-contract.md` | Deposit and inference must compute the same tree; boundaries are not turn metadata |
11
+ | Add a mechanism | `docs/architecture/mechanism-market.md` + `docs/mechanisms/*.md` | Market contract: the four constraints |
12
+ | Add a threshold | `docs/architecture/thresholds.md` | All cutoffs are formulas over D/W/N; `config.ts` holds budgets only |
13
+ | Debug an answer | `docs/architecture/cost-model.md` + `src/meter.ts` | One ladder decides every grounding choice |
14
+ | Understand the fold | `docs/architecture/fold-contract.md` | Deposit and inference compute the same tree |
15
15
  | Add a store backend | `docs/architecture/store.md` + `docs/architecture/bounded-reads.md` | `AbstractStore` owns domain logic; backends are thin wrappers with capped reads |
16
16
  | Add an ALU operation | `src/alu/README.md` | One `registry.derive` per op composing existing ops; no new `derive` needed |
17
17
  | Add a matcher or projection | `docs/architecture/match-project.md` | Mechanisms are `(matcher, direction, gate)` configs over the shared `match.ts` family |
@@ -19,23 +19,24 @@ proof in `test/` (pins that fail when the law is broken).
19
19
  | Change vector search | `docs/architecture/exact-vs-approximate.md` + `docs/architecture/bounded-reads.md` | Scores propose, bytes dispose; ANN is bounded by `hubBound` |
20
20
  | Profile or bound work | `docs/architecture/meter.md` + `docs/architecture/bounded-reads.md` | `meter.ts` is write-only; counters are product, phases are hints |
21
21
 
22
- ## Architecture laws (13)
22
+ ## Architecture laws (14)
23
23
 
24
- | Law | File | Summary | Pins |
25
- | --- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------- |
26
- | 1 | `docs/architecture/determinism.md` | No `Math.random`/`Date.now` in behaviour; seed-derived randomness; corpus-determined tie-breaks | `test/20` |
27
- | 2 | `docs/architecture/thresholds.md` | Every decision cutoff derived in `geometry.ts` over D/W/N; no tunable knobs | `test/40`, `test/64` |
28
- | 3 | `docs/architecture/exact-vs-approximate.md` | Vector scores rank only; identity via content-addressed lookup; five graded ladders | `test/51` |
29
- | 4 | `docs/architecture/cost-model.md` | Single ladder `MICRO`/`STEP`/`CONCEPT`/`PASS`; weight `moves + PASS·unaccounted`; `STEP`-grade compare | `test/04`, `test/55` |
30
- | 5 | `docs/architecture/match-project.md` | Shared `match.ts` family (`locate`/`alignGraded`/`frameSlots`/`project`); voicing gates belong to consumers | `test/24`, `test/76` |
31
- | 6 | `docs/architecture/mechanism-market.md` | `PipelineMechanism` (`floor`/`run`/`parse`); admissible-floor pruning and investment discipline | `test/01`, `test/04` |
32
- | 7 | `docs/architecture/commonality.md` | Two populations: corpus-global (`reachOf`+`dominates`) vs weave-local (`depth[]`) | `test/17`, `test/34` |
33
- | 8 | `docs/architecture/bounded-reads.md` | No per-query read grows with N; `hubBound=√N` enforced at store via LIMIT/probe/prefix caps | `test/77`, `test/90` |
34
- | 9 | `docs/architecture/store.md` | `AbstractStore` owns dedup/indexing/batch; `store-sqlite.ts` is thin wrappers; canon index optional | `test/08` |
35
- | 10 | `docs/architecture/fold-contract.md` | `perceiveDeposit` and `perceive` agree; `contentLevels` is single boundary rule; no W/offset dependence | `test/59`, `test/63` |
36
- | 11 | `docs/architecture/memoization.md` | `Precomputed` is per-response lazy cache (promise-cached async); `beginResponse`/`endResponse` lifecycle | `test/42` |
37
- | 12 | `docs/architecture/saturation.md` | Every walk names a deciding saturation beside its cap; cap is safety net, not decision | `test/27`, `test/16` |
38
- | 13 | `docs/architecture/meter.md` | `meter.ts` is write-only work accounting; counts are deterministic, phases nest | `test/55` |
24
+ | Law | File | Summary | Pins |
25
+ | --- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------- |
26
+ | 1 | `docs/architecture/determinism.md` | No `Math.random`/`Date.now` in behaviour; seed-derived randomness; corpus-determined tie-breaks | `test/20` |
27
+ | 2 | `docs/architecture/thresholds.md` | Every decision cutoff derived in `geometry.ts` over D/W/N; no tunable knobs | `test/40`, `test/64` |
28
+ | 3 | `docs/architecture/exact-vs-approximate.md` | Vector scores rank only; identity via content-addressed lookup; five graded ladders | `test/51` |
29
+ | 4 | `docs/architecture/cost-model.md` | Single ladder `MICRO`/`STEP`/`CONCEPT`/`PASS`; weight `moves + PASS·unaccounted`; `STEP`-grade compare | `test/04`, `test/55` |
30
+ | 5 | `docs/architecture/match-project.md` | Shared `match.ts` family (`locate`/`alignGraded`/`frameSlots`/`project`); voicing gates belong to consumers | `test/24`, `test/76` |
31
+ | 6 | `docs/architecture/mechanism-market.md` | `PipelineMechanism` (`floor`/`run`/`parse`); admissible-floor pruning and investment discipline | `test/01`, `test/04` |
32
+ | 7 | `docs/architecture/commonality.md` | Three: global (`reachOf`+`dominates`), weave-local (`depth[]`), window rarity | `test/17`, `test/34` |
33
+ | 8 | `docs/architecture/bounded-reads.md` | No per-query read grows with N; `hubBound=√N` enforced at store via LIMIT/probe/prefix caps | `test/77`, `test/90` |
34
+ | 9 | `docs/architecture/store.md` | `AbstractStore` owns dedup/indexing/batch; `store-sqlite.ts` is thin wrappers; canon index optional | `test/08` |
35
+ | 10 | `docs/architecture/fold-contract.md` | `perceiveDeposit` and `perceive` agree; `contentLevels` is single boundary rule; no W/offset dependence | `test/59`, `test/63` |
36
+ | 11 | `docs/architecture/memoization.md` | `Precomputed` is per-response lazy cache (promise-cached async); `beginResponse`/`endResponse` lifecycle | `test/42` |
37
+ | 12 | `docs/architecture/saturation.md` | Every walk names a deciding saturation beside its cap; cap is safety net, not decision | `test/27`, `test/16` |
38
+ | 13 | `docs/architecture/meter.md` | `meter.ts` is write-only work accounting; counts are exact, phases nest | `test/55` |
39
+ | 14 | `docs/architecture/closure.md` | A derivation is closed when its structure accounts for the question's remainder; every transition asks that law | `test/133`–`140` |
39
40
 
40
41
  ## Mechanisms (8)
41
42
 
@@ -63,9 +64,7 @@ proof in `test/` (pins that fail when the law is broken).
63
64
  - `docs/INVARIANTS.md` — the five invariants (determinism, derived thresholds,
64
65
  exact-decides, one cost currency, bounded reads) with file-level routing.
65
66
  - `docs/failures/tempting-but-wrong.md` — refuted simplifications that passed
66
- review but failed pins (e.g. reordering ladders, flattening attention
67
- asymmetries, one-cone-exhausted stop).
67
+ review but failed pins.
68
68
  - `docs/harness/gates.md` — how `AGENTS.md` recipes,
69
69
  `bench/profile-inference.mjs`, and `test/*.test.mjs` enforce the laws.
70
- - `docs/architecture/` — full per-law derivation (each file states why the law
71
- is this way; no separate HOW).
70
+ - `docs/architecture/` — per-law derivation: each file says why, not how.