@hviana/sema 0.8.9 → 0.9.1

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 (111) 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 +27 -0
  14. package/dist/src/meter.js +28 -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/graph-search.d.ts +89 -15
  22. package/dist/src/mind/graph-search.js +345 -174
  23. package/dist/src/mind/learning.js +1 -1
  24. package/dist/src/mind/mechanisms/cover.d.ts +19 -3
  25. package/dist/src/mind/mechanisms/cover.js +101 -58
  26. package/dist/src/mind/mechanisms/recall.js +0 -1
  27. package/dist/src/mind/mind.js +2 -2
  28. package/dist/src/mind/pipeline.d.ts +5 -1
  29. package/dist/src/mind/pipeline.js +175 -87
  30. package/dist/src/mind/primitives.d.ts +25 -5
  31. package/dist/src/mind/primitives.js +107 -44
  32. package/dist/src/mind/reasoning.d.ts +18 -4
  33. package/dist/src/mind/reasoning.js +445 -321
  34. package/dist/src/mind/recognition.js +55 -73
  35. package/dist/src/mind/resonance.js +1 -11
  36. package/dist/src/mind/traverse.d.ts +3 -3
  37. package/dist/src/mind/traverse.js +3 -3
  38. package/dist/src/mind/types.d.ts +7 -1
  39. package/dist/src/store-sqlite.d.ts +25 -0
  40. package/dist/src/store-sqlite.js +89 -1
  41. package/dist/src/store.d.ts +48 -4
  42. package/dist/src/store.js +86 -6
  43. package/docs/INDEX.md +18 -18
  44. package/docs/INVARIANTS.md +16 -16
  45. package/docs/architecture/bounded-reads.md +1 -1
  46. package/docs/architecture/caches.md +5 -4
  47. package/docs/architecture/closure.md +45 -5
  48. package/docs/architecture/cost-model.md +16 -0
  49. package/docs/architecture/factored-machinery.md +14 -13
  50. package/docs/architecture/fold-contract.md +51 -1
  51. package/docs/architecture/mechanism-market.md +21 -0
  52. package/docs/architecture/memoization.md +3 -3
  53. package/docs/architecture/meter.md +2 -1
  54. package/docs/architecture/saturation.md +12 -0
  55. package/docs/architecture/store.md +25 -2
  56. package/docs/failures/tempting-but-wrong.md +13 -2
  57. package/docs/harness/gates.md +12 -10
  58. package/docs/mechanisms/cover.md +23 -6
  59. package/jsr.json +1 -1
  60. package/package.json +1 -1
  61. package/src/alu/README.md +10 -2
  62. package/src/alu/src/index.ts +1 -0
  63. package/src/alu/src/parser.ts +6 -6
  64. package/src/alu/src/resonance.ts +42 -0
  65. package/src/alu/test/alu.test.ts +40 -0
  66. package/src/bytes.ts +13 -3
  67. package/src/canon.ts +40 -0
  68. package/src/geometry.ts +183 -154
  69. package/src/meter.ts +28 -1
  70. package/src/mind/articulation.ts +14 -2
  71. package/src/mind/attention.ts +47 -25
  72. package/src/mind/bridge.ts +3 -3
  73. package/src/mind/derivation.ts +77 -0
  74. package/src/mind/graph-search.ts +449 -221
  75. package/src/mind/learning.ts +1 -7
  76. package/src/mind/match.ts +1 -2
  77. package/src/mind/mechanisms/cast.ts +1 -2
  78. package/src/mind/mechanisms/cover.ts +149 -84
  79. package/src/mind/mechanisms/extraction.ts +1 -2
  80. package/src/mind/mechanisms/prefix-completion.ts +1 -1
  81. package/src/mind/mechanisms/recall.ts +1 -3
  82. package/src/mind/mechanisms/reference.ts +1 -1
  83. package/src/mind/mind.ts +5 -30
  84. package/src/mind/pipeline.ts +206 -102
  85. package/src/mind/primitives.ts +119 -43
  86. package/src/mind/reasoning.ts +558 -413
  87. package/src/mind/recognition.ts +49 -65
  88. package/src/mind/resonance.ts +2 -16
  89. package/src/mind/trace.ts +1 -1
  90. package/src/mind/traverse.ts +3 -3
  91. package/src/mind/types.ts +9 -11
  92. package/src/store-sqlite.ts +92 -1
  93. package/src/store.ts +113 -7
  94. package/test/105-derive-through-reports-its-refusal.test.mjs +8 -5
  95. package/test/106-the-join-fires.test.mjs +21 -0
  96. package/test/111-the-cover-assembly-is-counted.test.mjs +8 -5
  97. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +18 -12
  98. package/test/136-the-two-named-limits.test.mjs +3 -2
  99. package/test/137-the-law-lives-once-and-below.test.mjs +21 -0
  100. package/test/148-exact-shortcuts-agree.test.mjs +188 -0
  101. package/test/149-the-closure-engine.test.mjs +138 -0
  102. package/test/150-the-join-is-output-sensitive.test.mjs +66 -0
  103. package/test/151-the-cover-pays-for-what-it-reaches.test.mjs +142 -0
  104. package/test/152-the-read-side-names-as-the-write-side.test.mjs +146 -0
  105. package/test/153-a-cheaper-bound-is-looked-at-first.test.mjs +155 -0
  106. package/test/24-generalization.test.mjs +32 -0
  107. package/test/36-bloom.test.mjs +53 -0
  108. package/test/37-cluster-dispersion-fusion.test.mjs +75 -0
  109. package/test/48-recognise-turn-connective.test.mjs +3 -2
  110. package/test/55-cost-meter.test.mjs +4 -4
  111. 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,
@@ -27,7 +28,11 @@ import {
27
28
  import { rItem } from "./trace.js";
28
29
  import { unexplainedLabel } from "./rationale.js";
29
30
  import { hubBound } from "./traverse.js";
30
- import { type PipelineMechanism, Precomputed } from "./pipeline-mechanism.js";
31
+ import {
32
+ type MechanismResult,
33
+ type PipelineMechanism,
34
+ Precomputed,
35
+ } from "./pipeline-mechanism.js";
31
36
  import { coverMechanism } from "./mechanisms/cover.js";
32
37
  import { castMechanism } from "./mechanisms/cast.js";
33
38
  import { confluenceMechanism } from "./mechanisms/confluence.js";
@@ -38,7 +43,7 @@ import { recallMechanism } from "./mechanisms/recall.js";
38
43
 
39
44
  // Re-exports: cover's pre-resolution helpers and the ALU adapter kept
40
45
  // importable from the pipeline module (their historical home).
41
- export { resolveConcepts, resolveConnectors } from "./mechanisms/cover.js";
46
+ export { offerConcepts, offerConnectors } from "./mechanisms/cover.js";
42
47
  export { aluToMechanism } from "./mechanisms/alu.js";
43
48
 
44
49
  // ── Extension dispatch (pre-loop parse) ─────────────────────────────────────
@@ -161,6 +166,10 @@ export interface RegimePredictionData {
161
166
  * CAST's floor) — the bar the incumbent must sit at or below for the
162
167
  * consensus climb to be skipped. */
163
168
  climbFloorGrade: number;
169
+ /** The lowest grade reached by a mechanism run AHEAD of a dearer one (see
170
+ * the grounding loop), when one ran and grounded: a bound below
171
+ * `climbFloorGrade` makes the regime retrieval whatever the incumbent. */
172
+ boundGrade?: number;
164
173
  }
165
174
 
166
175
  /** Think: a single lightest-derivation exploration of the Sema graph.
@@ -335,7 +344,7 @@ export async function think(
335
344
  // below.
336
345
  const incumbent = best as Candidate | null;
337
346
  const incumbentGrade = incumbent === null ? null : grade(incumbent.weight);
338
- const regime: "retrieval" | "composition" = worthRunning(2 * STEP)
347
+ const regime: "retrieval" | "composition" = worthDeclared(2 * STEP)
339
348
  ? "composition"
340
349
  : "retrieval";
341
350
  ctx.trace?.step(
@@ -343,8 +352,10 @@ export async function think(
343
352
  [rItem(query, "query")],
344
353
  [],
345
354
  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`
355
+ ? (incumbentGrade !== null && incumbentGrade <= climbFloorGrade
356
+ ? `retrieval regime — incumbent grade ${incumbentGrade} ≤ climb floor ${climbFloorGrade}, `
357
+ : `retrieval regime — grade ${bound} already reached by a mechanism run ahead, below climb floor ${climbFloorGrade}, `) +
358
+ `so no mechanism floored above that grade runs; CAST will not climb`
348
359
  : `composition regime — ${
349
360
  incumbentGrade === null
350
361
  ? "no incumbent (nothing grounded)"
@@ -356,6 +367,7 @@ export async function think(
356
367
  regime,
357
368
  incumbentGrade,
358
369
  climbFloorGrade,
370
+ ...(bound !== Infinity ? { boundGrade: bound } : {}),
359
371
  } satisfies RegimePredictionData,
360
372
  );
361
373
  };
@@ -363,19 +375,139 @@ export async function think(
363
375
  // Per-mechanism accounting (src/meter.ts). The market's whole premise is
364
376
  // that mechanisms compete on one cost scale — so the profiling read-out is
365
377
  // 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();
378
+ //
379
+ // A CHEAPER BOUND IS LOOKED AT BEFORE A DEARER ONE IS PAID FOR.
380
+ //
381
+ // The declared order is the tie-break priority, and the pruning above is
382
+ // only as strong as the incumbent it has: a mechanism floored LOWER than the
383
+ // one about to invest, but declared after it, could not prune it. Measured
384
+ // on the 31.7M-node store: a lowercased dialogue turn (#97 of the battery)
385
+ // was won by recall at grade 1 — after CAST (floor grade 2) had paid the
386
+ // consensus climb and the weave, confluence (3) a reach climb, and extraction
387
+ // and reference their reads: ~10 s of a 12.6 s response, for candidates that
388
+ // could not win.
389
+ //
390
+ // So before mechanism `m` first-touches anything, every LATER mechanism whose
391
+ // bound is strictly lower runs AHEAD of it, cheapest bound first, and the
392
+ // lowest grade any of them reaches becomes `bound`. A mechanism whose floor
393
+ // grade exceeds `bound` is then skipped. THE DECISION IS UNCHANGED — the
394
+ // same candidate wins as in the declared order:
395
+ // • a mechanism `p` run ahead with best grade g bounds the final grade by
396
+ // g: in the declared order p either runs (its candidate is weighed) or is
397
+ // pruned by an incumbent already at or below p's floor ≤ g;
398
+ // • so a mechanism floored above `bound` has only candidates the final
399
+ // winner strictly outgrades — and with every candidate above `bound`
400
+ // dropped, every mechanism floored at or below it meets the same
401
+ // run-or-prune decision (`f < incumbent` iff `f < min(incumbent,
402
+ // bound + 1)` for f ≤ bound) and yields the same candidates;
403
+ // • and the winner is chosen from the candidates at or below `bound`, in
404
+ // declared order — `consider` replays them where they are declared.
405
+ // Equal-grade floors are NOT skipped (≤, not <): a mechanism declared earlier
406
+ // keeps the tie it would have won. Running `p` ahead is never extra work:
407
+ // what prunes p in the declared order is a candidate at or below p's floor,
408
+ // which only a mechanism floored at or below it can produce — and every such
409
+ // mechanism is either already run or run ahead of p.
410
+ //
411
+ // The bound is learnt by asking `floor` with a `worthRunning` that refuses:
412
+ // under the investment discipline (pipeline-mechanism.ts) a floor that cannot
413
+ // pay returns its bound UNINVESTED, so the question costs no analysis.
414
+ const refuse = () => false;
415
+ const probed: Array<number | null | undefined> = new Array(
416
+ mechanisms.length,
417
+ );
418
+ const probeGrade = async (i: number): Promise<number | null> => {
419
+ if (probed[i] === undefined) {
420
+ const f = await mechanisms[i].floor(ctx, query, pre, refuse);
421
+ probed[i] = f === null ? null : grade(f);
422
+ }
423
+ return probed[i]!;
424
+ };
425
+ /** Per mechanism: its floor once computed, and its results once run. */
426
+ const floors = new Map<number, number | null>();
427
+ const runs = new Map<number, MechanismResult[]>();
428
+ let bound = Infinity;
429
+ const worthAhead = (floor: number) =>
430
+ grade(floor) <
431
+ Math.min(best === null ? Infinity : grade(best.weight), bound);
432
+ const worthDeclared = (floor: number) =>
433
+ worthRunning(floor) && grade(floor) <= bound;
434
+ const floorOf = async (
435
+ i: number,
436
+ worth: (floor: number) => boolean,
437
+ ): Promise<number | null> => {
438
+ if (floors.has(i)) return floors.get(i)!;
439
+ const mech = mechanisms[i];
369
440
  const floor = meter
370
441
  ? await meter.time(
371
442
  `${mech.name}.floor`,
372
- () => mech.floor(ctx, query, pre, worthRunning),
443
+ () => mech.floor(ctx, query, pre, worth),
373
444
  )
374
- : await mech.floor(ctx, query, pre, worthRunning);
445
+ : await mech.floor(ctx, query, pre, worth);
375
446
  if (meter) {
376
447
  if (floor === null) meter.mechanismSkips++;
377
448
  else meter.mechanismFloors++;
378
449
  }
450
+ floors.set(i, floor);
451
+ return floor;
452
+ };
453
+ const runOf = async (i: number): Promise<MechanismResult[]> => {
454
+ let results = runs.get(i);
455
+ if (results === undefined) {
456
+ const mech = mechanisms[i];
457
+ if (meter) meter.mechanismRuns++;
458
+ results = meter
459
+ ? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
460
+ : await mech.run(ctx, query, pre);
461
+ runs.set(i, results);
462
+ }
463
+ return results;
464
+ };
465
+ const runAhead = async (mi: number) => {
466
+ const g = await probeGrade(mi);
467
+ if (g === null) return;
468
+ const ahead: Array<[number, number]> = [];
469
+ for (let j = mi + 1; j < mechanisms.length; j++) {
470
+ if (floors.has(j)) continue;
471
+ const gj = await probeGrade(j);
472
+ if (gj !== null && gj < g) ahead.push([gj, j]);
473
+ }
474
+ ahead.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
475
+ for (const [gj, j] of ahead) {
476
+ // Only what the declared order would also run: a bound that cannot beat
477
+ // what is already held is not even asked for its real floor.
478
+ if (
479
+ !(gj < Math.min(best === null ? Infinity : grade(best.weight), bound))
480
+ ) {
481
+ continue;
482
+ }
483
+ const floor = await floorOf(j, worthAhead);
484
+ if (floor === null || !worthAhead(floor)) continue;
485
+ ctx.trace?.step(
486
+ "runAhead",
487
+ [],
488
+ [],
489
+ `${mechanisms[j].name} runs ahead of ${
490
+ mechanisms[mi].name
491
+ } — its floor (grade ${grade(floor)}) is below ${
492
+ mechanisms[mi].name
493
+ }'s (grade ${g}), so its result bounds what ${
494
+ mechanisms[mi].name
495
+ } could win`,
496
+ );
497
+ for (const r of await runOf(j)) {
498
+ // `consider` drops an empty answer, so it bounds nothing.
499
+ if (r.bytes.length === 0) continue;
500
+ bound = Math.min(bound, grade(weigh(r.accounted, r.moves)));
501
+ }
502
+ }
503
+ };
504
+ for (let mi = 0; mi < mechanisms.length; mi++) {
505
+ const mech = mechanisms[mi];
506
+ if (mi > 0) {
507
+ await runAhead(mi);
508
+ reportRegime();
509
+ }
510
+ const floor = await floorOf(mi, worthDeclared);
379
511
  if (floor === null) {
380
512
  ctx.trace?.step(
381
513
  "skipMechanism",
@@ -385,6 +517,16 @@ export async function think(
385
517
  );
386
518
  continue;
387
519
  }
520
+ if (grade(floor) > bound) {
521
+ if (meter) meter.mechanismsBounded++;
522
+ ctx.trace?.step(
523
+ "skipMechanism",
524
+ [],
525
+ [],
526
+ `${mech.name} skipped — floor ${floor} cannot beat grade ${bound}, already reached by a mechanism run ahead`,
527
+ );
528
+ continue;
529
+ }
388
530
  if (!worthRunning(floor)) {
389
531
  ctx.trace?.step(
390
532
  "skipMechanism",
@@ -396,10 +538,7 @@ export async function think(
396
538
  );
397
539
  continue;
398
540
  }
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);
541
+ const results = await runOf(mi);
403
542
  for (const r of results) {
404
543
  // ONE FORMULA, EVERY CANDIDATE: the chart's derivation reports how many
405
544
  // discrete moves it made and which bytes it could not recognise; the
@@ -544,25 +683,20 @@ export async function think(
544
683
  // without evidence stays owed, and a later transition pays it only by carrying
545
684
  // it (the law reads the window; see derivation.ts). Same reading, one
546
685
  // definition — not a second spelling of it here.
547
- const explained: Array<[number, number]> = [
686
+ const priced: Array<[number, number]> = [
548
687
  ...decided.accounted,
549
688
  ...pre.computed.map((u): [number, number] => [u.i, u.j]),
550
- ].filter(([a, b]) =>
689
+ ];
690
+ const explained = priced.filter(([a, b]) =>
551
691
  windowOf([a, b], answer, query, ctx.space.maxGroup) !== null
552
692
  );
693
+ const paid = remainderOf(query.length, explained, ctx.space.maxGroup);
553
694
  // WHAT THE CONSTRUCTION WITHHOLDS, at or above one quantum: the difference between
554
695
  // 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);
696
+ // are the law's, so the floor is applied once and in one place — and the
697
+ // paid-in-full reading is computed only when a meter will read it.
565
698
  if (ctx.meter) {
699
+ const paidInFull = remainderOf(query.length, priced, ctx.space.maxGroup);
566
700
  ctx.meter.groundingWithheldBytes += unaccountedBytes(paid) -
567
701
  unaccountedBytes(paidInFull);
568
702
  }
@@ -664,89 +798,59 @@ export async function think(
664
798
  meter.postGroundingRemainderSpans += uncovered.length;
665
799
  meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
666
800
  }
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.
801
+ // ── THE CLOSURE ENGINE ───────────────────────────────────────────────
675
802
  //
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.
803
+ // The grounding's state is CLOSED under the law by two layers, in order: the
804
+ // multi-hop WALK (`walkLayer`: a forward absorb or a pivot, offered one at a
805
+ // time) and the multi-topic FUSION (`fusionLayer`: one composed transition).
806
+ // Every step either layer offers goes through the same `closure` and the same
807
+ // law; this function no longer sequences them or gates them by hand.
808
+ //
809
+ // WHAT USED TO BE TWO HAND-WRITTEN GATES IS NOW THE LAW'S, OR THE LAYER'S:
810
+ // • a declared-complete grounding (`fixed`) admits no transition — the
811
+ // engine enters no layer for it (the law's first clause), which is the
812
+ // walk-skip and the fusion-skip this branch used to spell separately;
813
+ // • a CLOSED derivation has nothing left for a second topic to account for,
814
+ // so the fusion layer does not ENGAGE — read off the state the walk
815
+ // reached, the one fusion is actually offered against.
816
+ //
817
+ // What the fusion needs from the grounding — whether its substance is purely
818
+ // computed (`unclimbed`) and where it stands in the query (`primarySpans`) —
819
+ // is the grounding's own evidence, resolved here where both readings are in
820
+ // hand.
821
+ //
822
+ // Whether the winning candidate's entire recognised substance is COMPUTED —
823
+ // every accounted span exactly a pre.computed span, nothing from a genuinely
824
+ // recognised/climbed site. fuseAttention's lone-root shortcut assumes a
825
+ // single point of attention already IS primary's own source; that assumption
826
+ // is exactly backwards for a pure computation (an ALU result has no anchor of
827
+ // its own) — gated there by Attention.breadth so a coincidental echo (which
828
+ // this flag alone cannot distinguish) is still rejected.
723
829
  const unclimbed = state.accounted.length > 0 &&
724
830
  state.accounted.every(([i, j]) =>
725
831
  pre.computed.some((u) => u.i === i && u.j === j)
726
832
  );
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.
833
+ // Where the winning grounding stands in the query — fusion places primary by
834
+ // it. `accounted` is the cost-ladder read and is authoritative when
835
+ // non-empty; when it is empty the grounding is a pure COMPUTATION, whose
836
+ // evidence is its computed span — the cost-ladder-vs-coverage distinction
837
+ // `explained` above draws, read here for POSITION instead of coverage.
734
838
  const primarySpans: ReadonlyArray<Span> = state.accounted.length > 0
735
839
  ? state.accounted
736
840
  : 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
- );
841
+ const fused = await closeOver(
842
+ state,
843
+ query,
844
+ ctx.space.maxGroup,
845
+ [
846
+ walkLayer(ctx, query, preConsumed, pre, voiced),
847
+ {
848
+ ...fusionLayer(ctx, query, pre, unclimbed, primarySpans),
849
+ engages: (d: DerivationState) => !closed(d),
850
+ },
851
+ ],
852
+ meter ? (name, walk) => meter.time(name, walk) : undefined,
853
+ );
750
854
 
751
855
  done(
752
856
  fused.product,