@hviana/sema 0.9.0 → 0.9.2

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 (118) hide show
  1. package/AGENTS.md +7 -7
  2. package/dist/src/alu/src/index.d.ts +1 -1
  3. package/dist/src/alu/src/index.js +1 -1
  4. package/dist/src/alu/src/parser.js +2 -6
  5. package/dist/src/alu/src/resonance.d.ts +13 -0
  6. package/dist/src/alu/src/resonance.js +41 -0
  7. package/dist/src/alu/test/alu.test.js +39 -0
  8. package/dist/src/bytes.d.ts +6 -2
  9. package/dist/src/bytes.js +10 -4
  10. package/dist/src/canon.js +44 -0
  11. package/dist/src/geometry.d.ts +19 -1
  12. package/dist/src/geometry.js +125 -141
  13. package/dist/src/meter.d.ts +33 -0
  14. package/dist/src/meter.js +34 -1
  15. package/dist/src/mind/articulation.js +14 -1
  16. package/dist/src/mind/attention.d.ts +12 -0
  17. package/dist/src/mind/attention.js +44 -16
  18. package/dist/src/mind/bridge.js +3 -3
  19. package/dist/src/mind/derivation.d.ts +40 -0
  20. package/dist/src/mind/derivation.js +34 -0
  21. package/dist/src/mind/evidence.d.ts +24 -0
  22. package/dist/src/mind/evidence.js +90 -0
  23. package/dist/src/mind/graph-search.d.ts +89 -15
  24. package/dist/src/mind/graph-search.js +345 -174
  25. package/dist/src/mind/learning.js +1 -1
  26. package/dist/src/mind/mechanisms/cover.d.ts +19 -3
  27. package/dist/src/mind/mechanisms/cover.js +142 -61
  28. package/dist/src/mind/mechanisms/recall.js +10 -3
  29. package/dist/src/mind/mind.d.ts +6 -0
  30. package/dist/src/mind/mind.js +5 -2
  31. package/dist/src/mind/pipeline.d.ts +5 -1
  32. package/dist/src/mind/pipeline.js +220 -90
  33. package/dist/src/mind/primitives.d.ts +25 -5
  34. package/dist/src/mind/primitives.js +107 -44
  35. package/dist/src/mind/reasoning.d.ts +18 -4
  36. package/dist/src/mind/reasoning.js +487 -328
  37. package/dist/src/mind/recognition.js +29 -13
  38. package/dist/src/mind/resonance.js +1 -11
  39. package/dist/src/mind/traverse.d.ts +45 -5
  40. package/dist/src/mind/traverse.js +285 -8
  41. package/dist/src/mind/types.d.ts +16 -1
  42. package/dist/src/store-sqlite.d.ts +25 -0
  43. package/dist/src/store-sqlite.js +89 -1
  44. package/dist/src/store.d.ts +48 -4
  45. package/dist/src/store.js +86 -6
  46. package/docs/INDEX.md +20 -19
  47. package/docs/INVARIANTS.md +17 -16
  48. package/docs/architecture/bounded-reads.md +1 -1
  49. package/docs/architecture/caches.md +5 -4
  50. package/docs/architecture/closure.md +45 -5
  51. package/docs/architecture/cost-model.md +16 -0
  52. package/docs/architecture/evidence.md +113 -0
  53. package/docs/architecture/exact-vs-approximate.md +10 -9
  54. package/docs/architecture/factored-machinery.md +14 -13
  55. package/docs/architecture/fold-contract.md +51 -1
  56. package/docs/architecture/mechanism-market.md +21 -0
  57. package/docs/architecture/memoization.md +3 -3
  58. package/docs/architecture/meter.md +2 -1
  59. package/docs/architecture/saturation.md +12 -0
  60. package/docs/architecture/store.md +25 -2
  61. package/docs/failures/tempting-but-wrong.md +13 -2
  62. package/docs/harness/gates.md +12 -10
  63. package/docs/mechanisms/cover.md +23 -6
  64. package/jsr.json +1 -1
  65. package/package.json +1 -1
  66. package/src/alu/README.md +10 -2
  67. package/src/alu/src/index.ts +1 -0
  68. package/src/alu/src/parser.ts +6 -6
  69. package/src/alu/src/resonance.ts +42 -0
  70. package/src/alu/test/alu.test.ts +40 -0
  71. package/src/bytes.ts +13 -3
  72. package/src/canon.ts +40 -0
  73. package/src/geometry.ts +183 -154
  74. package/src/meter.ts +34 -1
  75. package/src/mind/articulation.ts +14 -2
  76. package/src/mind/attention.ts +47 -25
  77. package/src/mind/bridge.ts +3 -3
  78. package/src/mind/derivation.ts +77 -0
  79. package/src/mind/evidence.ts +107 -0
  80. package/src/mind/graph-search.ts +449 -221
  81. package/src/mind/learning.ts +1 -7
  82. package/src/mind/match.ts +1 -2
  83. package/src/mind/mechanisms/cast.ts +1 -2
  84. package/src/mind/mechanisms/cover.ts +207 -87
  85. package/src/mind/mechanisms/extraction.ts +1 -2
  86. package/src/mind/mechanisms/prefix-completion.ts +1 -1
  87. package/src/mind/mechanisms/recall.ts +17 -5
  88. package/src/mind/mechanisms/reference.ts +1 -1
  89. package/src/mind/mind.ts +9 -30
  90. package/src/mind/pipeline.ts +263 -104
  91. package/src/mind/primitives.ts +119 -43
  92. package/src/mind/reasoning.ts +611 -419
  93. package/src/mind/recognition.ts +24 -9
  94. package/src/mind/resonance.ts +2 -16
  95. package/src/mind/trace.ts +1 -1
  96. package/src/mind/traverse.ts +321 -8
  97. package/src/mind/types.ts +15 -11
  98. package/src/store-sqlite.ts +92 -1
  99. package/src/store.ts +113 -7
  100. package/test/105-derive-through-reports-its-refusal.test.mjs +8 -5
  101. package/test/106-the-join-fires.test.mjs +21 -0
  102. package/test/111-the-cover-assembly-is-counted.test.mjs +8 -5
  103. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +18 -12
  104. package/test/136-the-two-named-limits.test.mjs +3 -2
  105. package/test/137-the-law-lives-once-and-below.test.mjs +21 -0
  106. package/test/148-exact-shortcuts-agree.test.mjs +188 -0
  107. package/test/149-the-closure-engine.test.mjs +138 -0
  108. package/test/150-the-join-is-output-sensitive.test.mjs +66 -0
  109. package/test/151-the-cover-pays-for-what-it-reaches.test.mjs +142 -0
  110. package/test/152-the-read-side-names-as-the-write-side.test.mjs +146 -0
  111. package/test/153-a-cheaper-bound-is-looked-at-first.test.mjs +155 -0
  112. package/test/154-the-question-names-the-step.test.mjs +281 -0
  113. package/test/24-generalization.test.mjs +32 -0
  114. package/test/36-bloom.test.mjs +53 -0
  115. package/test/37-cluster-dispersion-fusion.test.mjs +75 -0
  116. package/test/48-recognise-turn-connective.test.mjs +3 -2
  117. package/test/55-cost-meter.test.mjs +4 -4
  118. package/test/90-connector-read-cap.test.mjs +7 -7
@@ -14,9 +14,10 @@ import { PASS, STEP } from "./graph-search.js";
14
14
  import type { ComputedSpan } from "../extension.js";
15
15
  import { gistOf, read, resolve } from "./primitives.js";
16
16
  import { recognise } from "./recognition.js";
17
- import { fuseAttention, reason } from "./reasoning.js";
17
+ import { fusionLayer, walkLayer } from "./reasoning.js";
18
18
  import {
19
19
  closed,
20
+ closeOver,
20
21
  type DerivationState,
21
22
  remainderOf,
22
23
  type Span,
@@ -26,8 +27,14 @@ import {
26
27
  } from "./derivation.js";
27
28
  import { rItem } from "./trace.js";
28
29
  import { unexplainedLabel } from "./rationale.js";
29
- import { hubBound } from "./traverse.js";
30
- import { type PipelineMechanism, Precomputed } from "./pipeline-mechanism.js";
30
+ import { hubBound, offsetCanon, scaffoldExtents } from "./traverse.js";
31
+ import { windowIndex, witness } from "./evidence.js";
32
+ import { indexOf } from "../bytes.js";
33
+ import {
34
+ type MechanismResult,
35
+ type PipelineMechanism,
36
+ Precomputed,
37
+ } from "./pipeline-mechanism.js";
31
38
  import { coverMechanism } from "./mechanisms/cover.js";
32
39
  import { castMechanism } from "./mechanisms/cast.js";
33
40
  import { confluenceMechanism } from "./mechanisms/confluence.js";
@@ -38,7 +45,7 @@ import { recallMechanism } from "./mechanisms/recall.js";
38
45
 
39
46
  // Re-exports: cover's pre-resolution helpers and the ALU adapter kept
40
47
  // importable from the pipeline module (their historical home).
41
- export { resolveConcepts, resolveConnectors } from "./mechanisms/cover.js";
48
+ export { offerConcepts, offerConnectors } from "./mechanisms/cover.js";
42
49
  export { aluToMechanism } from "./mechanisms/alu.js";
43
50
 
44
51
  // ── Extension dispatch (pre-loop parse) ─────────────────────────────────────
@@ -161,6 +168,10 @@ export interface RegimePredictionData {
161
168
  * CAST's floor) — the bar the incumbent must sit at or below for the
162
169
  * consensus climb to be skipped. */
163
170
  climbFloorGrade: number;
171
+ /** The lowest grade reached by a mechanism run AHEAD of a dearer one (see
172
+ * the grounding loop), when one ran and grounded: a bound below
173
+ * `climbFloorGrade` makes the regime retrieval whatever the incumbent. */
174
+ boundGrade?: number;
164
175
  }
165
176
 
166
177
  /** Think: a single lightest-derivation exploration of the Sema graph.
@@ -180,6 +191,13 @@ export async function think(
180
191
  if (query.length === 0) return null;
181
192
 
182
193
  ctx._edgeGuide = gistOf(ctx, query);
194
+ {
195
+ const asked = offsetCanon(ctx, query);
196
+ ctx._edgeAsked = {
197
+ bytes: asked,
198
+ index: windowIndex(asked, ctx.space.maxGroup),
199
+ };
200
+ }
183
201
  ctx._edgeChoice.clear();
184
202
 
185
203
  const t = ctx.trace?.enter("think", [rItem(query, "query")]);
@@ -335,7 +353,7 @@ export async function think(
335
353
  // below.
336
354
  const incumbent = best as Candidate | null;
337
355
  const incumbentGrade = incumbent === null ? null : grade(incumbent.weight);
338
- const regime: "retrieval" | "composition" = worthRunning(2 * STEP)
356
+ const regime: "retrieval" | "composition" = worthDeclared(2 * STEP)
339
357
  ? "composition"
340
358
  : "retrieval";
341
359
  ctx.trace?.step(
@@ -343,8 +361,10 @@ export async function think(
343
361
  [rItem(query, "query")],
344
362
  [],
345
363
  regime === "retrieval"
346
- ? `retrieval regime — incumbent grade ${incumbentGrade} ≤ climb floor ${climbFloorGrade}, ` +
347
- `so no mechanism floored above that grade runs; the consensus climb will not run`
364
+ ? (incumbentGrade !== null && incumbentGrade <= climbFloorGrade
365
+ ? `retrieval regime — incumbent grade ${incumbentGrade} ≤ climb floor ${climbFloorGrade}, `
366
+ : `retrieval regime — grade ${bound} already reached by a mechanism run ahead, below climb floor ${climbFloorGrade}, `) +
367
+ `so no mechanism floored above that grade runs; CAST will not climb`
348
368
  : `composition regime — ${
349
369
  incumbentGrade === null
350
370
  ? "no incumbent (nothing grounded)"
@@ -356,6 +376,7 @@ export async function think(
356
376
  regime,
357
377
  incumbentGrade,
358
378
  climbFloorGrade,
379
+ ...(bound !== Infinity ? { boundGrade: bound } : {}),
359
380
  } satisfies RegimePredictionData,
360
381
  );
361
382
  };
@@ -363,19 +384,139 @@ export async function think(
363
384
  // Per-mechanism accounting (src/meter.ts). The market's whole premise is
364
385
  // that mechanisms compete on one cost scale — so the profiling read-out is
365
386
  // also per-mechanism, uniformly: the loop never asks which one it holds.
366
- for (let mi = 0; mi < mechanisms.length; mi++) {
367
- const mech = mechanisms[mi];
368
- if (mi > 0) reportRegime();
387
+ //
388
+ // A CHEAPER BOUND IS LOOKED AT BEFORE A DEARER ONE IS PAID FOR.
389
+ //
390
+ // The declared order is the tie-break priority, and the pruning above is
391
+ // only as strong as the incumbent it has: a mechanism floored LOWER than the
392
+ // one about to invest, but declared after it, could not prune it. Measured
393
+ // on the 31.7M-node store: a lowercased dialogue turn (#97 of the battery)
394
+ // was won by recall at grade 1 — after CAST (floor grade 2) had paid the
395
+ // consensus climb and the weave, confluence (3) a reach climb, and extraction
396
+ // and reference their reads: ~10 s of a 12.6 s response, for candidates that
397
+ // could not win.
398
+ //
399
+ // So before mechanism `m` first-touches anything, every LATER mechanism whose
400
+ // bound is strictly lower runs AHEAD of it, cheapest bound first, and the
401
+ // lowest grade any of them reaches becomes `bound`. A mechanism whose floor
402
+ // grade exceeds `bound` is then skipped. THE DECISION IS UNCHANGED — the
403
+ // same candidate wins as in the declared order:
404
+ // • a mechanism `p` run ahead with best grade g bounds the final grade by
405
+ // g: in the declared order p either runs (its candidate is weighed) or is
406
+ // pruned by an incumbent already at or below p's floor ≤ g;
407
+ // • so a mechanism floored above `bound` has only candidates the final
408
+ // winner strictly outgrades — and with every candidate above `bound`
409
+ // dropped, every mechanism floored at or below it meets the same
410
+ // run-or-prune decision (`f < incumbent` iff `f < min(incumbent,
411
+ // bound + 1)` for f ≤ bound) and yields the same candidates;
412
+ // • and the winner is chosen from the candidates at or below `bound`, in
413
+ // declared order — `consider` replays them where they are declared.
414
+ // Equal-grade floors are NOT skipped (≤, not <): a mechanism declared earlier
415
+ // keeps the tie it would have won. Running `p` ahead is never extra work:
416
+ // what prunes p in the declared order is a candidate at or below p's floor,
417
+ // which only a mechanism floored at or below it can produce — and every such
418
+ // mechanism is either already run or run ahead of p.
419
+ //
420
+ // The bound is learnt by asking `floor` with a `worthRunning` that refuses:
421
+ // under the investment discipline (pipeline-mechanism.ts) a floor that cannot
422
+ // pay returns its bound UNINVESTED, so the question costs no analysis.
423
+ const refuse = () => false;
424
+ const probed: Array<number | null | undefined> = new Array(
425
+ mechanisms.length,
426
+ );
427
+ const probeGrade = async (i: number): Promise<number | null> => {
428
+ if (probed[i] === undefined) {
429
+ const f = await mechanisms[i].floor(ctx, query, pre, refuse);
430
+ probed[i] = f === null ? null : grade(f);
431
+ }
432
+ return probed[i]!;
433
+ };
434
+ /** Per mechanism: its floor once computed, and its results once run. */
435
+ const floors = new Map<number, number | null>();
436
+ const runs = new Map<number, MechanismResult[]>();
437
+ let bound = Infinity;
438
+ const worthAhead = (floor: number) =>
439
+ grade(floor) <
440
+ Math.min(best === null ? Infinity : grade(best.weight), bound);
441
+ const worthDeclared = (floor: number) =>
442
+ worthRunning(floor) && grade(floor) <= bound;
443
+ const floorOf = async (
444
+ i: number,
445
+ worth: (floor: number) => boolean,
446
+ ): Promise<number | null> => {
447
+ if (floors.has(i)) return floors.get(i)!;
448
+ const mech = mechanisms[i];
369
449
  const floor = meter
370
450
  ? await meter.time(
371
451
  `${mech.name}.floor`,
372
- () => mech.floor(ctx, query, pre, worthRunning),
452
+ () => mech.floor(ctx, query, pre, worth),
373
453
  )
374
- : await mech.floor(ctx, query, pre, worthRunning);
454
+ : await mech.floor(ctx, query, pre, worth);
375
455
  if (meter) {
376
456
  if (floor === null) meter.mechanismSkips++;
377
457
  else meter.mechanismFloors++;
378
458
  }
459
+ floors.set(i, floor);
460
+ return floor;
461
+ };
462
+ const runOf = async (i: number): Promise<MechanismResult[]> => {
463
+ let results = runs.get(i);
464
+ if (results === undefined) {
465
+ const mech = mechanisms[i];
466
+ if (meter) meter.mechanismRuns++;
467
+ results = meter
468
+ ? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
469
+ : await mech.run(ctx, query, pre);
470
+ runs.set(i, results);
471
+ }
472
+ return results;
473
+ };
474
+ const runAhead = async (mi: number) => {
475
+ const g = await probeGrade(mi);
476
+ if (g === null) return;
477
+ const ahead: Array<[number, number]> = [];
478
+ for (let j = mi + 1; j < mechanisms.length; j++) {
479
+ if (floors.has(j)) continue;
480
+ const gj = await probeGrade(j);
481
+ if (gj !== null && gj < g) ahead.push([gj, j]);
482
+ }
483
+ ahead.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
484
+ for (const [gj, j] of ahead) {
485
+ // Only what the declared order would also run: a bound that cannot beat
486
+ // what is already held is not even asked for its real floor.
487
+ if (
488
+ !(gj < Math.min(best === null ? Infinity : grade(best.weight), bound))
489
+ ) {
490
+ continue;
491
+ }
492
+ const floor = await floorOf(j, worthAhead);
493
+ if (floor === null || !worthAhead(floor)) continue;
494
+ ctx.trace?.step(
495
+ "runAhead",
496
+ [],
497
+ [],
498
+ `${mechanisms[j].name} runs ahead of ${
499
+ mechanisms[mi].name
500
+ } — its floor (grade ${grade(floor)}) is below ${
501
+ mechanisms[mi].name
502
+ }'s (grade ${g}), so its result bounds what ${
503
+ mechanisms[mi].name
504
+ } could win`,
505
+ );
506
+ for (const r of await runOf(j)) {
507
+ // `consider` drops an empty answer, so it bounds nothing.
508
+ if (r.bytes.length === 0) continue;
509
+ bound = Math.min(bound, grade(weigh(r.accounted, r.moves)));
510
+ }
511
+ }
512
+ };
513
+ for (let mi = 0; mi < mechanisms.length; mi++) {
514
+ const mech = mechanisms[mi];
515
+ if (mi > 0) {
516
+ await runAhead(mi);
517
+ reportRegime();
518
+ }
519
+ const floor = await floorOf(mi, worthDeclared);
379
520
  if (floor === null) {
380
521
  ctx.trace?.step(
381
522
  "skipMechanism",
@@ -385,6 +526,16 @@ export async function think(
385
526
  );
386
527
  continue;
387
528
  }
529
+ if (grade(floor) > bound) {
530
+ if (meter) meter.mechanismsBounded++;
531
+ ctx.trace?.step(
532
+ "skipMechanism",
533
+ [],
534
+ [],
535
+ `${mech.name} skipped — floor ${floor} cannot beat grade ${bound}, already reached by a mechanism run ahead`,
536
+ );
537
+ continue;
538
+ }
388
539
  if (!worthRunning(floor)) {
389
540
  ctx.trace?.step(
390
541
  "skipMechanism",
@@ -396,10 +547,7 @@ export async function think(
396
547
  );
397
548
  continue;
398
549
  }
399
- if (meter) meter.mechanismRuns++;
400
- const results = meter
401
- ? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
402
- : await mech.run(ctx, query, pre);
550
+ const results = await runOf(mi);
403
551
  for (const r of results) {
404
552
  // ONE FORMULA, EVERY CANDIDATE: the chart's derivation reports how many
405
553
  // discrete moves it made and which bytes it could not recognise; the
@@ -544,25 +692,34 @@ export async function think(
544
692
  // without evidence stays owed, and a later transition pays it only by carrying
545
693
  // it (the law reads the window; see derivation.ts). Same reading, one
546
694
  // definition — not a second spelling of it here.
547
- const explained: Array<[number, number]> = [
695
+ const priced: Array<[number, number]> = [
548
696
  ...decided.accounted,
549
697
  ...pre.computed.map((u): [number, number] => [u.i, u.j]),
550
- ].filter(([a, b]) =>
698
+ ];
699
+ const explained = priced.filter(([a, b]) =>
551
700
  windowOf([a, b], answer, query, ctx.space.maxGroup) !== null
552
701
  );
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(
702
+ // THE DERIVATION IS BORN OWING ITS DISCRIMINATIVE MATERIAL. The bytes a
703
+ // corpus-global scaffolding window reaches (traverse.ts, `scaffoldExtents`)
704
+ // are nobody's debt — otherwise a later step could claim to pay them by
705
+ // restating ` is `, which every fact holds, and the law (which measures
706
+ // carrying by any window of what is owed) would admit it; the substitution
707
+ // bridge reads gaps the same way (`explainedSpan`). PRICING is untouched:
708
+ // the ladder still charges every unexplained byte, because for a question
709
+ // made of nothing but scaffolding (`How are you today?`) covering those bytes
710
+ // IS the evidence.
711
+ const scaffold = scaffoldExtents(ctx, query);
712
+ const paid = remainderOf(
557
713
  query.length,
558
- [
559
- ...decided.accounted,
560
- ...pre.computed.map((u): [number, number] => [u.i, u.j]),
561
- ],
714
+ [...explained, ...scaffold],
562
715
  ctx.space.maxGroup,
563
716
  );
564
- const paid = remainderOf(query.length, explained, ctx.space.maxGroup);
717
+ // WHAT THE CONSTRUCTION WITHHOLDS, at or above one quantum: the difference between
718
+ // the remainder paid in full and the remainder paid by carrying. Both readings
719
+ // are the law's, so the floor is applied once and in one place — and the
720
+ // paid-in-full reading is computed only when a meter will read it.
565
721
  if (ctx.meter) {
722
+ const paidInFull = remainderOf(query.length, priced, ctx.space.maxGroup);
566
723
  ctx.meter.groundingWithheldBytes += unaccountedBytes(paid) -
567
724
  unaccountedBytes(paidInFull);
568
725
  }
@@ -577,8 +734,31 @@ export async function think(
577
734
  const uncovered = state.remainder;
578
735
 
579
736
  // ── Post-grounding, gated by the declaration and the remainder ────────
737
+ // WHAT THE GROUNDING SPOKE FOR, when it did not declare it: the forms inside
738
+ // the answer that the ASKER already holds. An answer is the asker's material
739
+ // plus what the corpus added — the fact's own value — and only the former was
740
+ // spoken for: re-entering it walks back to the question. The latter is what a
741
+ // chain continues THROUGH. Consuming every form recognised in the answer
742
+ // consumed that too, so a chain could start only where recognition happened
743
+ // to miss the next entity — measured on 133 held-out 2Wiki compositional
744
+ // questions, 3 pivot steps in all once interior recognition named the entity
745
+ // inside every fact. Witnessed exactly, as `chooseNext`'s exact tier reads the
746
+ // question (evidence.ts); a form below one window cannot be witnessed, and is
747
+ // consumed as before.
748
+ const asked = ctx._edgeAsked;
580
749
  const preConsumed = declaredUsed ??
581
- new Set(recognise(ctx, answer).sites.map((s) => s.payload));
750
+ new Set(
751
+ recognise(ctx, answer).sites
752
+ .filter((s) =>
753
+ asked === null || s.end - s.start < ctx.space.maxGroup ||
754
+ witness(
755
+ offsetCanon(ctx, answer.subarray(s.start, s.end)),
756
+ [asked.index],
757
+ ctx.space.maxGroup,
758
+ ).complete
759
+ )
760
+ .map((s) => s.payload),
761
+ );
582
762
  // A grounding that DECLARED itself complete is not extended: the answer is
583
763
  // already a trained form's own continuation, reached through an identity
584
764
  // claim about the query, so a multi-hop pivot could only chain past the
@@ -607,8 +787,17 @@ export async function think(
607
787
  // `preConsumed` is derived by re-recognising the answer — "everything in
608
788
  // it", not "what it voiced" — and a containment rule over that would
609
789
  // suppress every pivot the answer legitimately contains.
790
+ //
791
+ // …and a continuation the ANSWER ITSELF holds was voiced, not withheld. A
792
+ // substitution that answers with its anchor's own continuation (`The director
793
+ // of Beat Girl is Edmond T. Gréville.`) declared that anchor used, so reading
794
+ // its continuations as withheld refused every pivot inside the answer — the
795
+ // chain could never step past the entity the first hop introduced.
610
796
  const voiced = declaredUsed === undefined ? [] : [...declaredUsed].flatMap(
611
- (id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)),
797
+ (id) =>
798
+ ctx.store.nextFirst(id, hubBound(ctx))
799
+ .map((n) => read(ctx, n))
800
+ .filter((v) => indexOf(answer, v, 0) < 0),
612
801
  );
613
802
  // WHAT THIS BRANCH READ, published where it was read. Post-grounding decides
614
803
  // by the DECLARATION (`decided.used`, which becomes `voiced`), by what the
@@ -664,89 +853,59 @@ export async function think(
664
853
  meter.postGroundingRemainderSpans += uncovered.length;
665
854
  meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
666
855
  }
667
- // THE WALK THAT DOES NOT RUN, NAMED — the audit's point 1 could not attribute
668
- // real questions that stopped with no note anywhere. They never reached the
669
- // offer: `decided.complete` says the grounding supplied a fixed point, so the
670
- // walk is skipped BY DESIGN and the state is the grounding's own. That state
671
- // carries `fixed: true` (the trace already reports it), and the law's first
672
- // clause refuses any continuation against it — so there is no `null` here to
673
- // read as exhaustion, and the `Offer` contract is not in play at all. The
674
- // silent stop is therefore a NAMED state, not a gap.
856
+ // ── THE CLOSURE ENGINE ───────────────────────────────────────────────
675
857
  //
676
- // THE WALK CONSUMES AND RETURNS A STATE. It is handed the derivation's own —
677
- // the grounding's product, accounting, remainder and cost — and hands back the
678
- // state it advanced to, so what follows reads a state rather than bytes plus a
679
- // tuple rebuilt here. A supplied fixed point is the one case where the walk
680
- // does not run at all, and then the state is the grounding's own.
681
- const extension = decided.complete ? undefined : meter
682
- ? await meter.time(
683
- "reason",
684
- () => reason(ctx, query, state, preConsumed, pre, voiced),
685
- )
686
- : await reason(ctx, query, state, preConsumed, pre, voiced);
687
- const reasoned = extension ?? state;
688
-
689
- // Fuse only when the query has a genuine REMAINDER no mechanism's
690
- // structural evidence touched at all. `decided.accounted` alone
691
- // undercounts this: it is a COST-LADDER quantity (cover.ts prices its
692
- // masked/computed spans at near-zero and deliberately leaves them out of
693
- // `accounted` so PASS-bridged bytes are still charged), not a coverage
694
- // one — a query fully explained by one computed span plus bridged
695
- // connectors can report `accounted: []` while nothing is actually left
696
- // unexplained. The genuine remainder is what NEITHER the winning
697
- // candidate's accounted spans NOR any recognised extension's computed
698
- // span (`pre.computed` — every mechanism's parse() output, ALU included)
699
- // ever touched. A remainder under one river-fold quantum (W, the same
700
- // floor cover.ts's restatedSpan and the honesty-density bar above both
701
- // use) is bridging punctuation/whitespace, never a second topic —
702
- // observed: a single space between two fully-computed arithmetic spans
703
- // ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
704
- // corpus fact, corrupting "4 6" into "4 63".
705
- // THE GATE ASKS THE LAW, and that is an OPTIMISATION, not a tidy-up: the state
706
- // above ALREADY carries the remainder (`remainderOf`, per-span, with the W
707
- // floor applied), so asking it costs nothing, while the total this line used to
708
- // compute (`unaccounted(explained)`) was one more sum over the spans on every
709
- // response. The two readings are the same condition, not two: the ACCOUNTING
710
- // applies the same W floor the gate does, so a gap below one quantum never
711
- // survives into `explained` and the total cannot reach W without some single
712
- // gap reaching it. Measured over twelve constructions at W = 4 (test/136.3,
713
- // which pins the equivalence and both sides of it).
714
- // Whether the winning candidate's entire recognised substance is
715
- // COMPUTED — every accounted span exactly a pre.computed span, nothing
716
- // from a genuinely recognised/climbed site. fuseAttention's lone-root
717
- // shortcut assumes a single point of attention already IS primary's own
718
- // source; that assumption is exactly backwards for a pure computation
719
- // (an ALU result has no anchor of its own) — see fuseAttention's
720
- // `unclimbed` parameter, gated there by Attention.breadth so a
721
- // coincidental echo (which this flag alone cannot distinguish) is still
722
- // rejected.
858
+ // The grounding's state is CLOSED under the law by two layers, in order: the
859
+ // multi-hop WALK (`walkLayer`: a forward absorb or a pivot, offered one at a
860
+ // time) and the multi-topic FUSION (`fusionLayer`: one composed transition).
861
+ // Every step either layer offers goes through the same `closure` and the same
862
+ // law; this function no longer sequences them or gates them by hand.
863
+ //
864
+ // WHAT USED TO BE TWO HAND-WRITTEN GATES IS NOW THE LAW'S, OR THE LAYER'S:
865
+ // • a declared-complete grounding (`fixed`) admits no transition — the
866
+ // engine enters no layer for it (the law's first clause), which is the
867
+ // walk-skip and the fusion-skip this branch used to spell separately;
868
+ // • a CLOSED derivation has nothing left for a second topic to account for,
869
+ // so the fusion layer does not ENGAGE — read off the state the walk
870
+ // reached, the one fusion is actually offered against.
871
+ //
872
+ // What the fusion needs from the grounding — whether its substance is purely
873
+ // computed (`unclimbed`) and where it stands in the query (`primarySpans`) —
874
+ // is the grounding's own evidence, resolved here where both readings are in
875
+ // hand.
876
+ //
877
+ // Whether the winning candidate's entire recognised substance is COMPUTED —
878
+ // every accounted span exactly a pre.computed span, nothing from a genuinely
879
+ // recognised/climbed site. fuseAttention's lone-root shortcut assumes a
880
+ // single point of attention already IS primary's own source; that assumption
881
+ // is exactly backwards for a pure computation (an ALU result has no anchor of
882
+ // its own) — gated there by Attention.breadth so a coincidental echo (which
883
+ // this flag alone cannot distinguish) is still rejected.
723
884
  const unclimbed = state.accounted.length > 0 &&
724
885
  state.accounted.every(([i, j]) =>
725
886
  pre.computed.some((u) => u.i === i && u.j === j)
726
887
  );
727
- // Where the winning grounding stands in the query — fusion places primary
728
- // by it (see fuseAttention's `primarySpans`). `accounted` is the
729
- // cost-ladder read and is authoritative when non-empty; when it is empty
730
- // the grounding is a pure COMPUTATION, whose evidence is its computed span.
731
- // Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
732
- // read here for POSITION instead of for coverage — and resolved here, where
733
- // both readings are in hand, rather than inside fuseAttention.
888
+ // Where the winning grounding stands in the query — fusion places primary by
889
+ // it. `accounted` is the cost-ladder read and is authoritative when
890
+ // non-empty; when it is empty the grounding is a pure COMPUTATION, whose
891
+ // evidence is its computed span — the cost-ladder-vs-coverage distinction
892
+ // `explained` above draws, read here for POSITION instead of coverage.
734
893
  const primarySpans: ReadonlyArray<Span> = state.accounted.length > 0
735
894
  ? state.accounted
736
895
  : pre.computed.map((u): [number, number] => [u.i, u.j]);
737
- const fused = closed(state) ? reasoned : meter
738
- ? await meter.time(
739
- "fuse",
740
- () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans),
741
- )
742
- : await fuseAttention(
743
- ctx,
744
- query,
745
- reasoned,
746
- pre,
747
- unclimbed,
748
- primarySpans,
749
- );
896
+ const fused = await closeOver(
897
+ state,
898
+ query,
899
+ ctx.space.maxGroup,
900
+ [
901
+ walkLayer(ctx, query, preConsumed, pre, voiced),
902
+ {
903
+ ...fusionLayer(ctx, query, pre, unclimbed, primarySpans),
904
+ engages: (d: DerivationState) => !closed(d),
905
+ },
906
+ ],
907
+ meter ? (name, walk) => meter.time(name, walk) : undefined,
908
+ );
750
909
 
751
910
  done(
752
911
  fused.product,