@hviana/sema 0.8.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/AGENTS.md +11 -10
  2. package/README.md +17 -38
  3. package/dist/example/demo.js +85 -34
  4. package/dist/src/geometry.d.ts +0 -10
  5. package/dist/src/geometry.js +0 -12
  6. package/dist/src/meter.d.ts +11 -0
  7. package/dist/src/meter.js +11 -0
  8. package/dist/src/mind/articulation.js +1 -1
  9. package/dist/src/mind/attention.js +2 -1
  10. package/dist/src/mind/derivation.d.ts +201 -0
  11. package/dist/src/mind/derivation.js +327 -0
  12. package/dist/src/mind/graph-search.d.ts +2 -1
  13. package/dist/src/mind/graph-search.js +37 -15
  14. package/dist/src/mind/match.d.ts +2 -0
  15. package/dist/src/mind/match.js +2 -0
  16. package/dist/src/mind/mechanisms/alu.js +0 -2
  17. package/dist/src/mind/mechanisms/cast.d.ts +1 -5
  18. package/dist/src/mind/mechanisms/cast.js +15 -18
  19. package/dist/src/mind/mechanisms/confluence.d.ts +0 -3
  20. package/dist/src/mind/mechanisms/confluence.js +3 -9
  21. package/dist/src/mind/mechanisms/cover.js +17 -20
  22. package/dist/src/mind/mechanisms/extraction.d.ts +0 -1
  23. package/dist/src/mind/mechanisms/extraction.js +13 -8
  24. package/dist/src/mind/mechanisms/prefix-completion.js +0 -1
  25. package/dist/src/mind/mechanisms/recall.d.ts +0 -1
  26. package/dist/src/mind/mechanisms/recall.js +8 -9
  27. package/dist/src/mind/mechanisms/reference.js +3 -4
  28. package/dist/src/mind/pipeline-mechanism.d.ts +1 -4
  29. package/dist/src/mind/pipeline.js +106 -41
  30. package/dist/src/mind/rationale.d.ts +0 -11
  31. package/dist/src/mind/rationale.js +6 -32
  32. package/dist/src/mind/reasoning.d.ts +4 -30
  33. package/dist/src/mind/reasoning.js +183 -151
  34. package/dist/src/mind/types.js +6 -3
  35. package/docs/INDEX.md +23 -24
  36. package/docs/INVARIANTS.md +16 -17
  37. package/docs/architecture/bounded-reads.md +4 -4
  38. package/docs/architecture/closure.md +65 -0
  39. package/docs/architecture/commonality.md +27 -18
  40. package/docs/architecture/cost-model.md +5 -5
  41. package/docs/architecture/exact-vs-approximate.md +4 -4
  42. package/docs/architecture/factored-machinery.md +14 -14
  43. package/docs/architecture/mechanism-market.md +9 -9
  44. package/docs/architecture/meter.md +4 -5
  45. package/docs/architecture/store.md +2 -2
  46. package/docs/architecture/thresholds.md +1 -1
  47. package/docs/failures/tempting-but-wrong.md +11 -1
  48. package/docs/harness/gates.md +6 -6
  49. package/docs/mechanisms/cover.md +2 -2
  50. package/example/demo.ts +90 -37
  51. package/jsr.json +1 -1
  52. package/package.json +1 -1
  53. package/src/geometry.ts +0 -13
  54. package/src/meter.ts +11 -0
  55. package/src/mind/articulation.ts +0 -1
  56. package/src/mind/attention.ts +2 -1
  57. package/src/mind/derivation.ts +473 -0
  58. package/src/mind/graph-search.ts +37 -20
  59. package/src/mind/match.ts +2 -0
  60. package/src/mind/mechanisms/alu.ts +0 -2
  61. package/src/mind/mechanisms/cast.ts +17 -21
  62. package/src/mind/mechanisms/confluence.ts +3 -13
  63. package/src/mind/mechanisms/cover.ts +17 -20
  64. package/src/mind/mechanisms/extraction.ts +13 -9
  65. package/src/mind/mechanisms/prefix-completion.ts +0 -1
  66. package/src/mind/mechanisms/recall.ts +7 -9
  67. package/src/mind/mechanisms/reference.ts +2 -3
  68. package/src/mind/pipeline-mechanism.ts +1 -4
  69. package/src/mind/pipeline.ts +121 -46
  70. package/src/mind/rationale.ts +6 -36
  71. package/src/mind/reasoning.ts +208 -178
  72. package/src/mind/types.ts +5 -2
  73. package/test/133-the-decision-point-renders-the-state.test.mjs +204 -0
  74. package/test/134-the-law-explains-the-engines-own-refusal.test.mjs +237 -0
  75. package/test/135-one-law-any-producer.test.mjs +289 -0
  76. package/test/136-the-two-named-limits.test.mjs +205 -0
  77. package/test/137-the-law-lives-once-and-below.test.mjs +400 -0
  78. package/test/138-the-remainder-drains-only-what-a-move-declares.test.mjs +62 -0
  79. package/test/139-the-witness-is-engagement-not-explanation.test.mjs +51 -0
  80. package/test/140-irrelevant-supply-does-not-change-an-answer.test.mjs +48 -0
  81. package/test/141-the-question-is-paid-at-construction.test.mjs +98 -0
  82. package/test/36-already-answered-fusion.test.mjs +20 -2
  83. package/test/37-cluster-dispersion-fusion.test.mjs +30 -3
  84. package/test/38-reason-restate-guard.test.mjs +22 -2
  85. package/test/55-cost-meter.test.mjs +4 -1
@@ -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 { unaccountedBytes, 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
@@ -393,14 +401,16 @@ export async function think(
393
401
  ? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
394
402
  : await mech.run(ctx, query, pre);
395
403
  for (const r of results) {
396
- 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);
397
408
  consider({
398
409
  bytes: r.bytes,
399
410
  provenance: r.provenance ?? mech.provenance,
400
411
  weight,
401
412
  used: r.used,
402
413
  accounted: r.accounted,
403
- unexplained: r.unexplained,
404
414
  complete: r.complete,
405
415
  scaffolding: r.scaffolding,
406
416
  });
@@ -433,14 +443,21 @@ export async function think(
433
443
  : null;
434
444
  ctx.trace?.step(
435
445
  "decideGrounding",
436
- candidates.map((c) =>
437
- 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(
438
455
  c.bytes,
439
456
  `${c.provenance} (weight ${c.weight.toFixed(3)}${
440
- c.unexplained ? `, unexplained: "${c.unexplained}"` : ""
457
+ label ? `, unexplained: "${label}"` : ""
441
458
  })`,
442
- )
443
- ),
459
+ );
460
+ }),
444
461
  decided ? [rItem(decided.bytes, decided.provenance)] : [],
445
462
  "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder",
446
463
  undefined,
@@ -505,7 +522,61 @@ export async function think(
505
522
  const provenance = decided.provenance as Provenance;
506
523
  const declaredUsed = decided.used;
507
524
 
508
- // ── Post-grounding, gated by provenance ──────────────────────────────
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 ────────
509
580
  const preConsumed = declaredUsed ??
510
581
  new Set(recognise(ctx, answer).sites.map((s) => s.payload));
511
582
  // A grounding that DECLARED itself complete is not extended: the answer is
@@ -540,10 +611,13 @@ export async function think(
540
611
  (id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)),
541
612
  );
542
613
  // WHAT THIS BRANCH READ, published where it was read. Post-grounding decides
543
- // by `decided.used` and by the provenance NAME; the operands were invisible in
544
- // the trace, so a change to the branching could not be shown equivalent or
545
- // otherwise from outside — three separate investigations failed on exactly
546
- // that gap. A gap in instrumentation is a defect IN the instrumentation
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
547
621
  // (AGENTS.md §6): closed here, once, as counts only — never content.
548
622
  ctx.trace?.step(
549
623
  "postGrounding",
@@ -558,6 +632,14 @@ export async function think(
558
632
  usedDeclared: decided.used !== undefined,
559
633
  preConsumed: preConsumed.size,
560
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,
561
643
  },
562
644
  );
563
645
  // REPORTABLE, NOT SILENT. A declared-complete grounding ends the derivation
@@ -575,19 +657,6 @@ export async function think(
575
657
  "post-grounding extension is skipped",
576
658
  );
577
659
  }
578
- // THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
579
- // the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
580
- // with the same W floor. `accounted` is a COST quantity (measured: a query
581
- // fully explained by one computed span plus bridged connectors reports
582
- // `accounted: []` while nothing is unexplained), and a remainder under one
583
- // river-fold quantum is bridging punctuation, never a second topic — so it
584
- // licenses no extension and blocks none.
585
- const explained: Array<[number, number]> = [
586
- ...decided.accounted,
587
- ...pre.computed.map((u): [number, number] => [u.i, u.j]),
588
- ];
589
- const uncovered = unexplainedSpans(query.length, explained)
590
- .filter(([a, b]) => b - a >= ctx.space.maxGroup);
591
660
  // PUBLISHED, NOT RECOMPUTED: the same `uncovered` the gates below read. A
592
661
  // write-only accounting (meter contract 1), so the number that licenses an
593
662
  // extension or a fusion stops being invisible.
@@ -595,17 +664,18 @@ export async function think(
595
664
  meter.postGroundingRemainderSpans += uncovered.length;
596
665
  meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
597
666
  }
598
- // The extension is kept as a WHOLE (bytes + what it carried + how many steps),
599
- // not just its bytes: pricing it — `steps · STEP` against `PASS · unaccounted`
600
- // — is the caller's job, one comparison away. `reasoned` stays the bytes so
601
- // everything downstream is untouched.
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.
602
672
  const extension = decided.complete ? undefined : meter
603
673
  ? await meter.time(
604
674
  "reason",
605
- () => reason(ctx, query, answer, preConsumed, pre, voiced, uncovered),
675
+ () => reason(ctx, query, state, preConsumed, pre, voiced),
606
676
  )
607
- : await reason(ctx, query, answer, preConsumed, pre, voiced, uncovered);
608
- const reasoned = extension?.bytes ?? answer;
677
+ : await reason(ctx, query, state, preConsumed, pre, voiced);
678
+ const reasoned = extension ?? state;
609
679
 
610
680
  // Fuse only when the query has a genuine REMAINDER no mechanism's
611
681
  // structural evidence touched at all. `decided.accounted` alone
@@ -623,7 +693,15 @@ export async function think(
623
693
  // observed: a single space between two fully-computed arithmetic spans
624
694
  // ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
625
695
  // corpus fact, corrupting "4 6" into "4 63".
626
- 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).
627
705
  // Whether the winning candidate's entire recognised substance is
628
706
  // COMPUTED — every accounted span exactly a pre.computed span, nothing
629
707
  // from a genuinely recognised/climbed site. fuseAttention's lone-root
@@ -633,8 +711,8 @@ export async function think(
633
711
  // `unclimbed` parameter, gated there by Attention.breadth so a
634
712
  // coincidental echo (which this flag alone cannot distinguish) is still
635
713
  // rejected.
636
- const unclimbed = decided.accounted.length > 0 &&
637
- decided.accounted.every(([i, j]) =>
714
+ const unclimbed = state.accounted.length > 0 &&
715
+ state.accounted.every(([i, j]) =>
638
716
  pre.computed.some((u) => u.i === i && u.j === j)
639
717
  );
640
718
  // Where the winning grounding stands in the query — fusion places primary
@@ -644,13 +722,10 @@ export async function think(
644
722
  // Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
645
723
  // read here for POSITION instead of for coverage — and resolved here, where
646
724
  // both readings are in hand, rather than inside fuseAttention.
647
- const primarySpans: ReadonlyArray<[number, number]> =
648
- decided.accounted.length > 0
649
- ? decided.accounted
650
- : pre.computed.map((u): [number, number] => [u.i, u.j]);
651
- const fused = remainder < ctx.space.maxGroup
652
- ? reasoned
653
- : 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
654
729
  ? await meter.time(
655
730
  "fuse",
656
731
  () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans),
@@ -665,7 +740,7 @@ export async function think(
665
740
  );
666
741
 
667
742
  done(
668
- fused,
743
+ fused.product,
669
744
  // NO CLAIM ABOUT FUSION HERE. `fuseAttention` is entered whenever a
670
745
  // remainder ≥ W exists and returns early when there is nothing to bridge, so
671
746
  // this note used to assert a fusion that frequently did not happen (measured:
@@ -674,5 +749,5 @@ export async function think(
674
749
  // did the work is the layer that says so.
675
750
  "grounded, reasoned forward",
676
751
  );
677
- return { bytes: fused, provenance };
752
+ return { bytes: fused.product, provenance };
678
753
  }
@@ -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
  *
@@ -112,42 +113,11 @@ export function decodeText(bytes: Uint8Array): string {
112
113
  return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
113
114
  }
114
115
 
115
- /** The BYTE COUNT of the complement — what the currency calls `unaccounted`
116
- * in `weight = moves + PASS·unaccounted`. It lives here, beside the function
117
- * that produces the gaps, because the price's second term has ONE definition:
118
- * this was four copies of the same `reduce` (two in reasoning.ts, two in
119
- * pipeline.ts) before the architecture audit of `../auditoria-arquitectura-sema.md`
120
- * collapsed them. Same value at every site — the control diff is identical. */
121
- export function unaccountedBytes(
122
- spans: ReadonlyArray<readonly [number, number]>,
123
- ): number {
124
- let total = 0;
125
- for (const [a, b] of spans) total += b - a;
126
- return total;
127
- }
128
-
129
- /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
130
- * the same union-of-spans reading think's grounding decider prices at PASS
131
- * per byte, exposed here so a mechanism can turn it into a human label. */
132
- export function unexplainedSpans(
133
- queryLen: number,
134
- accounted: ReadonlyArray<[number, number]>,
135
- ): Array<[number, number]> {
136
- const sorted = accounted
137
- .map(([s, e]) =>
138
- [Math.max(0, s), Math.min(queryLen, e)] as [number, number]
139
- )
140
- .filter(([s, e]) => e > s)
141
- .sort((a, b) => a[0] - b[0]);
142
- const gaps: Array<[number, number]> = [];
143
- let reach = 0;
144
- for (const [s, e] of sorted) {
145
- if (s > reach) gaps.push([reach, s]);
146
- if (e > reach) reach = e;
147
- }
148
- if (reach < queryLen) gaps.push([reach, queryLen]);
149
- return gaps;
150
- }
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.
151
121
 
152
122
  /** A human-readable label for the query bytes a mechanism's `accounted`
153
123
  * spans leave unexplained — purely diagnostic (Task 2's negative evidence):