@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
@@ -42,7 +42,7 @@ import { read } from "../primitives.js";
42
42
  import { corpusN, reachOf } from "../traverse.js";
43
43
  import { dominates } from "../../geometry.js";
44
44
  import { STEP } from "../graph-search.js";
45
- import { unexplainedLabel } from "../rationale.js";
45
+ import { insideAnsweredTurn } from "../derivation.js";
46
46
  import { rItem, rNode } from "../trace.js";
47
47
  /** The main confluence entry point. Given a query, detect whether it weaves
48
48
  * two or more INDEPENDENT constraints (ranked anchors supported by disjoint
@@ -75,13 +75,9 @@ export async function confluenceJoin(ctx, query, pre) {
75
75
  // Recognition and attention still see the full transcript; only this
76
76
  // mechanism's constraint population excludes answered spans.
77
77
  const queryWin = new Map();
78
- let answered = 0;
78
+ const answered = { at: 0 };
79
79
  for (const [off, id] of pre.queryWindows) {
80
- while (answered < ctx.answeredSpans.length &&
81
- ctx.answeredSpans[answered][1] <= off)
82
- answered++;
83
- const span = ctx.answeredSpans[answered];
84
- if (span && span[0] <= off && off + W <= span[1])
80
+ if (insideAnsweredTurn(ctx.answeredSpans, answered, off, off + W))
85
81
  continue;
86
82
  queryWin.set(off, id);
87
83
  }
@@ -258,7 +254,6 @@ export async function confluenceJoin(ctx, query, pre) {
258
254
  used: new Set([met.a.anchor, met.b.anchor]),
259
255
  accounted,
260
256
  moves: 3 * STEP,
261
- unexplained: unexplainedLabel(query, accounted),
262
257
  };
263
258
  }
264
259
  // ── Pipeline mechanism ──────────────────────────────────────────────────────
@@ -289,7 +284,6 @@ export const confluenceMechanism = {
289
284
  accounted: met.accounted,
290
285
  moves: met.moves,
291
286
  used: met.used,
292
- unexplained: met.unexplained,
293
287
  }];
294
288
  },
295
289
  };
@@ -13,8 +13,8 @@ import { guidedFirst, hubBound } from "../traverse.js";
13
13
  import { conceptHop } from "../match.js";
14
14
  import { bridge } from "../resonance.js";
15
15
  import { liftAnswer, liftedScaffolding, segRestatesQuery } from "../types.js";
16
- import { decodeText, unexplainedLabel } from "../rationale.js";
17
- import { indexOf } from "../../bytes.js";
16
+ import { decodeText } from "../rationale.js";
17
+ import { insideAnsweredTurn, restates } from "../derivation.js";
18
18
  import { rItem, rNode, traceDerivation } from "../trace.js";
19
19
  // ── Concept / connector pre-resolution ──────────────────────────────────────
20
20
  export async function resolveConcepts(ctx, sites) {
@@ -45,16 +45,13 @@ export async function resolveConnectors(ctx, sites, query) {
45
45
  // discarded — a semantically neutral gate (it removes work whose product
46
46
  // liftAnswer throws away), and a cumulative (multi-turn) query is exactly
47
47
  // where such already-answered continuations recur.
48
- let answered = 0;
48
+ const answered = { at: 0 };
49
49
  const ordered = [...sites]
50
50
  .sort((a, b) => a.start - b.start)
51
51
  .filter((s) => {
52
- while (answered < ctx.answeredSpans.length &&
53
- ctx.answeredSpans[answered][1] <= s.start)
54
- answered++;
55
- const span = ctx.answeredSpans[answered];
56
- if (span && span[0] <= s.start && s.end <= span[1])
52
+ if (insideAnsweredTurn(ctx.answeredSpans, answered, s.start, s.end)) {
57
53
  return false;
54
+ }
58
55
  if (query === undefined || ctx.answeredSpans.length === 0)
59
56
  return true;
60
57
  const continuations = ctx.store.nextFirst(s.payload, hubBound(ctx));
@@ -77,7 +74,11 @@ export async function resolveConnectors(ctx, sites, query) {
77
74
  // 3 bytes this reads 4 bytes per candidate instead of the ~231 it
78
75
  // averaged before.
79
76
  const bytes = read(ctx, answer, query.length + 1);
80
- return bytes.length <= query.length && indexOf(query, bytes, 0) >= 0;
77
+ // THE CONTENT READING of the same exclusion: the site's own
78
+ // continuation already occurs in the query, so voicing it back adds
79
+ // nothing. It is the restatement law with no `proper` flag — the
80
+ // containment reading that also admits the whole query.
81
+ return restates(query, bytes, 0);
81
82
  });
82
83
  });
83
84
  const bridgePair = async (l, r) => {
@@ -171,18 +172,11 @@ export const coverMechanism = {
171
172
  ? await ctx.meter.time("cover.resolveConnectors", () => resolveConnectors(ctx, sites, query))
172
173
  : await resolveConnectors(ctx, sites, query);
173
174
  let splits = rec.splits;
174
- let starts = rec.starts;
175
175
  if (computed.length > 0) {
176
176
  splits = new Set(rec.splits);
177
- starts = new Set(rec.starts);
178
177
  for (const u of computed) {
179
178
  splits.add(u.i);
180
179
  splits.add(u.j);
181
- // A computation's own boundaries carry the same fold-level evidence
182
- // a chunk boundary does — "computation always wins" (see the header
183
- // comment) extends to being trusted ground for cross-leaf recovery.
184
- starts.add(u.i);
185
- starts.add(u.j);
186
180
  }
187
181
  }
188
182
  const concepts = ctx.meter
@@ -208,7 +202,7 @@ export const coverMechanism = {
208
202
  ])),
209
203
  ...computedResults.map((u) => rItem(u.bytes, "computed")),
210
204
  ], coverDeps.length ? coverDeps : undefined);
211
- const solved = ctx.search.cover(query.length, sites, concepts, rec.leaves, splits, starts, undefined, connectors, computedResults, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
205
+ const solved = ctx.search.cover(query.length, sites, concepts, rec.leaves, splits, undefined, connectors, computedResults, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
212
206
  const segs = solved && solved.segs;
213
207
  tCover?.done(segs === null
214
208
  ? []
@@ -247,9 +241,12 @@ export const coverMechanism = {
247
241
  return [{
248
242
  bytes: composed,
249
243
  accounted,
250
- moves: 0,
251
- weight: solved.cost, // A*LD derivation's g-value IS the weight
252
- unexplained: unexplainedLabel(query, accounted),
244
+ // The derivation's DISCRETE work. The bytes the chart could not
245
+ // recognise are NOT priced here: they are exactly the spans `accounted`
246
+ // leaves uncovered, and the pipeline's one formula charges them at PASS —
247
+ // the same formula that prices every other mechanism's candidate. No
248
+ // mechanism spells a cost of its own.
249
+ moves: solved.moves,
253
250
  // How much of the composed answer is the asker's own unexplained words
254
251
  // (the spans the liftAnswer trace above labels "scaffolding"). Cover is
255
252
  // the mechanism that can carry them, because a PASS span still lands in
@@ -19,7 +19,6 @@ import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
19
19
  export declare function extractBySkill(ctx: MindContext, query: Uint8Array, pre: Precomputed): Promise<{
20
20
  bytes: Uint8Array;
21
21
  accounted: Array<[number, number]>;
22
- unexplained: string;
23
22
  } | null>;
24
23
  /** Decompose an answer into substrings of its surrounding context, in order —
25
24
  * the STRONG span-shape reading (see the section note above). Returns null
@@ -8,7 +8,7 @@
8
8
  // that contains it.
9
9
  import { locate } from "../match.js";
10
10
  import { concatBytes, indexOf } from "../../bytes.js";
11
- import { decodeText, unexplainedLabel } from "../rationale.js";
11
+ import { decodeText } from "../rationale.js";
12
12
  import { CONCEPT, STEP } from "../graph-search.js";
13
13
  import { rItem, rNode, traceFail } from "../trace.js";
14
14
  // ── Extraction ────────────────────────────────────────────────────────────
@@ -81,6 +81,12 @@ export async function extractBySkill(ctx, query, pre) {
81
81
  const searched = ranked.slice(0, pre.k);
82
82
  let shapeMisses = 0;
83
83
  let subQuantum = 0;
84
+ // A SECOND refusal counter, because the note below used to call an UNANCHORED
85
+ // read "sub-quantum" — which is false, and it is the kind of instrumentation
86
+ // defect AGENTS §6 says to close where it lives: a reader could not tell from
87
+ // the trace which of the two gates refused. Neither counter reaches a
88
+ // decision (meter.ts contract 1); they exist so the refusal is legible.
89
+ let unanchored = 0;
84
90
  for (const cand of searched) {
85
91
  const exemplar = await pre.spanShapedOf(cand.anchor);
86
92
  if (!exemplar) {
@@ -116,15 +122,16 @@ export async function extractBySkill(ctx, query, pre) {
116
122
  // Here the field is this mechanism's own output and carries its documented
117
123
  // meaning, so the test is sound exactly where the convention does not reach.
118
124
  if (built.accounted.length === 0) {
119
- subQuantum++;
125
+ unanchored++;
120
126
  continue;
121
127
  }
122
- if (shapeMisses > 0 || subQuantum > 0) {
128
+ if (shapeMisses > 0 || subQuantum > 0 || unanchored > 0) {
123
129
  ctx.trace?.step("trySkillAnchors", [
124
- rItem(query.subarray(0, 0), `skipped ${shapeMisses + subQuantum}`),
130
+ rItem(query.subarray(0, 0), `skipped ${shapeMisses + subQuantum + unanchored}`),
125
131
  rNode(ctx, cand.anchor, "chosen"),
126
- ], [], `skipped ${shapeMisses} non-exemplar and ${subQuantum} sub-quantum ` +
127
- `anchor(s) before one yielded a usable extraction`);
132
+ ], [], `skipped ${shapeMisses} non-exemplar, ${subQuantum} sub-quantum and ` +
133
+ `${unanchored} unanchored anchor(s) before one yielded a usable ` +
134
+ `extraction`);
128
135
  }
129
136
  t?.done([rItem(built.bytes, "extracted")], built.pieces === 1
130
137
  ? `apply a learnt extraction skill — read the analogous span of the query` +
@@ -134,7 +141,6 @@ export async function extractBySkill(ctx, query, pre) {
134
141
  return {
135
142
  bytes: built.bytes,
136
143
  accounted: built.accounted,
137
- unexplained: unexplainedLabel(query, built.accounted),
138
144
  };
139
145
  }
140
146
  if (shapeMisses === searched.length) {
@@ -321,7 +327,6 @@ export const extractionMechanism = {
321
327
  bytes: ex.bytes,
322
328
  accounted: ex.accounted,
323
329
  moves: CONCEPT + STEP * ex.accounted.length,
324
- unexplained: ex.unexplained,
325
330
  }];
326
331
  },
327
332
  };
@@ -255,7 +255,6 @@ export const prefixMechanism = {
255
255
  // IDENTITY bridge takes.
256
256
  accounted: [[0, query.length]],
257
257
  moves: STEP,
258
- unexplained: "",
259
258
  // NOT complete: the query is a proper PREFIX, so the form may carry more
260
259
  // past the remainder this voiced.
261
260
  }];
@@ -6,7 +6,6 @@ export interface RecallResult {
6
6
  echoed: boolean;
7
7
  accounted: Array<[number, number]>;
8
8
  moves: number;
9
- unexplained: string;
10
9
  /** See {@link import("../pipeline-mechanism.js").MechanismResult.complete}
11
10
  * — set by the IDENTITY-bridge tier alone. */
12
11
  complete?: boolean;
@@ -6,11 +6,11 @@
6
6
  import { cosine } from "../../vec.js";
7
7
  import { consensusFloor, dominates, identityBar, reachThreshold, significanceBar, } from "../../geometry.js";
8
8
  import { gistOf, read, resolve } from "../primitives.js";
9
- import { bytesEqual, indexOf } from "../../bytes.js";
9
+ import { indexOf } from "../../bytes.js";
10
10
  import { allWindowsAreScaffolding, corpusN, hubBound } from "../traverse.js";
11
11
  import { follow, project, reverseContext, voicesDisplacedFiller, } from "../match.js";
12
12
  import { CONCEPT, STEP } from "../graph-search.js";
13
- import { unexplainedLabel } from "../rationale.js";
13
+ import { restates as lawRestates } from "../derivation.js";
14
14
  import { rItem, rNode } from "../trace.js";
15
15
  import { substitutionBridge } from "../bridge.js";
16
16
  /** Recall the answer by resonating the whole query against the content index. */
@@ -29,7 +29,6 @@ export async function recallByResonance(ctx, query, pre) {
29
29
  echoed,
30
30
  accounted,
31
31
  moves,
32
- unexplained: unexplainedLabel(query, accounted),
33
32
  ...(complete ? { complete } : {}),
34
33
  };
35
34
  };
@@ -103,7 +102,7 @@ export async function recallByResonance(ctx, query, pre) {
103
102
  // guard the argument's OWN later restatement in the same
104
103
  // conversation reads as if it were the next thing to say.
105
104
  if (g !== null && g.length > 0 &&
106
- !(g.length < query.length && indexOf(query, g, 0) >= 0)) {
105
+ !lawRestates(query, g, 0, { proper: true })) {
107
106
  return ground(g, "argument binding — the query's sole edge-source constituent, continuation followed", [[arg.start, arg.end]], STEP);
108
107
  }
109
108
  }
@@ -130,9 +129,10 @@ export async function recallByResonance(ctx, query, pre) {
130
129
  // same principle that keeps cast from voicing stored questions), and
131
130
  // projecting them forward is reverse recall's containment failure in the
132
131
  // other direction — "whatever followed these bytes in some document".
133
- const qKey = ctx.canon ? ctx.canon(query) : query;
134
- const restates = (b) => bytesEqual(b, query) ||
135
- (ctx.canon !== null && bytesEqual(ctx.canon(b), qKey));
132
+ // THE EQUALITY READING of the restatement law: this tier rejects an answer
133
+ // that IS the question (an echo), and a proper fragment is handled by the
134
+ // tier's own subspan tests further down — so the law is asked with `whole`.
135
+ const restates = (b) => lawRestates(query, b, 0, { equate: ctx.canon, whole: true });
136
136
  const idBar = identityBar(ctx.store.D, ctx.space.maxGroup, query.length);
137
137
  if (top.score >= idBar) {
138
138
  for (const h of whole) {
@@ -315,7 +315,7 @@ export async function recallByResonance(ctx, query, pre) {
315
315
  // — never an answer (the same principle as `restates` above, extended
316
316
  // to fragments). Genuine anchor groundings — longer than the query,
317
317
  // or disjoint from it — pass untouched.
318
- else if (g && !(g.length < query.length && indexOf(query, g, 0) >= 0)) {
318
+ else if (g && !lawRestates(query, g, 0, { proper: true })) {
319
319
  return ground(g, "scaffolding-dominated query — ground the consensus-climb anchor", [[forest[0].start, forest[0].end]], CONCEPT);
320
320
  }
321
321
  }
@@ -493,7 +493,6 @@ export const recallMechanism = {
493
493
  bytes: r.bytes,
494
494
  accounted: r.accounted,
495
495
  moves: r.moves,
496
- unexplained: r.unexplained,
497
496
  provenance: r.echoed ? "recall-echo" : "recall",
498
497
  used: new Set(),
499
498
  ...(r.complete ? { complete: true } : {}),
@@ -35,8 +35,8 @@
35
35
  // made AVAILABLE, never imposed.
36
36
  import { carriesFillers, distinct, follow, substituteAll } from "../match.js";
37
37
  import { dominates } from "../../geometry.js";
38
- import { bytesEqual, indexOf } from "../../bytes.js";
39
- import { unexplainedLabel } from "../rationale.js";
38
+ import { bytesEqual } from "../../bytes.js";
39
+ import { restates } from "../derivation.js";
40
40
  import { STEP } from "../graph-search.js";
41
41
  import { rItem, rNode, traceFail } from "../trace.js";
42
42
  /** The minimum number of instances that can establish a frame. One instance
@@ -196,7 +196,7 @@ export async function bindReference(ctx, query, pre) {
196
196
  return fail("the binding produced nothing");
197
197
  // Answering with the question is not answering — the same restated-fragment
198
198
  // guard every recall tier applies.
199
- if (bytes.length < query.length && indexOf(query, bytes, 0) >= 0) {
199
+ if (restates(query, bytes, 0, { proper: true })) {
200
200
  return fail("the binding restates part of the question");
201
201
  }
202
202
  const carried = !bytesEqual(bytes, first);
@@ -230,7 +230,6 @@ export async function bindReference(ctx, query, pre) {
230
230
  // binding claims strictly more than a one-slot binding, so where both are
231
231
  // licensed the smaller claim wins.
232
232
  moves: STEP * slots.length + STEP,
233
- unexplained: unexplainedLabel(query, accounted),
234
233
  // NOT scaffolding. That field counts answer bytes carried through BECAUSE
235
234
  // NOTHING EXPLAINED THEM; a referent is carried because the frame's slot
236
235
  // explains it, and it is accounted above. Reporting it would make every
@@ -153,15 +153,12 @@ export interface MechanismResult {
153
153
  moves: number;
154
154
  /** WHAT THIS ANSWER SPEAKS FOR — the anchors it voices, and therefore the
155
155
  * content the reasoner must not pivot back through. Declared by the
156
- * mechanism about its OWN result, exactly like `accounted`/`unexplained`/
156
+ * mechanism about its OWN result, exactly like `accounted`/`used`/
157
157
  * `complete`: post-grounding honours the property and NEVER ASKS WHICH
158
158
  * MECHANISM SET IT, so the market stays uniform. An EMPTY set is a real
159
159
  * declaration — "this answer voices nothing" (recall) — and withholds
160
160
  * nothing; omit the field and the pipeline re-recognises the answer. */
161
161
  used?: ReadonlySet<number>;
162
- unexplained: string;
163
- /** Explicit weight override. When absent, weight = moves + PASS·unaccounted. */
164
- weight?: number;
165
162
  /** Bytes of `bytes` that came from spans nothing recognised — the asker's
166
163
  * own words carried through verbatim rather than derived (see
167
164
  * {@link liftedScaffolding}). Reported, not priced: the ladder prices what
@@ -12,8 +12,9 @@ import { PASS, STEP } from "./graph-search.js";
12
12
  import { gistOf, read, resolve } from "./primitives.js";
13
13
  import { recognise } from "./recognition.js";
14
14
  import { fuseAttention, reason } from "./reasoning.js";
15
- import { unaccountedBytes, unexplainedSpans } from "./rationale.js";
15
+ import { closed, remainderOf, unaccountedBytes, unexplainedSpans, windowOf, } from "./derivation.js";
16
16
  import { rItem } from "./trace.js";
17
+ import { unexplainedLabel } from "./rationale.js";
17
18
  import { hubBound } from "./traverse.js";
18
19
  import { Precomputed } from "./pipeline-mechanism.js";
19
20
  import { coverMechanism } from "./mechanisms/cover.js";
@@ -242,14 +243,16 @@ export async function think(ctx, query, mechs) {
242
243
  ? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
243
244
  : await mech.run(ctx, query, pre);
244
245
  for (const r of results) {
245
- const weight = r.weight ?? weigh(r.accounted, r.moves);
246
+ // ONE FORMULA, EVERY CANDIDATE: the chart's derivation reports how many
247
+ // discrete moves it made and which bytes it could not recognise; the
248
+ // currency prices both. No mechanism passes a price of its own.
249
+ const weight = weigh(r.accounted, r.moves);
246
250
  consider({
247
251
  bytes: r.bytes,
248
252
  provenance: r.provenance ?? mech.provenance,
249
253
  weight,
250
254
  used: r.used,
251
255
  accounted: r.accounted,
252
- unexplained: r.unexplained,
253
256
  complete: r.complete,
254
257
  scaffolding: r.scaffolding,
255
258
  });
@@ -280,7 +283,17 @@ export async function think(ctx, query, mechs) {
280
283
  const margin = decided !== null && runnerUp !== null
281
284
  ? grade(runnerUp.weight) - grade(decided.weight)
282
285
  : null;
283
- ctx.trace?.step("decideGrounding", candidates.map((c) => rItem(c.bytes, `${c.provenance} (weight ${c.weight.toFixed(3)}${c.unexplained ? `, unexplained: "${c.unexplained}"` : ""})`)), decided ? [rItem(decided.bytes, decided.provenance)] : [], "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder", undefined, {
286
+ ctx.trace?.step("decideGrounding",
287
+ // THE LABEL IS RENDERED WHERE IT IS SHOWN. It was a field on every
288
+ // mechanism's result, and at every one of them it was exactly
289
+ // `unexplainedLabel(query, accounted)` — a second representation of a
290
+ // quantity one pure function already yields, computed on every response
291
+ // whether or not anyone looked. Here it is computed only when a rationale
292
+ // is attached, because `trace?.step` short-circuits its arguments.
293
+ candidates.map((c) => {
294
+ const label = unexplainedLabel(query, c.accounted);
295
+ return rItem(c.bytes, `${c.provenance} (weight ${c.weight.toFixed(3)}${label ? `, unexplained: "${label}"` : ""})`);
296
+ }), decided ? [rItem(decided.bytes, decided.provenance)] : [], "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder", undefined, {
284
297
  version: 1,
285
298
  candidates: candidates.map((c) => ({
286
299
  provenance: c.provenance,
@@ -317,7 +330,54 @@ export async function think(ctx, query, mechs) {
317
330
  const answer = decided.bytes;
318
331
  const provenance = decided.provenance;
319
332
  const declaredUsed = decided.used;
320
- // ── Post-grounding, gated by provenance ──────────────────────────────
333
+ // ── THE DERIVATION STATE ─────────────────────────────────────────────
334
+ //
335
+ // THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
336
+ // the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
337
+ // with the same W floor. `accounted` is a COST quantity (measured: a query
338
+ // fully explained by one computed span plus bridged connectors reports
339
+ // `accounted: []` while nothing is unexplained), and a remainder under one
340
+ // river-fold quantum is bridging punctuation, never a second topic — so it
341
+ // licenses no extension and blocks none.
342
+ //
343
+ // The state is built HERE, where these quantities are already computed, so it
344
+ // costs nothing new: `accounted` is what the winning transition priced,
345
+ // `remainder` is the coverage reading over `accounted ∪ the response's
346
+ // computed spans` (the union is what makes the two different quantities, and
347
+ // both are kept), `cost` is the ladder position, and the two declarations are
348
+ // the producer's own (`fixed`, `used`). What follows reads THIS state rather
349
+ // than a tuple rebuilt at each call site.
350
+ // A DERIVATION IS BORN OWING WHAT ITS ANSWER DOES NOT CARRY. The winning
351
+ // transition PRICED these spans, and pricing is not carrying: coverage claimed
352
+ // without evidence stays owed, and a later transition pays it only by carrying
353
+ // it (the law reads the window; see derivation.ts). Same reading, one
354
+ // definition — not a second spelling of it here.
355
+ const explained = [
356
+ ...decided.accounted,
357
+ ...pre.computed.map((u) => [u.i, u.j]),
358
+ ].filter(([a, b]) => windowOf([a, b], answer, query, ctx.space.maxGroup) !== null);
359
+ // WHAT THE CONSTRUCTION WITHHOLDS, at or above one quantum: the difference between
360
+ // the remainder paid in full and the remainder paid by carrying. Both readings
361
+ // are the law's, so the floor is applied once and in one place.
362
+ const paidInFull = remainderOf(query.length, [
363
+ ...decided.accounted,
364
+ ...pre.computed.map((u) => [u.i, u.j]),
365
+ ], ctx.space.maxGroup);
366
+ const paid = remainderOf(query.length, explained, ctx.space.maxGroup);
367
+ if (ctx.meter) {
368
+ ctx.meter.groundingWithheldBytes += unaccountedBytes(paid) -
369
+ unaccountedBytes(paidInFull);
370
+ }
371
+ const state = {
372
+ product: answer,
373
+ accounted: decided.accounted,
374
+ remainder: paid,
375
+ cost: decided.weight,
376
+ fixed: decided.complete,
377
+ used: decided.used,
378
+ };
379
+ const uncovered = state.remainder;
380
+ // ── Post-grounding, gated by the declaration and the remainder ────────
321
381
  const preConsumed = declaredUsed ??
322
382
  new Set(recognise(ctx, answer).sites.map((s) => s.payload));
323
383
  // A grounding that DECLARED itself complete is not extended: the answer is
@@ -350,10 +410,13 @@ export async function think(ctx, query, mechs) {
350
410
  // suppress every pivot the answer legitimately contains.
351
411
  const voiced = declaredUsed === undefined ? [] : [...declaredUsed].flatMap((id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)));
352
412
  // WHAT THIS BRANCH READ, published where it was read. Post-grounding decides
353
- // by `decided.used` and by the provenance NAME; the operands were invisible in
354
- // the trace, so a change to the branching could not be shown equivalent or
355
- // otherwise from outside — three separate investigations failed on exactly
356
- // that gap. A gap in instrumentation is a defect IN the instrumentation
413
+ // by the DECLARATION (`decided.used`, which becomes `voiced`), by what the
414
+ // recognition already consumed (`preConsumed`) and by the derivation's own
415
+ // remainder — never by the provenance NAME, which is REPORTED throughout and
416
+ // compared nowhere (a stale comment here claimed otherwise; the register caught
417
+ // it, and this is the correction). The operands were invisible in the trace, so
418
+ // a change to the branching could not be shown equivalent or otherwise from
419
+ // outside — three separate investigations failed on exactly that gap. A gap in instrumentation is a defect IN the instrumentation
357
420
  // (AGENTS.md §6): closed here, once, as counts only — never content.
358
421
  ctx.trace?.step("postGrounding", [rItem(answer, provenance)], [], `used=${decided.used !== undefined ? "declared" : "absent"} · ` +
359
422
  `preConsumed=${preConsumed.size} · voiced=${voiced.length}`, undefined, {
@@ -362,6 +425,14 @@ export async function think(ctx, query, mechs) {
362
425
  usedDeclared: decided.used !== undefined,
363
426
  preConsumed: preConsumed.size,
364
427
  voiced: voiced.length,
428
+ // THE STATE THE LAW GOVERNS, rendered where it is decided: what the asker
429
+ // said that no step has accounted for, in spans at or above one quantum,
430
+ // and whether the producer supplied a fixed point. Counts only, like
431
+ // every other operand here — and the spans are the state's, so a reader
432
+ // can check them against the meter's aggregate of the same remainder.
433
+ remainderSpans: state.remainder.length,
434
+ remainderBytes: unaccountedBytes(state.remainder),
435
+ fixed: state.fixed === true,
365
436
  });
366
437
  // REPORTABLE, NOT SILENT. A declared-complete grounding ends the derivation
367
438
  // here, and that decision is part of the derivation's shape: the reader of a
@@ -373,19 +444,6 @@ export async function think(ctx, query, mechs) {
373
444
  ctx.trace?.step("completeGrounding", [rItem(answer, provenance)], [], "grounding declared complete — the query IS the context, so " +
374
445
  "post-grounding extension is skipped");
375
446
  }
376
- // THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
377
- // the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
378
- // with the same W floor. `accounted` is a COST quantity (measured: a query
379
- // fully explained by one computed span plus bridged connectors reports
380
- // `accounted: []` while nothing is unexplained), and a remainder under one
381
- // river-fold quantum is bridging punctuation, never a second topic — so it
382
- // licenses no extension and blocks none.
383
- const explained = [
384
- ...decided.accounted,
385
- ...pre.computed.map((u) => [u.i, u.j]),
386
- ];
387
- const uncovered = unexplainedSpans(query.length, explained)
388
- .filter(([a, b]) => b - a >= ctx.space.maxGroup);
389
447
  // PUBLISHED, NOT RECOMPUTED: the same `uncovered` the gates below read. A
390
448
  // write-only accounting (meter contract 1), so the number that licenses an
391
449
  // extension or a fusion stops being invisible.
@@ -393,14 +451,15 @@ export async function think(ctx, query, mechs) {
393
451
  meter.postGroundingRemainderSpans += uncovered.length;
394
452
  meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
395
453
  }
396
- // The extension is kept as a WHOLE (bytes + what it carried + how many steps),
397
- // not just its bytes: pricing it — `steps · STEP` against `PASS · unaccounted`
398
- // — is the caller's job, one comparison away. `reasoned` stays the bytes so
399
- // everything downstream is untouched.
454
+ // THE WALK CONSUMES AND RETURNS A STATE. It is handed the derivation's own —
455
+ // the grounding's product, accounting, remainder and cost — and hands back the
456
+ // state it advanced to, so what follows reads a state rather than bytes plus a
457
+ // tuple rebuilt here. A supplied fixed point is the one case where the walk
458
+ // does not run at all, and then the state is the grounding's own.
400
459
  const extension = decided.complete ? undefined : meter
401
- ? await meter.time("reason", () => reason(ctx, query, answer, preConsumed, pre, voiced, uncovered))
402
- : await reason(ctx, query, answer, preConsumed, pre, voiced, uncovered);
403
- const reasoned = extension?.bytes ?? answer;
460
+ ? await meter.time("reason", () => reason(ctx, query, state, preConsumed, pre, voiced))
461
+ : await reason(ctx, query, state, preConsumed, pre, voiced);
462
+ const reasoned = extension ?? state;
404
463
  // Fuse only when the query has a genuine REMAINDER no mechanism's
405
464
  // structural evidence touched at all. `decided.accounted` alone
406
465
  // undercounts this: it is a COST-LADDER quantity (cover.ts prices its
@@ -417,7 +476,15 @@ export async function think(ctx, query, mechs) {
417
476
  // observed: a single space between two fully-computed arithmetic spans
418
477
  // ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
419
478
  // corpus fact, corrupting "4 6" into "4 63".
420
- const remainder = unaccounted(explained);
479
+ // THE GATE ASKS THE LAW, and that is an OPTIMISATION, not a tidy-up: the state
480
+ // above ALREADY carries the remainder (`remainderOf`, per-span, with the W
481
+ // floor applied), so asking it costs nothing, while the total this line used to
482
+ // compute (`unaccounted(explained)`) was one more sum over the spans on every
483
+ // response. The two readings are the same condition, not two: the ACCOUNTING
484
+ // applies the same W floor the gate does, so a gap below one quantum never
485
+ // survives into `explained` and the total cannot reach W without some single
486
+ // gap reaching it. Measured over twelve constructions at W = 4 (test/136.3,
487
+ // which pins the equivalence and both sides of it).
421
488
  // Whether the winning candidate's entire recognised substance is
422
489
  // COMPUTED — every accounted span exactly a pre.computed span, nothing
423
490
  // from a genuinely recognised/climbed site. fuseAttention's lone-root
@@ -427,8 +494,8 @@ export async function think(ctx, query, mechs) {
427
494
  // `unclimbed` parameter, gated there by Attention.breadth so a
428
495
  // coincidental echo (which this flag alone cannot distinguish) is still
429
496
  // rejected.
430
- const unclimbed = decided.accounted.length > 0 &&
431
- decided.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
497
+ const unclimbed = state.accounted.length > 0 &&
498
+ state.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
432
499
  // Where the winning grounding stands in the query — fusion places primary
433
500
  // by it (see fuseAttention's `primarySpans`). `accounted` is the
434
501
  // cost-ladder read and is authoritative when non-empty; when it is empty
@@ -436,15 +503,13 @@ export async function think(ctx, query, mechs) {
436
503
  // Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
437
504
  // read here for POSITION instead of for coverage — and resolved here, where
438
505
  // both readings are in hand, rather than inside fuseAttention.
439
- const primarySpans = decided.accounted.length > 0
440
- ? decided.accounted
506
+ const primarySpans = state.accounted.length > 0
507
+ ? state.accounted
441
508
  : pre.computed.map((u) => [u.i, u.j]);
442
- const fused = remainder < ctx.space.maxGroup
443
- ? reasoned
444
- : meter
445
- ? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
446
- : await fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans);
447
- done(fused,
509
+ const fused = closed(state) ? reasoned : meter
510
+ ? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
511
+ : await fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans);
512
+ done(fused.product,
448
513
  // NO CLAIM ABOUT FUSION HERE. `fuseAttention` is entered whenever a
449
514
  // remainder ≥ W exists and returns early when there is nothing to bridge, so
450
515
  // this note used to assert a fusion that frequently did not happen (measured:
@@ -452,5 +517,5 @@ export async function think(ctx, query, mechs) {
452
517
  // reported by `fuseAttention`'s own `done` when it happens — the layer that
453
518
  // did the work is the layer that says so.
454
519
  "grounded, reasoned forward");
455
- return { bytes: fused, provenance };
520
+ return { bytes: fused.product, provenance };
456
521
  }
@@ -83,17 +83,6 @@ export type InspectRationale = (step: RationaleStep) => void;
83
83
  /** Decode bytes to text for display, dropping the NUL padding the encoder uses
84
84
  * (the same cleanup {@link Mind.respondText} does for its result). */
85
85
  export declare function decodeText(bytes: Uint8Array): string;
86
- /** The BYTE COUNT of the complement — what the currency calls `unaccounted`
87
- * in `weight = moves + PASS·unaccounted`. It lives here, beside the function
88
- * that produces the gaps, because the price's second term has ONE definition:
89
- * this was four copies of the same `reduce` (two in reasoning.ts, two in
90
- * pipeline.ts) before the architecture audit of `../auditoria-arquitectura-sema.md`
91
- * collapsed them. Same value at every site — the control diff is identical. */
92
- export declare function unaccountedBytes(spans: ReadonlyArray<readonly [number, number]>): number;
93
- /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
94
- * the same union-of-spans reading think's grounding decider prices at PASS
95
- * per byte, exposed here so a mechanism can turn it into a human label. */
96
- export declare function unexplainedSpans(queryLen: number, accounted: ReadonlyArray<[number, number]>): Array<[number, number]>;
97
86
  /** A human-readable label for the query bytes a mechanism's `accounted`
98
87
  * spans leave unexplained — purely diagnostic (Task 2's negative evidence):
99
88
  * it never changes a candidate's weight, only what the rationale trace
@@ -19,43 +19,17 @@
19
19
  // out into a ranked list of hits. So a step's `inputs` and `outputs` are each a
20
20
  // VECTOR — an ordered list of {@link RationaleItem}s, one per element — and the
21
21
  // fan-out / fan-in is visible in their lengths.
22
+ import { unexplainedSpans } from "./derivation.js";
22
23
  /** Decode bytes to text for display, dropping the NUL padding the encoder uses
23
24
  * (the same cleanup {@link Mind.respondText} does for its result). */
24
25
  export function decodeText(bytes) {
25
26
  return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
26
27
  }
27
- /** The BYTE COUNT of the complement — what the currency calls `unaccounted`
28
- * in `weight = moves + PASS·unaccounted`. It lives here, beside the function
29
- * that produces the gaps, because the price's second term has ONE definition:
30
- * this was four copies of the same `reduce` (two in reasoning.ts, two in
31
- * pipeline.ts) before the architecture audit of `../auditoria-arquitectura-sema.md`
32
- * collapsed them. Same value at every site — the control diff is identical. */
33
- export function unaccountedBytes(spans) {
34
- let total = 0;
35
- for (const [a, b] of spans)
36
- total += b - a;
37
- return total;
38
- }
39
- /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
40
- * the same union-of-spans reading think's grounding decider prices at PASS
41
- * per byte, exposed here so a mechanism can turn it into a human label. */
42
- export function unexplainedSpans(queryLen, accounted) {
43
- const sorted = accounted
44
- .map(([s, e]) => [Math.max(0, s), Math.min(queryLen, e)])
45
- .filter(([s, e]) => e > s)
46
- .sort((a, b) => a[0] - b[0]);
47
- const gaps = [];
48
- let reach = 0;
49
- for (const [s, e] of sorted) {
50
- if (s > reach)
51
- gaps.push([reach, s]);
52
- if (e > reach)
53
- reach = e;
54
- }
55
- if (reach < queryLen)
56
- gaps.push([reach, queryLen]);
57
- return gaps;
58
- }
28
+ // THE SPAN ALGEBRA LIVES IN derivation.ts. `unaccountedBytes` and
29
+ // `unexplainedSpans` are the closure law's vocabulary — gap arithmetic over the
30
+ // asker's own bytes — and they moved to the layer that owns the law, so they
31
+ // have ONE home and are no longer asked of the tracer. This module keeps the
32
+ // inference TOLD as it happens, and reads the gaps only to render a label.
59
33
  /** A human-readable label for the query bytes a mechanism's `accounted`
60
34
  * spans leave unexplained — purely diagnostic (Task 2's negative evidence):
61
35
  * it never changes a candidate's weight, only what the rationale trace