@hviana/sema 0.8.1 → 0.8.3

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 (114) hide show
  1. package/AGENTS.md +29 -29
  2. package/TRADEMARKS.md +0 -1
  3. package/dist/src/config.d.ts +28 -0
  4. package/dist/src/config.js +20 -0
  5. package/dist/src/geometry.d.ts +21 -0
  6. package/dist/src/geometry.js +21 -0
  7. package/dist/src/meter.d.ts +76 -0
  8. package/dist/src/meter.js +95 -0
  9. package/dist/src/mind/attention.d.ts +4 -0
  10. package/dist/src/mind/attention.js +165 -16
  11. package/dist/src/mind/canonical.d.ts +16 -0
  12. package/dist/src/mind/canonical.js +41 -0
  13. package/dist/src/mind/corpus.d.ts +40 -0
  14. package/dist/src/mind/corpus.js +149 -0
  15. package/dist/src/mind/graph-search.d.ts +7 -0
  16. package/dist/src/mind/graph-search.js +254 -24
  17. package/dist/src/mind/index.d.ts +3 -1
  18. package/dist/src/mind/index.js +1 -0
  19. package/dist/src/mind/match.d.ts +9 -4
  20. package/dist/src/mind/match.js +147 -61
  21. package/dist/src/mind/mechanisms/cast.js +19 -3
  22. package/dist/src/mind/mechanisms/confluence.js +24 -0
  23. package/dist/src/mind/mechanisms/cover.js +6 -0
  24. package/dist/src/mind/mechanisms/recall.js +32 -4
  25. package/dist/src/mind/mind.d.ts +57 -0
  26. package/dist/src/mind/mind.js +72 -1
  27. package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
  28. package/dist/src/mind/pipeline.js +66 -20
  29. package/dist/src/mind/primitives.js +9 -1
  30. package/dist/src/mind/rationale.d.ts +28 -1
  31. package/dist/src/mind/rationale.js +22 -1
  32. package/dist/src/mind/reasoning.d.ts +25 -3
  33. package/dist/src/mind/reasoning.js +125 -20
  34. package/dist/src/mind/recognition.js +4 -8
  35. package/dist/src/mind/resonance.js +20 -1
  36. package/dist/src/mind/trace.js +1 -0
  37. package/dist/src/mind/traverse.js +15 -3
  38. package/dist/src/mind/types.d.ts +49 -4
  39. package/docs/INVARIANTS.md +2 -2
  40. package/docs/architecture/bounded-reads.md +1 -1
  41. package/docs/architecture/commonality.md +2 -2
  42. package/docs/architecture/cost-model.md +2 -2
  43. package/docs/architecture/determinism.md +7 -7
  44. package/docs/architecture/match-project.md +2 -3
  45. package/docs/architecture/mechanism-market.md +10 -10
  46. package/docs/architecture/meter.md +5 -5
  47. package/docs/architecture/store.md +3 -3
  48. package/docs/failures/tempting-but-wrong.md +34 -6
  49. package/docs/harness/gates.md +2 -2
  50. package/docs/mechanisms/cast.md +2 -2
  51. package/docs/mechanisms/cover.md +2 -3
  52. package/docs/mechanisms/extraction.md +7 -7
  53. package/docs/mechanisms/recall.md +8 -9
  54. package/jsr.json +1 -1
  55. package/package.json +1 -1
  56. package/src/alu/README.md +11 -12
  57. package/src/config.ts +48 -0
  58. package/src/geometry.ts +21 -0
  59. package/src/meter.ts +98 -0
  60. package/src/mind/attention.ts +167 -16
  61. package/src/mind/canonical.ts +43 -0
  62. package/src/mind/corpus.ts +202 -0
  63. package/src/mind/graph-search.ts +277 -23
  64. package/src/mind/index.ts +8 -1
  65. package/src/mind/match.ts +148 -57
  66. package/src/mind/mechanisms/cast.ts +20 -2
  67. package/src/mind/mechanisms/confluence.ts +24 -0
  68. package/src/mind/mechanisms/cover.ts +5 -0
  69. package/src/mind/mechanisms/recall.ts +32 -4
  70. package/src/mind/mind.ts +125 -0
  71. package/src/mind/pipeline-mechanism.ts +7 -0
  72. package/src/mind/pipeline.ts +79 -22
  73. package/src/mind/primitives.ts +9 -1
  74. package/src/mind/rationale.ts +35 -1
  75. package/src/mind/reasoning.ts +145 -13
  76. package/src/mind/recognition.ts +4 -8
  77. package/src/mind/resonance.ts +19 -1
  78. package/src/mind/trace.ts +1 -0
  79. package/src/mind/traverse.ts +16 -6
  80. package/src/mind/types.ts +53 -4
  81. package/test/100-complete-grounding-trace.test.mjs +109 -0
  82. package/test/101-alignment-gap-bound.test.mjs +106 -0
  83. package/test/102-production-composes-at-scale.test.mjs +110 -0
  84. package/test/103-alignment-gap-budget.test.mjs +89 -0
  85. package/test/104-composition-is-reported.test.mjs +90 -0
  86. package/test/105-derive-through-reports-its-refusal.test.mjs +137 -0
  87. package/test/106-the-join-fires.test.mjs +94 -0
  88. package/test/107-the-join-is-counted.test.mjs +81 -0
  89. package/test/108-the-join-chains.test.mjs +78 -0
  90. package/test/109-the-pivot-is-counted.test.mjs +60 -0
  91. package/test/110-the-reasoner-stops-when-the-question-is-answered.test.mjs +91 -0
  92. package/test/111-the-cover-assembly-is-counted.test.mjs +74 -0
  93. package/test/112-the-exploration-does-not-grow-with-the-hub.test.mjs +89 -0
  94. package/test/113-the-rationale-payload-is-bounded.test.mjs +84 -0
  95. package/test/114-alignment-budget-is-per-sweep.test.mjs +93 -0
  96. package/test/116-the-extension-is-gated-by-the-pipelines-own-remainder.test.mjs +100 -0
  97. package/test/117-corpus-search.test.mjs +171 -0
  98. package/test/118-the-join-reaches-a-key-off-the-cut.test.mjs +74 -0
  99. package/test/119-the-work-does-not-grow-with-the-corpus.test.mjs +122 -0
  100. package/test/120-composition-is-consequence.test.mjs +132 -0
  101. package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +128 -0
  102. package/test/122-the-climb-search-does-not-grow-with-the-corpus.test.mjs +117 -0
  103. package/test/123-the-paired-formulas-agree.test.mjs +90 -0
  104. package/test/125-the-post-grounding-branch-publishes-its-operand.test.mjs +51 -0
  105. package/test/126-the-pipeline-does-not-name-mechanisms.test.mjs +42 -0
  106. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +83 -0
  107. package/test/129-the-trace-payload-shape.test.mjs +164 -0
  108. package/test/14-scaling.test.mjs +10 -7
  109. package/test/32-confluence.test.mjs +68 -0
  110. package/test/38-reason-restate-guard.test.mjs +8 -2
  111. package/test/43-cast-analog-seat.test.mjs +10 -0
  112. package/test/55-cost-meter.test.mjs +859 -0
  113. package/test/76-reference-binding.test.mjs +6 -1
  114. package/test/89-completion-recursion.test.mjs +30 -5
@@ -282,3 +282,862 @@ test("10. sumReports and formatReport aggregate a battery", async () => {
282
282
  assert.match(text, /perceptions/);
283
283
  await mind.store.close();
284
284
  });
285
+
286
+ test("10. the recompose descent is counted, and its zero is omitted", async () => {
287
+ // `recompleteNode` decomposes a completion by ITS OWN kids, and that descent
288
+ // was invisible: a caller could read the chain's result but not whether the
289
+ // recomposition ran, so "the recursion stopped" and "it never ran" looked the
290
+ // same from the counters alone.
291
+ //
292
+ // Measured on this fixture: the simple queries deepen (recompletes = 1) while
293
+ // "eva director country" is answered by the JOIN and never descends — the same
294
+ // store, one counter, both directions.
295
+ const store = new SQliteStore({ path: ":memory:" });
296
+ const mind = new Mind({ seed: 7, store, profile: true });
297
+ const F1 = "The director of Eva is Gustaf Molander.";
298
+ const F2 = "The country of Gustaf Molander is Sweden.";
299
+ await mind.ingest([
300
+ ["eva", F1],
301
+ ["eva director", F1],
302
+ ["gustaf molander", F2],
303
+ ["gustaf molander country", F2],
304
+ ]);
305
+
306
+ await mind.respondText("eva director");
307
+ const descended = mind.lastCost.counters.recompletes ?? 0;
308
+
309
+ await mind.respondText("eva director country");
310
+ const joined = mind.lastCost.counters;
311
+
312
+ await store.close();
313
+
314
+ assert.ok(
315
+ descended >= 1,
316
+ `the recompose descent must be counted (got ${descended}) — without it the ` +
317
+ `recursion is unobservable`,
318
+ );
319
+ assert.ok(
320
+ (joined.joinFired ?? 0) >= 1,
321
+ `the control query must be the join's, not the descent's (joinFired=${joined.joinFired})`,
322
+ );
323
+ assert.equal(
324
+ joined.recompletes,
325
+ undefined,
326
+ "a response that never descends must omit the counter (test 6's convention)",
327
+ );
328
+ });
329
+
330
+ test("11. the pivot's probe cap is visible: what it spent, and what it withheld", async () => {
331
+ // The cap itself is deliberate — probing every branch of a long answer made
332
+ // the pivot sweep the dominant ANN cost at corpus scale — but what it spent
333
+ // and what it withheld were both invisible. Untraced on purpose (meter.ts
334
+ // contract 1), so seeing them cannot perturb the search.
335
+ //
336
+ // The withheld count is a CAPACITY fact, never a verdict: the sweep is
337
+ // breadth-first (largest regions first) and recognition still contributes
338
+ // every exact containment candidate, so the answer below is asserted to be
339
+ // the same either way — if a future change makes the cap actually lose
340
+ // reach, this pair of assertions forces it to be reported as a loss.
341
+ const LONG =
342
+ "the capital of France is Paris and Paris is a city on the river Seine in " +
343
+ "the north of the country and the river runs through the heart of the city " +
344
+ "past the tower and the museums and the wide avenues of the old quarters";
345
+ const run = async (probeK) => {
346
+ const store = new SQliteStore({ path: ":memory:" });
347
+ const mind = new Mind({
348
+ seed: 7,
349
+ store,
350
+ profile: true,
351
+ // The pivot's budget is ITS OWN since F4: `recallQueryK` no longer widens
352
+ // it, which is exactly what this test now pins.
353
+ pivotProbeK: probeK,
354
+ });
355
+ await mind.ingest([
356
+ ["what is the capital of France", LONG],
357
+ ["Paris", "Paris is famous for the Eiffel Tower"],
358
+ ["the Eiffel Tower", "the Eiffel Tower is in Paris"],
359
+ ]);
360
+ const answer = String(
361
+ await mind.respondText("what is the capital of France famous for"),
362
+ ).trim();
363
+ const c = mind.lastCost.counters;
364
+ await store.close();
365
+ return {
366
+ answer,
367
+ probes: c.pivotProbes ?? 0,
368
+ // RAW on purpose: the "withholds nothing" case must be able to observe
369
+ // the field's ABSENCE (test 6's convention drops zeros), so coercing here
370
+ // would make the assertion below unable to fail.
371
+ withheld: c.pivotBranchesUnprobed,
372
+ pivots: c.pivotSteps ?? 0,
373
+ };
374
+ };
375
+
376
+ const wide = await run(64);
377
+ const tight = await run(12); // the default pivot budget
378
+
379
+ assert.ok(tight.probes > 0, "the sweep really ran");
380
+ assert.ok(
381
+ tight.withheld >= 1,
382
+ `the capacity the cap withheld must be counted (got ${tight.withheld})`,
383
+ );
384
+ assert.equal(
385
+ wide.withheld,
386
+ undefined,
387
+ "an allowance that probes every branch withholds nothing (zeros are dropped)",
388
+ );
389
+ assert.equal(tight.pivots, wide.pivots, "both runs took the same hop");
390
+ assert.equal(
391
+ tight.answer,
392
+ wide.answer,
393
+ "the cap withheld capacity, not the answer",
394
+ );
395
+ });
396
+
397
+ test("12. the extension's cost obeys the ladder's own inequality", async () => {
398
+ // The extension (reason()) is bounded by the material gate but was never
399
+ // PRICED. It now reports both facts — steps taken, and the bytes of
400
+ // uncovered material it was justified by — so the inequality the ladder
401
+ // would apply is checkable:
402
+ //
403
+ // steps · STEP < PASS · carried
404
+ //
405
+ // The gate accepts whenever a step carries a W-window, so the two agree for
406
+ // every extension with `steps ≤ (PASS/STEP) · carried`. This pins that
407
+ // inequality on a fixture where the extension really runs (the anti-vacuity
408
+ // guard below), and derives both constants from the ladder instead of
409
+ // spelling them out.
410
+ const { PASS, STEP } = await import("../dist/src/mind/graph-search.js");
411
+ const store = new SQliteStore({ path: ":memory:" });
412
+ const mind = new Mind({ seed: 7, store, profile: true });
413
+ await mind.ingest([
414
+ ["What is the capital of France", "The capital of France is Paris"],
415
+ ["Paris", "Paris is famous for the Eiffel Tower"],
416
+ ]);
417
+ await mind.respondText("What is the capital of France famous for");
418
+ const c = mind.lastCost.counters;
419
+ await store.close();
420
+
421
+ const steps = c.reasonSteps ?? 0;
422
+ const carried = c.reasonCarriedBytes ?? 0;
423
+ assert.ok(
424
+ steps >= 1,
425
+ `the extension must run for this to mean anything (got ${steps})`,
426
+ );
427
+ assert.ok(
428
+ carried >= 1,
429
+ `and it must be justified by real material (got ${carried})`,
430
+ );
431
+ assert.ok(
432
+ steps * STEP < PASS * carried,
433
+ `the extension spent ${steps} step(s) on ${carried} carried byte(s) — the ` +
434
+ `ladder would refuse that (STEP·${steps} vs PASS·${carried})`,
435
+ );
436
+ });
437
+
438
+ test("13. the fusion is counted when it FUSES, and claimed only then", async () => {
439
+ // `fuseAttention` is ENTERED whenever the query has a remainder ≥ W and
440
+ // returns `primary` untouched when there is nothing to bridge (`pieces.length
441
+ // === 1`). Entering and fusing are different facts — the rationale's own
442
+ // `enter` step fires on every call, so before `fuseRuns` the untraced view
443
+ // could not tell them apart — and the pipeline's outer note claimed the fusion
444
+ // REGARDLESS, measured false on 4 of the 5 queries below.
445
+ const CORPUS = [
446
+ ["What is the capital of France", "The capital of France is Paris"],
447
+ ["Paris", "Paris is famous for the Eiffel Tower"],
448
+ ["2+2", "2+2 equals 4"],
449
+ [
450
+ "The tallest tower in Paris",
451
+ "The tallest tower in Paris is the Eiffel Tower",
452
+ ],
453
+ ];
454
+ const run = async (q) => {
455
+ const store = new SQliteStore({ path: ":memory:" });
456
+ const mind = new Mind({ seed: 7, store, profile: true });
457
+ await mind.ingest(CORPUS);
458
+ const steps = [];
459
+ await mind.respondText(q, (s) => steps.push(s));
460
+ const c = mind.lastCost.counters;
461
+ await store.close();
462
+ const pipelineSteps = steps.filter((s) =>
463
+ String(s.note ?? "").includes("reasoned forward")
464
+ );
465
+ return {
466
+ fused: c.fuseRuns,
467
+ claims:
468
+ pipelineSteps.filter((s) =>
469
+ String(s.note ?? "").includes("fused across")
470
+ ).length,
471
+ };
472
+ };
473
+
474
+ // The one query that really fuses (measured: four entries, one fusion).
475
+ const fusing = await run("2+2 and the Eiffel Tower");
476
+ assert.ok(
477
+ (fusing.fused ?? 0) >= 1,
478
+ `a query that fuses must be counted (fuseRuns=${fusing.fused})`,
479
+ );
480
+
481
+ // The one that enters and bails — the anti-vacuity pair.
482
+ const bailing = await run("What is the capital of France famous for");
483
+ assert.equal(
484
+ bailing.fused,
485
+ undefined,
486
+ "a call that fused nothing must not be counted (zeros are dropped)",
487
+ );
488
+ assert.equal(
489
+ bailing.claims,
490
+ 0,
491
+ "and no step may CLAIM a fusion that did not happen",
492
+ );
493
+ });
494
+
495
+ test("14. the climb publishes the peak that recall's gate reads", async () => {
496
+ // `Attention.peak` (types.ts) is the LARGEST single-region contribution behind
497
+ // an anchor, and mechanisms/recall.ts gates on it (`forest[0].peak > LN2`).
498
+ // The climb computed it, carried it into `ranked`, and the rationale showed
499
+ // everything EXCEPT it — the one decision-making quantity that was invisible.
500
+ //
501
+ // FAIL BEFORE: `anchor.peak` did not exist. The assertions below are a real
502
+ // relation, not a magic number: one contribution cannot exceed the sum of
503
+ // contributions, so `peak <= pooledVote`, and at least one anchor must have
504
+ // peak > 0 or the fixture never reached the quantity at all.
505
+ const CORPUS = [
506
+ ["What is the capital of France", "The capital of France is Paris"],
507
+ ["Paris", "Paris is famous for the Eiffel Tower"],
508
+ ["2+2", "2+2 equals 4"],
509
+ [
510
+ "The tallest tower in Paris",
511
+ "The tallest tower in Paris is the Eiffel Tower",
512
+ ],
513
+ ];
514
+ const findAnchors = (o, depth = 0) => {
515
+ if (o === null || typeof o !== "object" || depth > 5) return null;
516
+ if (Array.isArray(o.anchors) && o.anchors.length) return o;
517
+ for (const v of Object.values(o)) {
518
+ const r = findAnchors(v, depth + 1);
519
+ if (r) return r;
520
+ }
521
+ return null;
522
+ };
523
+ const store = new SQliteStore({ path: ":memory:" });
524
+ const mind = new Mind({ seed: 7, store, profile: true });
525
+ await mind.ingest(CORPUS);
526
+ const steps = [];
527
+ await mind.respondText(
528
+ "capital of France and the tallest tower in Paris",
529
+ (s) => steps.push(s),
530
+ );
531
+ await store.close();
532
+ const step = steps.find((s) => findAnchors(s.data ?? s) !== null);
533
+ const td = step ? findAnchors(step.data ?? step) : null;
534
+ assert.ok(td, "the climb must report its anchors");
535
+ assert.ok(
536
+ td.anchors.every((a) => typeof a.peak === "number"),
537
+ "every anchor must publish its peak",
538
+ );
539
+ assert.ok(
540
+ td.anchors.some((a) => a.peak > 0),
541
+ `at least one anchor must have a real peak (got ${
542
+ JSON.stringify(td.anchors.map((a) => a.peak))
543
+ })`,
544
+ );
545
+ assert.ok(
546
+ td.anchors.every((a) => a.peak <= a.pooledVote),
547
+ "one contribution cannot exceed the sum of contributions",
548
+ );
549
+ });
550
+
551
+ test("15. the live record tells the two refusal gates apart", async () => {
552
+ // Item 2 of the open list: `below-natural-break` and `below-consensus-floor`
553
+ // always travelled TOGETHER in the fixtures measured before, so the margin
554
+ // proved the bar was tight but not that it was the cause. Measured over 53
555
+ // anchors on 12 queries, they DO separate: 9 anchors have the bar refusing
556
+ // what the natural break would accept, and none the other way round — but in
557
+ // every case both `passes*` flags were false, so the reasons alone could not
558
+ // show WHICH gate refused.
559
+ //
560
+ // This pins the one gate that refused, from the live commit record
561
+ // (`recordAnchor`, spec §8: decisions recorded as the gates apply them, never
562
+ // reconstructed). It asserts a PROPERTY, not an anchor id: at least one
563
+ // anchor must be rejected with the floor failing and the break PASSING —
564
+ // otherwise the fixture never reached the divergence it is here to pin.
565
+ const CORPUS = [
566
+ ["What is the capital of France", "The capital of France is Paris"],
567
+ ["Paris", "Paris is famous for the Eiffel Tower"],
568
+ ["2+2", "2+2 equals 4"],
569
+ [
570
+ "The tallest tower in Paris",
571
+ "The tallest tower in Paris is the Eiffel Tower",
572
+ ],
573
+ ["Gustaf Molander", "Gustaf Molander was a Swedish film director"],
574
+ ["Stockholm", "Stockholm is the capital of Sweden"],
575
+ ];
576
+ const findAnchors = (o, depth = 0) => {
577
+ if (o === null || typeof o !== "object" || depth > 5) return null;
578
+ if (Array.isArray(o.anchors) && o.anchors.length) return o;
579
+ for (const v of Object.values(o)) {
580
+ const r = findAnchors(v, depth + 1);
581
+ if (r) return r;
582
+ }
583
+ return null;
584
+ };
585
+ const store = new SQliteStore({ path: ":memory:" });
586
+ const mind = new Mind({ seed: 7, store, profile: true });
587
+ await mind.ingest(CORPUS);
588
+ const steps = [];
589
+ await mind.respondText(
590
+ "Gustaf Molander and the tallest tower in Paris and 2+2",
591
+ (s) => steps.push(s),
592
+ );
593
+ await store.close();
594
+ const step = steps.find((s) => findAnchors(s.data ?? s) !== null);
595
+ const td = step ? findAnchors(step.data ?? step) : null;
596
+ assert.ok(td, "the climb must report its anchors");
597
+
598
+ const barRefusedOnly = td.anchors.filter((a) =>
599
+ (a.commit ?? {}).passesConsensusFloor === false &&
600
+ (a.commit ?? {}).passesNaturalBreak === true
601
+ );
602
+ assert.ok(
603
+ barRefusedOnly.length >= 1,
604
+ `the fixture must contain a floor-only refusal (got ${
605
+ JSON.stringify(
606
+ td.anchors.map((
607
+ a,
608
+ ) => [
609
+ a.anchor,
610
+ a.commit?.passesNaturalBreak,
611
+ a.commit?.passesConsensusFloor,
612
+ ]),
613
+ )
614
+ })`,
615
+ );
616
+ for (const a of barRefusedOnly) {
617
+ assert.equal((a.commit ?? {}).status, "rejected");
618
+ assert.ok(
619
+ (a.commit ?? {}).rejectionReasons.includes("below-consensus-floor"),
620
+ "the reason must name the floor",
621
+ );
622
+ assert.ok(
623
+ !(a.commit ?? {}).rejectionReasons.includes("below-natural-break"),
624
+ "and must NOT name a gate that passed",
625
+ );
626
+ }
627
+ });
628
+
629
+ test("16. the climb's own search is timed", async () => {
630
+ // Item 3 of the open list: the climb's phases were timed
631
+ // (`climb.voteRegions`, `climb.structuralResonance`, `climb.crossRegion`) but
632
+ // the pooled derivation itself was NOT — `lightestDerivation(system)` ran
633
+ // outside any phase, so any cost or gain inside it was invisible. It is now
634
+ // wrapped in `meter.timeSync` (the synchronous seam: the graph search is
635
+ // synchronous, and wrapping it in a promise just to measure it would make the
636
+ // profiled path await where the unprofiled one does not).
637
+ //
638
+ // MEASURED: climb.derivation = 0.1-0.2 ms, i.e. ~0.2% of a ~85 ms response —
639
+ // the climb's cost is in voteRegions (5.8 ms), not in the search. The
640
+ // wall-clock min over 5 runs moved 43.1 → 43.0, 84.8 → 84.6, 27.7 → 27.8 ms:
641
+ // the two-snapshot overhead is not measurable.
642
+ //
643
+ // FAIL BEFORE: `climb.derivation` did not exist. The sibling assertion is the
644
+ // anti-vacuity guard — without it this would pass on a fixture that never
645
+ // climbed at all.
646
+ const CORPUS = [
647
+ ["What is the capital of France", "The capital of France is Paris"],
648
+ ["Paris", "Paris is famous for the Eiffel Tower"],
649
+ ["2+2", "2+2 equals 4"],
650
+ [
651
+ "The tallest tower in Paris",
652
+ "The tallest tower in Paris is the Eiffel Tower",
653
+ ],
654
+ ];
655
+ const store = new SQliteStore({ path: ":memory:" });
656
+ const mind = new Mind({ seed: 7, store, profile: true });
657
+ await mind.ingest(CORPUS);
658
+ await mind.respondText("capital of France and the tallest tower in Paris");
659
+ const phases = mind.lastCost.phases;
660
+ await store.close();
661
+
662
+ const sibling = phases["climb.voteRegions"];
663
+ assert.ok(
664
+ sibling && sibling.calls >= 1,
665
+ `the fixture must reach the climb (phases: ${
666
+ Object.keys(phases).join(", ")
667
+ })`,
668
+ );
669
+ const search = phases["climb.derivation"];
670
+ assert.ok(search, "the climb's own search must be a timed phase");
671
+ assert.equal(search.calls, 1);
672
+ assert.ok(
673
+ typeof search.ms === "number" && search.ms >= 0,
674
+ `and it must carry a real duration (got ${search.ms})`,
675
+ );
676
+ });
677
+
678
+ test("17. the bar and the climb's vote are in one dimension", async () => {
679
+ // `consensusFloor(N) = ln(N) + 1/2` is the POOLED-vote significance floor
680
+ // (thresholds.md §2: "each region contributes at most ln(N/c) <= ln(N)"), and
681
+ // attention.ts builds the vote on the same scale. The comparison in recall and
682
+ // in cast holds because the climb WEIGHTS BY IDF: `wf` is `direct ? df :
683
+ // combined ? idf + df : idf`, and the engine only runs the last one (DFMode's
684
+ // default "inverse", the mode every non-test caller uses). There, `wf === idf`,
685
+ // so the pooled vote IS the per-place reading the bar prices.
686
+ //
687
+ // FAIL BEFORE: nothing pinned this. Changing the default mode, or `wf`'s
688
+ // formula, would silently move `vote >= consensusFloor` out of the floor's
689
+ // dimension — and `direct`/`combined` are real reads (test/24 pins that their
690
+ // votes differ), not dead code.
691
+ const CORPUS = [
692
+ ["What is the capital of France", "The capital of France is Paris"],
693
+ ["Paris", "Paris is famous for the Eiffel Tower"],
694
+ ["2+2", "2+2 equals 4"],
695
+ [
696
+ "The tallest tower in Paris",
697
+ "The tallest tower in Paris is the Eiffel Tower",
698
+ ],
699
+ ];
700
+ const findAnchors = (o, depth = 0) => {
701
+ if (o === null || typeof o !== "object" || depth > 5) return null;
702
+ if (Array.isArray(o.anchors) && o.anchors.length) return o;
703
+ for (const v of Object.values(o)) {
704
+ const r = findAnchors(v, depth + 1);
705
+ if (r) return r;
706
+ }
707
+ return null;
708
+ };
709
+ const store = new SQliteStore({ path: ":memory:" });
710
+ const mind = new Mind({ seed: 7, store, profile: true });
711
+ await mind.ingest(CORPUS);
712
+ const steps = [];
713
+ await mind.respondText(
714
+ "capital of France and the tallest tower in Paris",
715
+ (s) => steps.push(s),
716
+ );
717
+ await store.close();
718
+ const step = steps.find((s) => findAnchors(s.data ?? s) !== null);
719
+ const td = step ? findAnchors(step.data ?? step) : null;
720
+ assert.ok(td, "the climb must report its anchors");
721
+ assert.equal(
722
+ td.config.mode,
723
+ "inverse",
724
+ "the engine's mode is the IDF-weighted one",
725
+ );
726
+
727
+ // Anti-vacuity: at least one anchor must actually stand on contributions.
728
+ const real = td.anchors.filter((a) => (a.contributingVotes ?? 0) >= 1);
729
+ assert.ok(real.length >= 1, "the fixture must reach an anchor with evidence");
730
+
731
+ for (const a of real) {
732
+ assert.equal(
733
+ a.pooledVote,
734
+ a.idfVote,
735
+ `the pooled vote and the per-place reading must coincide under the ` +
736
+ `engine's mode (anchor ${a.anchor}: pooled=${a.pooledVote} idf=${a.idfVote})`,
737
+ );
738
+ }
739
+ });
740
+
741
+ test("18. the remainder the pipeline decides on is visible", async () => {
742
+ // The remainder that licenses an extension or a fusion was computed at the
743
+ // decision point and never published: the reasoner's own counters say what it
744
+ // CARRIED, not what the grounding LEFT. Two write-only counters close that,
745
+ // and they are the instrument the structural question about the climb needs —
746
+ // "does an elected anchor's span fall inside what the cover left open?".
747
+ //
748
+ // FAIL BEFORE: the counters did not exist.
749
+ const CORPUS = [
750
+ ["What is the capital of France", "The capital of France is Paris"],
751
+ ["Paris", "Paris is famous for the Eiffel Tower"],
752
+ ];
753
+ const run = async (q) => {
754
+ const store = new SQliteStore({ path: ":memory:" });
755
+ const mind = new Mind({ seed: 7, store, profile: true });
756
+ await mind.ingest(CORPUS);
757
+ await mind.respondText(q);
758
+ const c = mind.lastCost.counters;
759
+ await store.close();
760
+ return {
761
+ spans: c.postGroundingRemainderSpans,
762
+ bytes: c.postGroundingRemainderBytes,
763
+ steps: c.reasonSteps,
764
+ };
765
+ };
766
+
767
+ // The pivoting query: the reasoner runs, so the remainder is non-empty
768
+ // (measured: reasonSteps 1, reasonCarriedBytes 11).
769
+ const pivot = await run("What is the capital of France famous for");
770
+ assert.ok(
771
+ (pivot.steps ?? 0) >= 1,
772
+ `the fixture must reach the post-grounding stage (reasonSteps=${pivot.steps})`,
773
+ );
774
+ assert.ok(
775
+ (pivot.spans ?? 0) >= 1,
776
+ `a licence to extend needs a remainder (spans=${pivot.spans})`,
777
+ );
778
+ assert.ok(
779
+ (pivot.bytes ?? 0) >= 1,
780
+ `and it is measured in bytes (bytes=${pivot.bytes})`,
781
+ );
782
+
783
+ // The directly-answered query: no remainder, so the zero convention drops both.
784
+ const direct = await run("What is the capital of France");
785
+ assert.equal(direct.spans, undefined);
786
+ assert.equal(direct.bytes, undefined);
787
+ });
788
+
789
+ test("19. the floor is read on the pooled vote, not on one region", async () => {
790
+ // thresholds.md §2 derives `consensusFloor` as the POOLED-vote floor (one
791
+ // maximally-specific region contributes at most ln N, and ln(N)+1/2 demands
792
+ // corroboration BEYOND one region). The engine reads it that way in three
793
+ // places (`commitVotes`, `recall`, `cast`), and this pins the practice with
794
+ // the measurement that decided it: across 27 anchors on 6 queries, all 11
795
+ // admissions cleared the floor by the SUM and NONE by `peak` alone.
796
+ //
797
+ // FAIL BEFORE: `types.ts` prescribed the opposite ("must read `peak`, not
798
+ // `vote`"), and nothing pinned what the engine actually does. A gate reading
799
+ // `peak` would refuse every root the engine elects.
800
+ const CORPUS = [
801
+ ["What is the capital of France", "The capital of France is Paris"],
802
+ ["Paris", "Paris is famous for the Eiffel Tower"],
803
+ ["2+2", "2+2 equals 4"],
804
+ [
805
+ "The tallest tower in Paris",
806
+ "The tallest tower in Paris is the Eiffel Tower",
807
+ ],
808
+ ];
809
+ const findAnchors = (o, depth = 0) => {
810
+ if (o === null || typeof o !== "object" || depth > 5) return null;
811
+ if (Array.isArray(o.anchors) && o.anchors.length) return o;
812
+ for (const v of Object.values(o)) {
813
+ const r = findAnchors(v, depth + 1);
814
+ if (r) return r;
815
+ }
816
+ return null;
817
+ };
818
+ const store = new SQliteStore({ path: ":memory:" });
819
+ const mind = new Mind({ seed: 7, store, profile: true });
820
+ await mind.ingest(CORPUS);
821
+ const steps = [];
822
+ await mind.respondText(
823
+ "capital of France and the tallest tower in Paris",
824
+ (s) => steps.push(s),
825
+ );
826
+ await store.close();
827
+ const step = steps.find((s) => findAnchors(s.data ?? s) !== null);
828
+ const td = step ? findAnchors(step.data ?? step) : null;
829
+ assert.ok(td, "the climb must report its anchors");
830
+ const floor = td.config.consensusFloor;
831
+
832
+ const roots = td.anchors.filter((a) => (a.commit ?? {}).status === "root");
833
+ assert.ok(roots.length >= 1, "the fixture must elect at least one root");
834
+ for (const a of roots) {
835
+ assert.ok(
836
+ a.pooledVote >= floor,
837
+ `a root must clear the floor by the pooled vote (${a.pooledVote} vs ${floor})`,
838
+ );
839
+ }
840
+ const bySumOnly = roots.filter((a) => a.peak < floor);
841
+ assert.ok(
842
+ bySumOnly.length >= 1,
843
+ `this fixture must contain a root that only the SUM admits (peaks: ${
844
+ JSON.stringify(roots.map((a) => a.peak))
845
+ } vs floor ${floor})`,
846
+ );
847
+ });
848
+
849
+ test("20. the non-IDF weighting modes never flip a gate verdict", async () => {
850
+ // `consensusFloor` is derived for the POOLED IDF-weighted vote. The other two
851
+ // weighting modes deviate — and the deviation is TWO-SIDED and bounded by
852
+ // `ln 2`: `direct` deflates a region (ln(1+c) < ln(N/c) for small c) and
853
+ // `combined` inflates it. MEASURED across 8 anchors in 5 queries, running the
854
+ // same climb in all three modes: gating on the mode-dependent `vote` DID flip a
855
+ // verdict — anchor 87 of the second query (inverse 2.682 admitted, direct 1.468
856
+ // refused, floor 2.292). The fix is to gate on the IDF sum, which is
857
+ // mode-independent by construction and EQUALS `vote` in the engine's own mode,
858
+ // so no verdict in `inverse` moves.
859
+ //
860
+ // The anchor that makes this test non-vacuous is #148 of the second query: its
861
+ // `combined` reading sits ABOVE the floor and its `direct` reading BELOW, while
862
+ // its inverse reading is above — i.e. both deviations are present in the
863
+ // fixture, and still neither crosses the floor in the wrong place.
864
+ const CORPUS = [
865
+ ["What is the capital of France", "The capital of France is Paris"],
866
+ ["Paris", "Paris is famous for the Eiffel Tower"],
867
+ ["2+2", "2+2 equals 4"],
868
+ [
869
+ "The tallest tower in Paris",
870
+ "The tallest tower in Paris is the Eiffel Tower",
871
+ ],
872
+ ["Gustaf Molander", "Gustaf Molander was a Swedish film director"],
873
+ ["Stockholm", "Stockholm is the capital of Sweden"],
874
+ ];
875
+ const { corpusN } = await import("../dist/src/mind/traverse.js");
876
+ const { consensusFloor } = await import("../dist/src/geometry.js");
877
+ const store = new SQliteStore({ path: ":memory:" });
878
+ const mind = new Mind({ seed: 7, store, profile: true });
879
+ await mind.ingest(CORPUS);
880
+ const queries = [
881
+ "capital of France and the tallest tower in Paris",
882
+ "2+2 and the Eiffel Tower",
883
+ "Gustaf Molander and the tallest tower in Paris and 2+2",
884
+ ];
885
+ let anchors = 0;
886
+ let straddling = 0;
887
+ for (const q of queries) {
888
+ const enc = new TextEncoder().encode(q);
889
+ const floor = consensusFloor(corpusN(mind));
890
+ const rootsOf = {};
891
+ const votes = {};
892
+ const idfOf = {};
893
+ for (const mode of ["inverse", "direct", "combined"]) {
894
+ const got = await mind.climbAttention(enc, 24, mode);
895
+ rootsOf[mode] = got.map((a) => a.anchor).sort((x, y) => x - y);
896
+ votes[mode] = new Map(got.map((a) => [a.anchor, a.vote]));
897
+ // `idfVote` is on Attention; the gate reads it.
898
+ idfOf[mode] = new Map(got.map((a) => [a.anchor, a.idfVote]));
899
+ }
900
+ // THE PROPERTY pinned here is the GATE's, per anchor: the IDF reading — the
901
+ // quantity `consensusFloor` is derived for — is mode-independent, so an
902
+ // anchor's floor verdict cannot change with the weighting. The ELECTED SET
903
+ // is deliberately NOT pinned: the election walks the ranked anchors in `vote`
904
+ // order, which is mode-dependent by design (that order is a legitimate
905
+ // ranking, used as an order by `confluence`), and overlap resolution can
906
+ // therefore elect a different anchor in another mode.
907
+ const first = [...votes.inverse.keys()];
908
+ for (const id of first) {
909
+ const iv = idfOf.inverse.get(id);
910
+ assert.equal(
911
+ idfOf.direct.get(id),
912
+ iv,
913
+ `the IDF reading must not depend on the mode (anchor ${id})`,
914
+ );
915
+ assert.equal(
916
+ idfOf.combined.get(id),
917
+ iv,
918
+ `the IDF reading must not depend on the mode (anchor ${id})`,
919
+ );
920
+ assert.equal(
921
+ (idfOf.direct.get(id) ?? 0) >= floor,
922
+ (iv ?? 0) >= floor,
923
+ `the floor verdict must not depend on the mode (anchor ${id})`,
924
+ );
925
+ }
926
+ // Anti-vacuity: the fixture must contain an anchor whose mode-dependent
927
+ // VOTE straddles the floor on opposite sides, so the deviations are present.
928
+ for (const id of rootsOf.inverse) {
929
+ anchors++;
930
+ const c = votes.combined.get(id) ?? 0;
931
+ const d = votes.direct.get(id) ?? 0;
932
+ if ((c >= floor) !== (d >= floor)) straddling++;
933
+ }
934
+ }
935
+ await store.close();
936
+ assert.ok(
937
+ anchors >= 4,
938
+ `the fixture must reach several anchors (${anchors})`,
939
+ );
940
+ assert.ok(
941
+ straddling >= 1,
942
+ `the fixture must contain an anchor whose two deviations straddle the floor, ` +
943
+ `else this test pins nothing (straddling=${straddling})`,
944
+ );
945
+ });
946
+
947
+ test("21. the price's second term has one definition, and it is the complement", async () => {
948
+ // `unaccountedBytes` collapsed four copies of the same `reduce` (audit, etapa 6).
949
+ // Since the value did NOT change, the lot is only pinned if something would
950
+ // fail when the helper sums the wrong thing — so this test uses an input where
951
+ // the two candidate readings DIFFER.
952
+ const { unexplainedSpans, unaccountedBytes } = await import(
953
+ "../dist/src/mind/rationale.js"
954
+ );
955
+ // Empty accounted ⇒ the whole query is unaccounted.
956
+ assert.equal(unaccountedBytes(unexplainedSpans(10, [])), 10);
957
+ // Full cover ⇒ nothing is unaccounted (this is the honest-silence end).
958
+ assert.equal(unaccountedBytes(unexplainedSpans(10, [[0, 10]])), 0);
959
+ // THE DISCRIMINATING CASE — OVERLAPPING accounted spans:
960
+ // [[0,6],[4,10]] covers the union [0,10) = 10 bytes, while summing their
961
+ // EXTENSIONS gives 6 + 6 = 12. The price's term is the COMPLEMENT, so it
962
+ // must be 0 here; a helper that summed extensions or accounted lengths
963
+ // would answer 12 (or 6+6) and fail.
964
+ const overlapping = [[0, 6], [4, 10]];
965
+ const gaps = unexplainedSpans(10, overlapping);
966
+ assert.equal(
967
+ gaps.length,
968
+ 0,
969
+ `the union fully covers: gaps=${JSON.stringify(gaps)}`,
970
+ );
971
+ assert.equal(unaccountedBytes(gaps), 0);
972
+ // And the complement is additive against the covered union: half-covered.
973
+ assert.equal(unaccountedBytes(unexplainedSpans(10, [[0, 4], [2, 6]])), 4);
974
+ // Nothing is ever negative or double-counted on a mixed input.
975
+ assert.equal(
976
+ unaccountedBytes(unexplainedSpans(20, [[5, 8], [5, 8], [12, 15]])),
977
+ 14,
978
+ );
979
+ });
980
+
981
+ test("22. the trace publishes the bar the margin gate ACTUALLY applied", async () => {
982
+ // The margin gate scales its bar by what the region does NOT address:
983
+ // `estimatorNoise(D) * (1 - cov)`. The REJECTION path recorded that scaled
984
+ // bar; the "voted" path recorded the RAW `estimatorNoise(D)` — so a region
985
+ // that PASSED was reported closer to its limit than it was. The field lives
986
+ // on the REGION trace (`recordRegion`), which is where the gate runs.
987
+ //
988
+ // ANTI-VACUITY, two guards: (1) the fixture must contain at least one region
989
+ // that reached the margin gate at all — on a corpus where no approximate
990
+ // region gets there, this test would pin nothing; (2) among those, at least
991
+ // one VOTED region must have been judged with a bar BELOW the raw one
992
+ // (cov > 0 on the passing side). On the old code every voted floor was raw,
993
+ // so guard (2) fails — which is what makes this test discriminating.
994
+ const { Mind, SQliteStore } = await import("../dist/src/index.js");
995
+ const { estimatorNoise } = await import("../dist/src/geometry.js");
996
+ const achar = (o, d = 0) => {
997
+ if (o === null || typeof o !== "object" || d > 5) return null;
998
+ if (Array.isArray(o.regions) && o.regions.length) return o;
999
+ for (const v of Object.values(o)) {
1000
+ const r = achar(v, d + 1);
1001
+ if (r) return r;
1002
+ }
1003
+ return null;
1004
+ };
1005
+ const store = new SQliteStore({ path: ":memory:" });
1006
+ const mind = new Mind({ seed: 7, store, profile: true });
1007
+ const P = [
1008
+ "France",
1009
+ "Sweden",
1010
+ "Japan",
1011
+ "Peru",
1012
+ "Kenya",
1013
+ "Nepal",
1014
+ "Chile",
1015
+ "Oman",
1016
+ "Fiji",
1017
+ "Malta",
1018
+ "Cuba",
1019
+ "Laos",
1020
+ ];
1021
+ const CORPUS = [];
1022
+ for (const p of P) {
1023
+ CORPUS.push([
1024
+ `What is the capital of ${p}`,
1025
+ `The capital of ${p} is the city of ${p}town`,
1026
+ ]);
1027
+ CORPUS.push([
1028
+ `The capital of ${p} is the city of ${p}town`,
1029
+ `The ${p}town parliament sits by the river`,
1030
+ ]);
1031
+ }
1032
+ CORPUS.push([
1033
+ "What is the tallest tower in Paris",
1034
+ "The tallest tower in Paris is the Eiffel Tower",
1035
+ ]);
1036
+ CORPUS.push(["2+2", "2+2 equals 4"]);
1037
+ await mind.ingest(CORPUS);
1038
+ const q =
1039
+ "how do you say thank you in the language of Nepal and in the language of Oman";
1040
+ const steps = [];
1041
+ await mind.respondText(q, (x) => steps.push(x));
1042
+ const st = steps.find((x) => achar(x.data ?? x) !== null);
1043
+ const td = st ? achar(st.data ?? st) : null;
1044
+ const raw = estimatorNoise(store.D);
1045
+ const regions = td?.regions ?? [];
1046
+ const withFloor = regions.filter((r) =>
1047
+ r.contrastiveNoiseFloor !== undefined
1048
+ );
1049
+ assert.ok(
1050
+ withFloor.length >= 1,
1051
+ `the fixture must reach the margin gate at all (regions=${regions.length}, ` +
1052
+ `withFloor=${withFloor.length})`,
1053
+ );
1054
+ for (const r of withFloor) {
1055
+ assert.ok(
1056
+ r.contrastiveNoiseFloor <= raw + 1e-12,
1057
+ `the applied bar never exceeds the raw one (${r.contrastiveNoiseFloor} > ${raw})`,
1058
+ );
1059
+ }
1060
+ // guard (2): the outcome field names which regions PASSED.
1061
+ const voted = withFloor.filter((r) => r.ordinaryVoteProduced === true);
1062
+ assert.ok(
1063
+ voted.length >= 1,
1064
+ `the fixture must contain a region that PASSED the gate and reached the margin ` +
1065
+ `(withFloor=${withFloor.length}); fields present: ${
1066
+ JSON.stringify(Object.keys(withFloor[0] ?? {}))
1067
+ }`,
1068
+ );
1069
+ assert.ok(
1070
+ voted.some((r) => r.contrastiveNoiseFloor < raw - 1e-12),
1071
+ `at least one VOTED region must carry a bar BELOW the raw one (cov > 0 on the ` +
1072
+ `passing side); otherwise this test pins nothing ` +
1073
+ `(voted=${voted.length}, floors=${
1074
+ voted.map((r) => r.contrastiveNoiseFloor).join(",")
1075
+ } raw=${raw})`,
1076
+ );
1077
+ await store.close();
1078
+ });
1079
+
1080
+ test("23. the payload carries every section the draft supplies", async () => {
1081
+ // `ClimbConsensusData` is assembled by hand from `TraceDraft` (see
1082
+ // traceAttention): four sections are plain pass-throughs (`regions`,
1083
+ // `saturation`, `pooling`, `anchors`), one comes from the config
1084
+ // (`reaches`), and one is REBUILT from the summary plus two draft arrays
1085
+ // (`crossRegion`). A section added to the draft and missed in that assembly
1086
+ // vanishes from the payload SILENTLY — which is the drift `visited?` records
1087
+ // ("Absent on payloads recorded before this field existed"). The draft
1088
+ // itself is not observable from outside, but the PAYLOAD is: this test runs a
1089
+ // climb that produces each pass-through section and asserts it arrives.
1090
+ const { Mind, SQliteStore } = await import("../dist/src/index.js");
1091
+ const achar = (o, d = 0) => {
1092
+ if (o === null || typeof o !== "object" || d > 5) return null;
1093
+ if (o.config && Array.isArray(o.anchors)) return o;
1094
+ for (const v of Object.values(o)) {
1095
+ const r = achar(v, d + 1);
1096
+ if (r) return r;
1097
+ }
1098
+ return null;
1099
+ };
1100
+ const store = new SQliteStore({ path: ":memory:" });
1101
+ const mind = new Mind({ seed: 7, store, profile: true });
1102
+ await mind.ingest([
1103
+ ["What is the capital of France", "The capital of France is Paris"],
1104
+ ["Paris", "Paris is famous for the Eiffel Tower"],
1105
+ [
1106
+ "The tallest tower in Paris",
1107
+ "The tallest tower in Paris is the Eiffel Tower",
1108
+ ],
1109
+ ["2+2", "2+2 equals 4"],
1110
+ ["Gustaf Molander", "Gustaf Molander was a Swedish film director"],
1111
+ ["Stockholm", "Stockholm is the capital of Sweden"],
1112
+ ]);
1113
+ const q = "the capital of France and the tallest tower in Paris and 2+2";
1114
+ const steps = [];
1115
+ await mind.respondText(q, (x) => steps.push(x));
1116
+ const st = steps.find((x) => achar(x.data ?? x) !== null);
1117
+ const td = st ? achar(st.data ?? st) : null;
1118
+ await store.close();
1119
+ assert.ok(td !== null, "the traced climb must emit a payload at all");
1120
+ for (const k of ["config", "candidates", "result"]) {
1121
+ assert.ok(td[k] !== undefined, `the payload must always carry \`${k}\``);
1122
+ }
1123
+ // The four pass-through sections, plus the one the cfg supplies.
1124
+ const missing = [];
1125
+ for (const k of ["regions", "saturation", "pooling", "anchors", "reaches"]) {
1126
+ if (td[k] === undefined) missing.push(k);
1127
+ }
1128
+ assert.deepEqual(
1129
+ missing,
1130
+ [],
1131
+ `the payload is missing section(s) the draft/config supplied: ${
1132
+ missing.join(", ")
1133
+ } ` +
1134
+ `(present: ${Object.keys(td).join(", ")})`,
1135
+ );
1136
+ assert.ok(
1137
+ (td.regions ?? []).length >= 1 && (td.anchors ?? []).length >= 1,
1138
+ `the fixture must produce regions and anchors (regions=${
1139
+ (td.regions ?? []).length
1140
+ }, ` +
1141
+ `anchors=${(td.anchors ?? []).length})`,
1142
+ );
1143
+ });