@hviana/sema 0.8.2 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/AGENTS.md +38 -37
  2. package/README.md +17 -38
  3. package/TRADEMARKS.md +0 -1
  4. package/dist/example/demo.js +85 -34
  5. package/dist/src/config.d.ts +11 -0
  6. package/dist/src/config.js +2 -0
  7. package/dist/src/geometry.d.ts +21 -10
  8. package/dist/src/geometry.js +21 -12
  9. package/dist/src/meter.d.ts +62 -0
  10. package/dist/src/meter.js +62 -0
  11. package/dist/src/mind/articulation.js +1 -1
  12. package/dist/src/mind/attention.d.ts +4 -0
  13. package/dist/src/mind/attention.js +167 -17
  14. package/dist/src/mind/canonical.d.ts +16 -0
  15. package/dist/src/mind/canonical.js +41 -0
  16. package/dist/src/mind/derivation.d.ts +201 -0
  17. package/dist/src/mind/derivation.js +327 -0
  18. package/dist/src/mind/graph-search.d.ts +2 -1
  19. package/dist/src/mind/graph-search.js +70 -29
  20. package/dist/src/mind/match.d.ts +3 -1
  21. package/dist/src/mind/match.js +7 -3
  22. package/dist/src/mind/mechanisms/alu.js +0 -2
  23. package/dist/src/mind/mechanisms/cast.d.ts +1 -5
  24. package/dist/src/mind/mechanisms/cast.js +16 -19
  25. package/dist/src/mind/mechanisms/confluence.d.ts +0 -3
  26. package/dist/src/mind/mechanisms/confluence.js +27 -9
  27. package/dist/src/mind/mechanisms/cover.js +17 -20
  28. package/dist/src/mind/mechanisms/extraction.d.ts +0 -1
  29. package/dist/src/mind/mechanisms/extraction.js +13 -8
  30. package/dist/src/mind/mechanisms/prefix-completion.js +0 -1
  31. package/dist/src/mind/mechanisms/recall.d.ts +0 -1
  32. package/dist/src/mind/mechanisms/recall.js +40 -13
  33. package/dist/src/mind/mechanisms/reference.js +3 -4
  34. package/dist/src/mind/mind.d.ts +4 -2
  35. package/dist/src/mind/mind.js +5 -4
  36. package/dist/src/mind/pipeline-mechanism.d.ts +7 -3
  37. package/dist/src/mind/pipeline.js +136 -44
  38. package/dist/src/mind/primitives.js +9 -1
  39. package/dist/src/mind/rationale.d.ts +21 -5
  40. package/dist/src/mind/rationale.js +16 -21
  41. package/dist/src/mind/reasoning.d.ts +12 -20
  42. package/dist/src/mind/reasoning.js +190 -106
  43. package/dist/src/mind/recognition.js +4 -8
  44. package/dist/src/mind/resonance.js +20 -1
  45. package/dist/src/mind/trace.js +1 -0
  46. package/dist/src/mind/traverse.js +6 -2
  47. package/dist/src/mind/types.d.ts +36 -13
  48. package/dist/src/mind/types.js +6 -3
  49. package/docs/INDEX.md +23 -24
  50. package/docs/INVARIANTS.md +16 -17
  51. package/docs/architecture/bounded-reads.md +5 -5
  52. package/docs/architecture/closure.md +65 -0
  53. package/docs/architecture/commonality.md +29 -20
  54. package/docs/architecture/cost-model.md +7 -7
  55. package/docs/architecture/determinism.md +7 -7
  56. package/docs/architecture/exact-vs-approximate.md +4 -4
  57. package/docs/architecture/factored-machinery.md +14 -14
  58. package/docs/architecture/match-project.md +2 -3
  59. package/docs/architecture/mechanism-market.md +16 -16
  60. package/docs/architecture/meter.md +10 -11
  61. package/docs/architecture/store.md +4 -4
  62. package/docs/architecture/thresholds.md +1 -1
  63. package/docs/failures/tempting-but-wrong.md +14 -5
  64. package/docs/harness/gates.md +7 -7
  65. package/docs/mechanisms/cast.md +2 -2
  66. package/docs/mechanisms/cover.md +4 -5
  67. package/docs/mechanisms/extraction.md +7 -7
  68. package/docs/mechanisms/recall.md +8 -9
  69. package/example/demo.ts +90 -37
  70. package/jsr.json +1 -1
  71. package/package.json +1 -1
  72. package/src/alu/README.md +11 -12
  73. package/src/config.ts +13 -0
  74. package/src/geometry.ts +21 -13
  75. package/src/meter.ts +62 -0
  76. package/src/mind/articulation.ts +0 -1
  77. package/src/mind/attention.ts +169 -17
  78. package/src/mind/canonical.ts +43 -0
  79. package/src/mind/derivation.ts +473 -0
  80. package/src/mind/graph-search.ts +76 -34
  81. package/src/mind/match.ts +7 -3
  82. package/src/mind/mechanisms/alu.ts +0 -2
  83. package/src/mind/mechanisms/cast.ts +20 -22
  84. package/src/mind/mechanisms/confluence.ts +27 -13
  85. package/src/mind/mechanisms/cover.ts +17 -20
  86. package/src/mind/mechanisms/extraction.ts +13 -9
  87. package/src/mind/mechanisms/prefix-completion.ts +0 -1
  88. package/src/mind/mechanisms/recall.ts +39 -13
  89. package/src/mind/mechanisms/reference.ts +2 -3
  90. package/src/mind/mind.ts +6 -4
  91. package/src/mind/pipeline-mechanism.ts +7 -3
  92. package/src/mind/pipeline.ts +160 -52
  93. package/src/mind/primitives.ts +9 -1
  94. package/src/mind/rationale.ts +27 -23
  95. package/src/mind/reasoning.ts +227 -120
  96. package/src/mind/recognition.ts +4 -8
  97. package/src/mind/resonance.ts +19 -1
  98. package/src/mind/trace.ts +1 -0
  99. package/src/mind/traverse.ts +7 -5
  100. package/src/mind/types.ts +41 -15
  101. package/test/105-derive-through-reports-its-refusal.test.mjs +24 -0
  102. package/test/118-the-join-reaches-a-key-off-the-cut.test.mjs +74 -0
  103. package/test/119-the-work-does-not-grow-with-the-corpus.test.mjs +122 -0
  104. package/test/120-composition-is-consequence.test.mjs +132 -0
  105. package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +128 -0
  106. package/test/122-the-climb-search-does-not-grow-with-the-corpus.test.mjs +117 -0
  107. package/test/123-the-paired-formulas-agree.test.mjs +90 -0
  108. package/test/125-the-post-grounding-branch-publishes-its-operand.test.mjs +51 -0
  109. package/test/126-the-pipeline-does-not-name-mechanisms.test.mjs +42 -0
  110. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +83 -0
  111. package/test/129-the-trace-payload-shape.test.mjs +164 -0
  112. package/test/133-the-decision-point-renders-the-state.test.mjs +204 -0
  113. package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
  114. package/test/135-one-law-any-producer.test.mjs +289 -0
  115. package/test/136-the-two-named-limits.test.mjs +205 -0
  116. package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
  117. package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
  118. package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
  119. package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
  120. package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
  121. package/test/32-confluence.test.mjs +68 -0
  122. package/test/36-already-answered-fusion.test.mjs +20 -2
  123. package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
  124. package/test/38-reason-restate-guard.test.mjs +28 -2
  125. package/test/43-cast-analog-seat.test.mjs +10 -0
  126. package/test/55-cost-meter.test.mjs +862 -0
@@ -24,7 +24,7 @@ import {
24
24
  voicesDisplacedFiller,
25
25
  } from "../match.js";
26
26
  import { CONCEPT, STEP } from "../graph-search.js";
27
- import { unexplainedLabel } from "../rationale.js";
27
+ import { restates as lawRestates } from "../derivation.js";
28
28
  import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
29
29
  import { rItem, rNode } from "../trace.js";
30
30
  import { substitutionBridge } from "../bridge.js";
@@ -35,7 +35,6 @@ export interface RecallResult {
35
35
  echoed: boolean;
36
36
  accounted: Array<[number, number]>;
37
37
  moves: number;
38
- unexplained: string;
39
38
  /** See {@link import("../pipeline-mechanism.js").MechanismResult.complete}
40
39
  * — set by the IDENTITY-bridge tier alone. */
41
40
  complete?: boolean;
@@ -71,7 +70,6 @@ export async function recallByResonance(
71
70
  echoed,
72
71
  accounted,
73
72
  moves,
74
- unexplained: unexplainedLabel(query, accounted),
75
73
  ...(complete ? { complete } : {}),
76
74
  };
77
75
  };
@@ -156,7 +154,7 @@ export async function recallByResonance(
156
154
  // conversation reads as if it were the next thing to say.
157
155
  if (
158
156
  g !== null && g.length > 0 &&
159
- !(g.length < query.length && indexOf(query, g, 0) >= 0)
157
+ !lawRestates(query, g, 0, { proper: true })
160
158
  ) {
161
159
  return ground(
162
160
  g,
@@ -196,10 +194,11 @@ export async function recallByResonance(
196
194
  // same principle that keeps cast from voicing stored questions), and
197
195
  // projecting them forward is reverse recall's containment failure in the
198
196
  // other direction — "whatever followed these bytes in some document".
199
- const qKey = ctx.canon ? ctx.canon(query) : query;
197
+ // THE EQUALITY READING of the restatement law: this tier rejects an answer
198
+ // that IS the question (an echo), and a proper fragment is handled by the
199
+ // tier's own subspan tests further down — so the law is asked with `whole`.
200
200
  const restates = (b: Uint8Array): boolean =>
201
- bytesEqual(b, query) ||
202
- (ctx.canon !== null && bytesEqual(ctx.canon(b), qKey));
201
+ lawRestates(query, b, 0, { equate: ctx.canon, whole: true });
203
202
  const idBar = identityBar(ctx.store.D, ctx.space.maxGroup, query.length);
204
203
  if (top.score >= idBar) {
205
204
  for (const h of whole) {
@@ -276,9 +275,36 @@ export async function recallByResonance(
276
275
  // consensus", while breadth is the SCALE-INVARIANT reading — "a point whose
277
276
  // breadth clears `dominates` (> half the query's regions corroborate it) is
278
277
  // real consensus; one that does not is a coincidental single-region echo".
279
- // Attention.peak's contract makes the same point from the other side:
280
- // comparing a POOLED SUM against a floor that prices ONE region's evidence
281
- // is a dimensional error.
278
+ // THIS USED TO CLAIM A DIMENSIONAL ERROR, AND THAT CLAIM WAS FALSE.
279
+ // It read: "comparing a POOLED SUM against a floor that prices ONE region's
280
+ // evidence is a dimensional error." `consensusFloor` is not priced for one
281
+ // region: thresholds.md §2 derives it as the POOLED-vote significance floor —
282
+ // "each region contributes at most ln(N/c) <= ln(N); ln(N)+1/2 demands ..." —
283
+ // and attention.ts says the same where it builds the vote ("the scale
284
+ // consensusFloor is derived for"). The comparison is in ONE dimension, and
285
+ // it is so because the climb WEIGHTS BY IDF: `wf` in voteRegions is
286
+ // `direct ? df : combined ? idf + df : idf`, and the engine only ever runs the
287
+ // last one (DFMode's default "inverse", the mode every non-test caller uses —
288
+ // `direct` and `combined` are exercised by test/24 and test/27 only, and
289
+ // test/24 pins that their votes DO differ). In those two the sum would leave
290
+ // the floor's dimension and the floor would need re-deriving.
291
+ //
292
+ // What the OR below is really for is SCALE, not dimension (the paragraph
293
+ // above says it): a vote that clears ln(N)+1/2 means "strong" on a small store
294
+ // and "weak" on a large one for the same genuine consensus, so the
295
+ // scale-invariant breadth reading is added beside it.
296
+ //
297
+ // AND THE PREMISE IS IDF. The deviation in the other two weighting modes is
298
+ // TWO-SIDED and DERIVED: `direct` DEFLATES a region (ln(1+c) < ln(N/c) for
299
+ // small c) and `combined` INFLATES it (ln N + ln(1+1/c)), both by at most
300
+ // `ln 2` — see `geometry.ts`'s `consensusFloor`, where the bound lives.
301
+ // MEASURED on 8 anchors across 5 queries, running the same climb in all
302
+ // three modes: ZERO gate inversions — every anchor's `vote >= floor` verdict
303
+ // is the same in `inverse`, `direct` and `combined`, even where the readings
304
+ // straddle the floor on opposite sides (#148: inverse 3.39, combined 4.71
305
+ // above it, direct 1.31 below). Pinned by test/55's test 20. The bar is not
306
+ // re-derived for those modes because nothing reachable needs it; the premise
307
+ // is IDF, and that is now written where the gate reads it.
282
308
  //
283
309
  // Measured on the 15.7M-node store (N=325,615, so the old floor was 13.19).
284
310
  // The absolute vote cannot separate right from wrong at this scale, and the
@@ -348,7 +374,7 @@ export async function recallByResonance(
348
374
  if (
349
375
  forest.length > 0 &&
350
376
  !allWindowsAreScaffolding(ctx, query) &&
351
- (forest[0].vote >= minVote ||
377
+ (forest[0].idfVote >= minVote || // the IDF sum: the bar's own quantity
352
378
  (dominates(forest[0].breadth, 1) && forest[0].peak > Math.LN2))
353
379
  ) {
354
380
  const g = await project(ctx, forest[0].anchor, queryGist);
@@ -381,7 +407,7 @@ export async function recallByResonance(
381
407
  // — never an answer (the same principle as `restates` above, extended
382
408
  // to fragments). Genuine anchor groundings — longer than the query,
383
409
  // or disjoint from it — pass untouched.
384
- else if (g && !(g.length < query.length && indexOf(query, g, 0) >= 0)) {
410
+ else if (g && !lawRestates(query, g, 0, { proper: true })) {
385
411
  return ground(
386
412
  g,
387
413
  "scaffolding-dominated query — ground the consensus-climb anchor",
@@ -600,8 +626,8 @@ export const recallMechanism: PipelineMechanism = {
600
626
  bytes: r.bytes,
601
627
  accounted: r.accounted,
602
628
  moves: r.moves,
603
- unexplained: r.unexplained,
604
629
  provenance: r.echoed ? "recall-echo" : "recall",
630
+ used: new Set<number>(),
605
631
  ...(r.complete ? { complete: true } : {}),
606
632
  }];
607
633
  },
@@ -39,7 +39,7 @@ import type { FrameInstance } from "../match.js";
39
39
  import { carriesFillers, distinct, follow, substituteAll } from "../match.js";
40
40
  import { dominates } from "../../geometry.js";
41
41
  import { bytesEqual, indexOf } from "../../bytes.js";
42
- import { unexplainedLabel } from "../rationale.js";
42
+ import { restates } from "../derivation.js";
43
43
  import { STEP } from "../graph-search.js";
44
44
  import type {
45
45
  MechanismResult,
@@ -231,7 +231,7 @@ export async function bindReference(
231
231
  if (bytes.length === 0) return fail("the binding produced nothing");
232
232
  // Answering with the question is not answering — the same restated-fragment
233
233
  // guard every recall tier applies.
234
- if (bytes.length < query.length && indexOf(query, bytes, 0) >= 0) {
234
+ if (restates(query, bytes, 0, { proper: true })) {
235
235
  return fail("the binding restates part of the question");
236
236
  }
237
237
  const carried = !bytesEqual(bytes, first);
@@ -278,7 +278,6 @@ export async function bindReference(
278
278
  // binding claims strictly more than a one-slot binding, so where both are
279
279
  // licensed the smaller claim wins.
280
280
  moves: STEP * slots.length + STEP,
281
- unexplained: unexplainedLabel(query, accounted),
282
281
  // NOT scaffolding. That field counts answer bytes carried through BECAUSE
283
282
  // NOTHING EXPLAINED THEM; a referent is carried because the frame's slot
284
283
  // explains it, and it is accounted above. Reporting it would make every
package/src/mind/mind.ts CHANGED
@@ -16,7 +16,6 @@ import type { CorpusPair, CorpusResult } from "./corpus.js";
16
16
  import { Alphabet } from "../alphabet.js";
17
17
  import {
18
18
  bytesToTree,
19
- contentBoundaries,
20
19
  contentFoldIncremental,
21
20
  Grid,
22
21
  gridToTree,
@@ -24,6 +23,7 @@ import {
24
23
  reachThreshold,
25
24
  stackGrids,
26
25
  } from "../geometry.js";
26
+ import { keyEnds } from "./canonical.js";
27
27
  import type { ContentFold } from "../geometry.js";
28
28
  import { BoundedMap, type Store } from "../store.js";
29
29
  import { SQliteStore } from "../store-sqlite.js";
@@ -244,6 +244,8 @@ export interface MindOptions {
244
244
  seed?: number;
245
245
  recallQueryK?: number;
246
246
  haloQueryK?: number;
247
+ /** Branch nodes the pivot sweep may probe — see {@link MindConfig}. */
248
+ pivotProbeK?: number;
247
249
  /** Items one rationale step may itemise — see {@link MindConfig}. */
248
250
  rationaleSampleK?: number;
249
251
  /** Corpus-reading capacities and budgets — see {@link MindConfig}. */
@@ -443,9 +445,9 @@ export class Mind implements MindContext {
443
445
  * with the most distributional evidence (highest `prevOf` count — the
444
446
  * structural manifestation of its halo). When evidence is equal the
445
447
  * first-inserted edge wins. */
446
- /** See {@link GraphSearchHost.contentCuts}. */
447
- contentCuts(bytes: Uint8Array): readonly number[] {
448
- return contentBoundaries(this.space, bytes);
448
+ /** See {@link GraphSearchHost.contentKeyEnds}. */
449
+ contentKeyEnds(prefix: Uint8Array, tail: Uint8Array): readonly number[] {
450
+ return keyEnds(this, prefix, tail);
449
451
  }
450
452
 
451
453
  chooseNext(node: number): number | undefined {
@@ -675,10 +675,14 @@ export interface MechanismResult {
675
675
  bytes: Uint8Array;
676
676
  accounted: Array<[number, number]>;
677
677
  moves: number;
678
+ /** WHAT THIS ANSWER SPEAKS FOR — the anchors it voices, and therefore the
679
+ * content the reasoner must not pivot back through. Declared by the
680
+ * mechanism about its OWN result, exactly like `accounted`/`used`/
681
+ * `complete`: post-grounding honours the property and NEVER ASKS WHICH
682
+ * MECHANISM SET IT, so the market stays uniform. An EMPTY set is a real
683
+ * declaration — "this answer voices nothing" (recall) — and withholds
684
+ * nothing; omit the field and the pipeline re-recognises the answer. */
678
685
  used?: ReadonlySet<number>;
679
- unexplained: string;
680
- /** Explicit weight override. When absent, weight = moves + PASS·unaccounted. */
681
- weight?: number;
682
686
  /** Bytes of `bytes` that came from spans nothing recognised — the asker's
683
687
  * own words carried through verbatim rather than derived (see
684
688
  * {@link liftedScaffolding}). Reported, not priced: the ladder prices what
@@ -15,8 +15,17 @@ import type { ComputedSpan } from "../extension.js";
15
15
  import { gistOf, read, resolve } from "./primitives.js";
16
16
  import { recognise } from "./recognition.js";
17
17
  import { fuseAttention, reason } from "./reasoning.js";
18
- import { unexplainedSpans } from "./rationale.js";
18
+ import {
19
+ closed,
20
+ type DerivationState,
21
+ remainderOf,
22
+ type Span,
23
+ unaccountedBytes,
24
+ unexplainedSpans,
25
+ windowOf,
26
+ } from "./derivation.js";
19
27
  import { rItem } from "./trace.js";
28
+ import { unexplainedLabel } from "./rationale.js";
20
29
  import { hubBound } from "./traverse.js";
21
30
  import { type PipelineMechanism, Precomputed } from "./pipeline-mechanism.js";
22
31
  import { coverMechanism } from "./mechanisms/cover.js";
@@ -244,7 +253,6 @@ export async function think(
244
253
  weight: number;
245
254
  used?: ReadonlySet<number>;
246
255
  accounted: ReadonlyArray<[number, number]>;
247
- unexplained: string;
248
256
  complete?: boolean;
249
257
  /** Bytes of this candidate's ANSWER that came from spans nothing
250
258
  * recognised — query words carried through verbatim (see
@@ -253,8 +261,7 @@ export async function think(
253
261
  }
254
262
  const grade = (w: number) => Math.floor(w / STEP);
255
263
  const unaccounted = (spans: ReadonlyArray<[number, number]>): number =>
256
- unexplainedSpans(query.length, spans)
257
- .reduce((sum, [s, e]) => sum + (e - s), 0);
264
+ unaccountedBytes(unexplainedSpans(query.length, spans));
258
265
  const weigh = (
259
266
  accounted: ReadonlyArray<[number, number]>,
260
267
  moves: number,
@@ -394,14 +401,16 @@ export async function think(
394
401
  ? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
395
402
  : await mech.run(ctx, query, pre);
396
403
  for (const r of results) {
397
- const weight = r.weight ?? weigh(r.accounted, r.moves);
404
+ // ONE FORMULA, EVERY CANDIDATE: the chart's derivation reports how many
405
+ // discrete moves it made and which bytes it could not recognise; the
406
+ // currency prices both. No mechanism passes a price of its own.
407
+ const weight = weigh(r.accounted, r.moves);
398
408
  consider({
399
409
  bytes: r.bytes,
400
410
  provenance: r.provenance ?? mech.provenance,
401
411
  weight,
402
412
  used: r.used,
403
413
  accounted: r.accounted,
404
- unexplained: r.unexplained,
405
414
  complete: r.complete,
406
415
  scaffolding: r.scaffolding,
407
416
  });
@@ -434,14 +443,21 @@ export async function think(
434
443
  : null;
435
444
  ctx.trace?.step(
436
445
  "decideGrounding",
437
- candidates.map((c) =>
438
- rItem(
446
+ // THE LABEL IS RENDERED WHERE IT IS SHOWN. It was a field on every
447
+ // mechanism's result, and at every one of them it was exactly
448
+ // `unexplainedLabel(query, accounted)` — a second representation of a
449
+ // quantity one pure function already yields, computed on every response
450
+ // whether or not anyone looked. Here it is computed only when a rationale
451
+ // is attached, because `trace?.step` short-circuits its arguments.
452
+ candidates.map((c) => {
453
+ const label = unexplainedLabel(query, c.accounted);
454
+ return rItem(
439
455
  c.bytes,
440
456
  `${c.provenance} (weight ${c.weight.toFixed(3)}${
441
- c.unexplained ? `, unexplained: "${c.unexplained}"` : ""
457
+ label ? `, unexplained: "${label}"` : ""
442
458
  })`,
443
- )
444
- ),
459
+ );
460
+ }),
445
461
  decided ? [rItem(decided.bytes, decided.provenance)] : [],
446
462
  "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder",
447
463
  undefined,
@@ -504,14 +520,65 @@ export async function think(
504
520
  }
505
521
  const answer: Uint8Array = decided.bytes;
506
522
  const provenance = decided.provenance as Provenance;
507
- const castUsed: ReadonlySet<number> = decided.used ?? new Set();
523
+ const declaredUsed = decided.used;
508
524
 
509
- // ── Post-grounding, gated by provenance ──────────────────────────────
510
- const preConsumed = provenance === "cast" || provenance === "join"
511
- ? castUsed
512
- : provenance === "recall" || provenance === "recall-echo"
513
- ? new Set<number>()
514
- : new Set(recognise(ctx, answer).sites.map((s) => s.payload));
525
+ // ── THE DERIVATION STATE ─────────────────────────────────────────────
526
+ //
527
+ // THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
528
+ // the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
529
+ // with the same W floor. `accounted` is a COST quantity (measured: a query
530
+ // fully explained by one computed span plus bridged connectors reports
531
+ // `accounted: []` while nothing is unexplained), and a remainder under one
532
+ // river-fold quantum is bridging punctuation, never a second topic — so it
533
+ // licenses no extension and blocks none.
534
+ //
535
+ // The state is built HERE, where these quantities are already computed, so it
536
+ // costs nothing new: `accounted` is what the winning transition priced,
537
+ // `remainder` is the coverage reading over `accounted ∪ the response's
538
+ // computed spans` (the union is what makes the two different quantities, and
539
+ // both are kept), `cost` is the ladder position, and the two declarations are
540
+ // the producer's own (`fixed`, `used`). What follows reads THIS state rather
541
+ // than a tuple rebuilt at each call site.
542
+ // A DERIVATION IS BORN OWING WHAT ITS ANSWER DOES NOT CARRY. The winning
543
+ // transition PRICED these spans, and pricing is not carrying: coverage claimed
544
+ // without evidence stays owed, and a later transition pays it only by carrying
545
+ // it (the law reads the window; see derivation.ts). Same reading, one
546
+ // definition — not a second spelling of it here.
547
+ const explained: Array<[number, number]> = [
548
+ ...decided.accounted,
549
+ ...pre.computed.map((u): [number, number] => [u.i, u.j]),
550
+ ].filter(([a, b]) =>
551
+ windowOf([a, b], answer, query, ctx.space.maxGroup) !== null
552
+ );
553
+ // WHAT THE CONSTRUCTION WITHHOLDS, at or above one quantum: the difference between
554
+ // the remainder paid in full and the remainder paid by carrying. Both readings
555
+ // are the law's, so the floor is applied once and in one place.
556
+ const paidInFull = remainderOf(
557
+ query.length,
558
+ [
559
+ ...decided.accounted,
560
+ ...pre.computed.map((u): [number, number] => [u.i, u.j]),
561
+ ],
562
+ ctx.space.maxGroup,
563
+ );
564
+ const paid = remainderOf(query.length, explained, ctx.space.maxGroup);
565
+ if (ctx.meter) {
566
+ ctx.meter.groundingWithheldBytes += unaccountedBytes(paid) -
567
+ unaccountedBytes(paidInFull);
568
+ }
569
+ const state: DerivationState = {
570
+ product: answer,
571
+ accounted: decided.accounted,
572
+ remainder: paid,
573
+ cost: decided.weight,
574
+ fixed: decided.complete,
575
+ used: decided.used,
576
+ };
577
+ const uncovered = state.remainder;
578
+
579
+ // ── Post-grounding, gated by the declaration and the remainder ────────
580
+ const preConsumed = declaredUsed ??
581
+ new Set(recognise(ctx, answer).sites.map((s) => s.payload));
515
582
  // A grounding that DECLARED itself complete is not extended: the answer is
516
583
  // already a trained form's own continuation, reached through an identity
517
584
  // claim about the query, so a multi-hop pivot could only chain past the
@@ -540,11 +607,41 @@ export async function think(
540
607
  // `preConsumed` is derived by re-recognising the answer — "everything in
541
608
  // it", not "what it voiced" — and a containment rule over that would
542
609
  // suppress every pivot the answer legitimately contains.
543
- const voiced = (provenance === "cast" || provenance === "join")
544
- ? [...castUsed].flatMap((id) =>
545
- ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n))
546
- )
547
- : [];
610
+ const voiced = declaredUsed === undefined ? [] : [...declaredUsed].flatMap(
611
+ (id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)),
612
+ );
613
+ // WHAT THIS BRANCH READ, published where it was read. Post-grounding decides
614
+ // by the DECLARATION (`decided.used`, which becomes `voiced`), by what the
615
+ // recognition already consumed (`preConsumed`) and by the derivation's own
616
+ // remainder — never by the provenance NAME, which is REPORTED throughout and
617
+ // compared nowhere (a stale comment here claimed otherwise; the register caught
618
+ // it, and this is the correction). The operands were invisible in the trace, so
619
+ // a change to the branching could not be shown equivalent or otherwise from
620
+ // outside — three separate investigations failed on exactly that gap. A gap in instrumentation is a defect IN the instrumentation
621
+ // (AGENTS.md §6): closed here, once, as counts only — never content.
622
+ ctx.trace?.step(
623
+ "postGrounding",
624
+ [rItem(answer, provenance)],
625
+ [],
626
+ `used=${decided.used !== undefined ? "declared" : "absent"} · ` +
627
+ `preConsumed=${preConsumed.size} · voiced=${voiced.length}`,
628
+ undefined,
629
+ {
630
+ version: 1,
631
+ provenance,
632
+ usedDeclared: decided.used !== undefined,
633
+ preConsumed: preConsumed.size,
634
+ voiced: voiced.length,
635
+ // THE STATE THE LAW GOVERNS, rendered where it is decided: what the asker
636
+ // said that no step has accounted for, in spans at or above one quantum,
637
+ // and whether the producer supplied a fixed point. Counts only, like
638
+ // every other operand here — and the spans are the state's, so a reader
639
+ // can check them against the meter's aggregate of the same remainder.
640
+ remainderSpans: state.remainder.length,
641
+ remainderBytes: unaccountedBytes(state.remainder),
642
+ fixed: state.fixed === true,
643
+ },
644
+ );
548
645
  // REPORTABLE, NOT SILENT. A declared-complete grounding ends the derivation
549
646
  // here, and that decision is part of the derivation's shape: the reader of a
550
647
  // rationale must be able to see that the chain stopped because the mechanism
@@ -560,25 +657,25 @@ export async function think(
560
657
  "post-grounding extension is skipped",
561
658
  );
562
659
  }
563
- // THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
564
- // the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
565
- // with the same W floor. `accounted` is a COST quantity (measured: a query
566
- // fully explained by one computed span plus bridged connectors reports
567
- // `accounted: []` while nothing is unexplained), and a remainder under one
568
- // river-fold quantum is bridging punctuation, never a second topic — so it
569
- // licenses no extension and blocks none.
570
- const explained: Array<[number, number]> = [
571
- ...decided.accounted,
572
- ...pre.computed.map((u): [number, number] => [u.i, u.j]),
573
- ];
574
- const uncovered = unexplainedSpans(query.length, explained)
575
- .filter(([a, b]) => b - a >= ctx.space.maxGroup);
576
- const reasoned = decided.complete ? answer : meter
660
+ // PUBLISHED, NOT RECOMPUTED: the same `uncovered` the gates below read. A
661
+ // write-only accounting (meter contract 1), so the number that licenses an
662
+ // extension or a fusion stops being invisible.
663
+ if (meter) {
664
+ meter.postGroundingRemainderSpans += uncovered.length;
665
+ meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
666
+ }
667
+ // THE WALK CONSUMES AND RETURNS A STATE. It is handed the derivation's own —
668
+ // the grounding's product, accounting, remainder and cost — and hands back the
669
+ // state it advanced to, so what follows reads a state rather than bytes plus a
670
+ // tuple rebuilt here. A supplied fixed point is the one case where the walk
671
+ // does not run at all, and then the state is the grounding's own.
672
+ const extension = decided.complete ? undefined : meter
577
673
  ? await meter.time(
578
674
  "reason",
579
- () => reason(ctx, query, answer, preConsumed, pre, voiced, uncovered),
675
+ () => reason(ctx, query, state, preConsumed, pre, voiced),
580
676
  )
581
- : await reason(ctx, query, answer, preConsumed, pre, voiced, uncovered);
677
+ : await reason(ctx, query, state, preConsumed, pre, voiced);
678
+ const reasoned = extension ?? state;
582
679
 
583
680
  // Fuse only when the query has a genuine REMAINDER no mechanism's
584
681
  // structural evidence touched at all. `decided.accounted` alone
@@ -596,7 +693,15 @@ export async function think(
596
693
  // observed: a single space between two fully-computed arithmetic spans
597
694
  // ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
598
695
  // corpus fact, corrupting "4 6" into "4 63".
599
- const remainder = unaccounted(explained);
696
+ // THE GATE ASKS THE LAW, and that is an OPTIMISATION, not a tidy-up: the state
697
+ // above ALREADY carries the remainder (`remainderOf`, per-span, with the W
698
+ // floor applied), so asking it costs nothing, while the total this line used to
699
+ // compute (`unaccounted(explained)`) was one more sum over the spans on every
700
+ // response. The two readings are the same condition, not two: the ACCOUNTING
701
+ // applies the same W floor the gate does, so a gap below one quantum never
702
+ // survives into `explained` and the total cannot reach W without some single
703
+ // gap reaching it. Measured over twelve constructions at W = 4 (test/136.3,
704
+ // which pins the equivalence and both sides of it).
600
705
  // Whether the winning candidate's entire recognised substance is
601
706
  // COMPUTED — every accounted span exactly a pre.computed span, nothing
602
707
  // from a genuinely recognised/climbed site. fuseAttention's lone-root
@@ -606,8 +711,8 @@ export async function think(
606
711
  // `unclimbed` parameter, gated there by Attention.breadth so a
607
712
  // coincidental echo (which this flag alone cannot distinguish) is still
608
713
  // rejected.
609
- const unclimbed = decided.accounted.length > 0 &&
610
- decided.accounted.every(([i, j]) =>
714
+ const unclimbed = state.accounted.length > 0 &&
715
+ state.accounted.every(([i, j]) =>
611
716
  pre.computed.some((u) => u.i === i && u.j === j)
612
717
  );
613
718
  // Where the winning grounding stands in the query — fusion places primary
@@ -617,13 +722,10 @@ export async function think(
617
722
  // Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
618
723
  // read here for POSITION instead of for coverage — and resolved here, where
619
724
  // both readings are in hand, rather than inside fuseAttention.
620
- const primarySpans: ReadonlyArray<[number, number]> =
621
- decided.accounted.length > 0
622
- ? decided.accounted
623
- : pre.computed.map((u): [number, number] => [u.i, u.j]);
624
- const fused = remainder < ctx.space.maxGroup
625
- ? reasoned
626
- : meter
725
+ const primarySpans: ReadonlyArray<Span> = state.accounted.length > 0
726
+ ? state.accounted
727
+ : pre.computed.map((u): [number, number] => [u.i, u.j]);
728
+ const fused = closed(state) ? reasoned : meter
627
729
  ? await meter.time(
628
730
  "fuse",
629
731
  () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans),
@@ -638,8 +740,14 @@ export async function think(
638
740
  );
639
741
 
640
742
  done(
641
- fused,
642
- "grounded, reasoned forward, fused across points of attention",
743
+ fused.product,
744
+ // NO CLAIM ABOUT FUSION HERE. `fuseAttention` is entered whenever a
745
+ // remainder ≥ W exists and returns early when there is nothing to bridge, so
746
+ // this note used to assert a fusion that frequently did not happen (measured:
747
+ // "What is the capital of France famous for" fuses 0 times). The fusion is
748
+ // reported by `fuseAttention`'s own `done` when it happens — the layer that
749
+ // did the work is the layer that says so.
750
+ "grounded, reasoned forward",
643
751
  );
644
- return { bytes: fused, provenance };
752
+ return { bytes: fused.product, provenance };
645
753
  }
@@ -338,7 +338,15 @@ export function canonResolve(
338
338
  // on exactly the node the canonical-case query would have found.
339
339
  const folded = foldTree(ctx, perceive(ctx, bytesOf), 0).node;
340
340
  const use = folded ?? id;
341
- const leads = store.hasNext(use) || store.haloMass(use) > 0;
341
+ // THE ADMISSION PREDICATE, by its own pair of probes: `traverse.ts`'s
342
+ // `leadsSomewhere` is edge-or-halo, and `hasHalo` is the one that carries
343
+ // the mass bar (`mass >= minHaloMass`). Asking `haloMass(use) > 0` instead
344
+ // is the same answer only while `minHaloMass <= 1` (its default): raise the
345
+ // bar and this site would rank a node as leading on evidence the law
346
+ // refuses. Calling `leadsSomewhere` here is not possible — `traverse.ts`
347
+ // imports THIS file, so it would be a cycle — which is why the pair is
348
+ // spelled out rather than named.
349
+ const leads = store.hasNext(use) || store.hasHalo(use);
342
350
  if (
343
351
  best === null || (leads && !bestLeads) ||
344
352
  (leads === bestLeads && use < best)
@@ -21,6 +21,7 @@
21
21
  // fan-out / fan-in is visible in their lengths.
22
22
 
23
23
  import type { Vec } from "../vec.js";
24
+ import { unexplainedSpans } from "./derivation.js";
24
25
 
25
26
  /** One element of a step's input or output vector.
26
27
  *
@@ -49,6 +50,17 @@ export interface RationaleItem {
49
50
  * caller asked to carry it (off by default — a D-float array per item would
50
51
  * bury the reasoning it is meant to explain). */
51
52
  v?: Vec;
53
+ /** The element's OWN bytes, attached BY REFERENCE when the step was built from
54
+ * bytes (a `rationale.ts` item made from a node carries none: read it back
55
+ * through `node`). `text` is a RENDERING and cannot stand in for them — it
56
+ * decodes UTF-8 and DROPS NUL bytes, so a key containing one is unrecoverable
57
+ * from it, which is exactly how a join refusal (`deriveThroughMiss`) became
58
+ * impossible to test exactly without re-encoding. Treat as READ-ONLY: the
59
+ * array belongs to the caller (and may be a view into the query).
60
+ *
61
+ * Costs nothing when nothing inspects: items exist only while a rationale
62
+ * sink is attached, and this holds a reference rather than a copy. */
63
+ bytes?: Uint8Array;
52
64
  }
53
65
 
54
66
  /** A single completed act of inference — one mechanism, run once.
@@ -101,28 +113,11 @@ export function decodeText(bytes: Uint8Array): string {
101
113
  return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
102
114
  }
103
115
 
104
- /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
105
- * the same union-of-spans reading think's grounding decider prices at PASS
106
- * per byte, exposed here so a mechanism can turn it into a human label. */
107
- export function unexplainedSpans(
108
- queryLen: number,
109
- accounted: ReadonlyArray<[number, number]>,
110
- ): Array<[number, number]> {
111
- const sorted = accounted
112
- .map(([s, e]) =>
113
- [Math.max(0, s), Math.min(queryLen, e)] as [number, number]
114
- )
115
- .filter(([s, e]) => e > s)
116
- .sort((a, b) => a[0] - b[0]);
117
- const gaps: Array<[number, number]> = [];
118
- let reach = 0;
119
- for (const [s, e] of sorted) {
120
- if (s > reach) gaps.push([reach, s]);
121
- if (e > reach) reach = e;
122
- }
123
- if (reach < queryLen) gaps.push([reach, queryLen]);
124
- return gaps;
125
- }
116
+ // THE SPAN ALGEBRA LIVES IN derivation.ts. `unaccountedBytes` and
117
+ // `unexplainedSpans` are the closure law's vocabulary — gap arithmetic over the
118
+ // asker's own bytes — and they moved to the layer that owns the law, so they
119
+ // have ONE home and are no longer asked of the tracer. This module keeps the
120
+ // inference TOLD as it happens, and reads the gaps only to render a label.
126
121
 
127
122
  /** A human-readable label for the query bytes a mechanism's `accounted`
128
123
  * spans leave unexplained — purely diagnostic (Task 2's negative evidence):
@@ -264,7 +259,16 @@ export class Rationale {
264
259
  };
265
260
  }
266
261
 
267
- /** Record a mechanism that has no sub-steps — its inputs and outputs are both
262
+ /** WHY THIS NAME IS A FREE STRING, when the derivation's moves are a closed
263
+ * union: a mechanism name is WRITTEN and DISPLAYED, and it COMPOSES with
264
+ * the nesting — `mechanism` is the whole path (`["respond", "think",
265
+ * "recognise"]`), which no fixed union can express. Nothing branches on it:
266
+ * `nothing here drives the inference; it only WITNESSES it`. A vocabulary
267
+ * that is only witnessed needs no union; one that is read does
268
+ * (`DerivationMove`, in graph-search.ts). The asymmetry is the design, not
269
+ * a drift.
270
+ *
271
+ * Record a mechanism that has no sub-steps — its inputs and outputs are both
268
272
  * known at the call site. Returns its index, for a later step to depend on. */
269
273
  step(
270
274
  name: string,