@hviana/sema 0.8.0 → 0.8.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 (84) hide show
  1. package/AGENTS.md +22 -1
  2. package/DATASETS.md +1 -1
  3. package/dist/example/train_base/config.js +2 -2
  4. package/dist/example/train_base/corpora/massive.js +1 -1
  5. package/dist/example/train_base/readers.js +1 -1
  6. package/dist/src/geometry.d.ts +10 -10
  7. package/dist/src/geometry.js +25 -24
  8. package/dist/src/meter.d.ts +4 -12
  9. package/dist/src/meter.js +14 -14
  10. package/dist/src/mind/attention.js +12 -12
  11. package/dist/src/mind/bridge.d.ts +8 -8
  12. package/dist/src/mind/bridge.js +33 -32
  13. package/dist/src/mind/graph-search.d.ts +0 -8
  14. package/dist/src/mind/graph-search.js +9 -8
  15. package/dist/src/mind/junction.d.ts +1 -1
  16. package/dist/src/mind/junction.js +8 -8
  17. package/dist/src/mind/learning.js +36 -35
  18. package/dist/src/mind/match.js +14 -13
  19. package/dist/src/mind/mechanisms/cover.js +13 -12
  20. package/dist/src/mind/mechanisms/prefix-completion.js +24 -24
  21. package/dist/src/mind/mechanisms/recall.js +38 -40
  22. package/dist/src/mind/mechanisms/reference.js +16 -16
  23. package/dist/src/mind/mind.d.ts +6 -7
  24. package/dist/src/mind/pipeline-mechanism.d.ts +10 -8
  25. package/dist/src/mind/pipeline-mechanism.js +25 -21
  26. package/dist/src/mind/pipeline.d.ts +9 -9
  27. package/dist/src/mind/pipeline.js +24 -23
  28. package/dist/src/mind/primitives.d.ts +5 -5
  29. package/dist/src/mind/primitives.js +5 -5
  30. package/dist/src/mind/recognition.d.ts +14 -13
  31. package/dist/src/mind/recognition.js +23 -23
  32. package/dist/src/mind/resonance.js +21 -21
  33. package/dist/src/mind/traverse.d.ts +54 -52
  34. package/dist/src/mind/traverse.js +74 -72
  35. package/dist/src/mind/types.d.ts +4 -4
  36. package/dist/src/store.d.ts +12 -12
  37. package/dist/src/store.js +12 -12
  38. package/docs/INDEX.md +2 -2
  39. package/docs/architecture/exact-vs-approximate.md +2 -1
  40. package/docs/architecture/fold-contract.md +1 -1
  41. package/docs/failures/tempting-but-wrong.md +2 -3
  42. package/docs/harness/gates.md +7 -7
  43. package/example/train_base/config.ts +2 -2
  44. package/example/train_base/corpora/massive.ts +1 -1
  45. package/example/train_base/readers.ts +1 -1
  46. package/jsr.json +1 -1
  47. package/package.json +1 -1
  48. package/src/geometry.ts +25 -24
  49. package/src/meter.ts +14 -14
  50. package/src/mind/attention.ts +12 -12
  51. package/src/mind/bridge.ts +33 -32
  52. package/src/mind/graph-search.ts +9 -8
  53. package/src/mind/junction.ts +8 -8
  54. package/src/mind/learning.ts +36 -35
  55. package/src/mind/match.ts +20 -19
  56. package/src/mind/mechanisms/cover.ts +13 -12
  57. package/src/mind/mechanisms/prefix-completion.ts +24 -24
  58. package/src/mind/mechanisms/recall.ts +38 -40
  59. package/src/mind/mechanisms/reference.ts +16 -16
  60. package/src/mind/mind.ts +6 -7
  61. package/src/mind/pipeline-mechanism.ts +25 -21
  62. package/src/mind/pipeline.ts +33 -32
  63. package/src/mind/primitives.ts +5 -5
  64. package/src/mind/recognition.ts +23 -23
  65. package/src/mind/resonance.ts +21 -21
  66. package/src/mind/traverse.ts +74 -72
  67. package/src/mind/types.ts +4 -4
  68. package/src/store.ts +20 -20
  69. package/test/08-storage.test.mjs +1 -1
  70. package/test/35-prefix-edge.test.mjs +1 -1
  71. package/test/40-choosenext-scale-guard.test.mjs +16 -17
  72. package/test/56-bridge-identity-admission.test.mjs +6 -6
  73. package/test/70-prefix-completion.test.mjs +4 -3
  74. package/test/72-prefix-candidate-supply.test.mjs +3 -3
  75. package/test/73-scaffolding-only-bridge-abstains.test.mjs +6 -6
  76. package/test/75-multiturn-context-optimisation.test.mjs +5 -5
  77. package/test/84-composed-answer-honesty.test.mjs +5 -6
  78. package/test/88-dependency-footprint.test.mjs +1 -1
  79. package/test/89-completion-recursion.test.mjs +17 -14
  80. package/test/90-connector-read-cap.test.mjs +10 -8
  81. package/test/93-regime-prediction.test.mjs +10 -10
  82. package/test/94-cross-region-budget.test.mjs +2 -2
  83. package/test/95-wide-resonance-removed.test.mjs +8 -7
  84. package/test/96-bytes-walk-termination.test.mjs +3 -3
@@ -32,11 +32,11 @@ interface StructCache {
32
32
  hasParents: Map<number, boolean>;
33
33
  }
34
34
  //
35
- // Budgeted on the same terms as the reach memo below (AGENTS §2.12): these
36
- // three maps are cleared on every write, but a long read-only session over a
37
- // large store converges on one entry per node per map with nothing to bound
38
- // it. Past the cap all three are dropped together and re-derived, costing
39
- // cold structural probes and never a wrong answer.
35
+ // Budgeted on the same terms as the reach memo below (caches.md): these three
36
+ // maps are cleared on every write, but a long read-only session over a large
37
+ // store converges on one entry per node per map with nothing to bound it. Past
38
+ // the cap all three are dropped together and re-derived, costing cold
39
+ // structural probes and never a wrong answer.
40
40
  const STRUCT_MEMO_MAX = 100_000;
41
41
  const structCaches = new WeakMap<object, StructCache>();
42
42
 
@@ -55,21 +55,22 @@ const structCaches = new WeakMap<object, StructCache>();
55
55
  // battery repeatedly reaches the same corpus scaffolding even when its
56
56
  // surface questions differ.
57
57
  //
58
- // Budgeted, not unbounded (AGENTS §2.12): past the cap the whole map is
59
- // dropped and re-derived, costing a cold climb and never a wrong answer.
58
+ // Budgeted, not unbounded (caches.md): past the cap the whole map is dropped
59
+ // and
60
+ // re-derived, costing a cold climb and never a wrong answer.
60
61
  const REACH_MEMO_MAX = 100_000;
61
62
  const reachCaches = new WeakMap<object, Map<number, AncestorReach>>();
62
63
 
63
64
  /** The reach memo this ask should use — see the note above.
64
65
  *
65
- * A TRACED response always gets a fresh, empty one. `AncestorReach`'s
66
- * `visited`/`maxDepth`/`saturation` fields are populated only when a trace
67
- * is attached, so an entry deposited by an untraced earlier turn would
68
- * silently black out the reach detail of a later traced one; and the trace's
69
- * reach payload is serialised by ITERATING this map, which must therefore
70
- * hold what THIS climb consulted, not the whole conversation's history.
71
- * Consistent with AGENTS §2.11: a traced response is a different machine —
72
- * never benchmark with a trace attached. */
66
+ * A TRACED response always gets a fresh, empty one. `AncestorReach`'s
67
+ * `visited`/`maxDepth`/`saturation` fields are populated only when a trace is
68
+ * attached, so an entry deposited by an untraced earlier turn would silently
69
+ * black out the reach detail of a later traced one; and the trace's reach
70
+ * payload is serialised by ITERATING this map, which must therefore hold what
71
+ * THIS climb consulted, not the whole conversation's history. Consistent with
72
+ * memoization.md: a traced response is a different machine — never benchmark
73
+ * with a trace attached. */
73
74
  export function sharedReachMemo(
74
75
  ctx: MindContext,
75
76
  ): Map<number, AncestorReach> {
@@ -481,13 +482,14 @@ export function bearsEdge(ctx: MindContext, id: number): boolean {
481
482
  }
482
483
 
483
484
  /** Whether a node LEADS SOMEWHERE — it bears a continuation edge or a halo.
484
- * The admission predicate recognition filters sites with (HOW_IT_WORKS
485
- * §15.3): a form that leads nowhere contributes nothing to any derivation.
486
- * Runs once per candidate span on the recognition hot path — `hasNext` is
487
- * cached per response (the same flat-branch ids are probed across prefix
488
- * variants by canonicalChunkId). `hasHalo` is not cached: it's a single
489
- * indexed point probe per candidate, and the candidates that reach this
490
- * check have already been filtered by hasNext above in edgeAncestors. */
485
+ * The admission predicate recognition filters sites with (cover.md): a form
486
+ * that
487
+ * leads nowhere contributes nothing to any derivation. Runs once per candidate
488
+ * span on the recognition hot path — `hasNext` is cached per response (the same
489
+ * flat-branch ids are probed across prefix variants by canonicalChunkId).
490
+ * `hasHalo` is not cached: it's a single indexed point probe per candidate, and
491
+ * the candidates that reach this check have already been filtered by hasNext
492
+ * above in edgeAncestors. */
491
493
  export function leadsSomewhere(ctx: MindContext, id: number): boolean {
492
494
  const memo = getStructCache(ctx);
493
495
  if (cachedHasNext(ctx, id, memo)) return true;
@@ -539,10 +541,10 @@ function boundFor(contextCount: number): number {
539
541
  }
540
542
 
541
543
  /** Cap a candidate list at the hub bound √N (insertion order) — the ONE
542
- * fan-out convention every walk and disambiguation uses (see HOW_IT_WORKS
543
- * §8.6). A node connected to more than √N others is a hub whose individual
544
- * connections carry ~no discriminative information; materialising or scoring
545
- * them all would make single decisions scale with the corpus. */
544
+ * fan-out convention every walk and disambiguation uses (see bounded-reads.md).
545
+ * A node connected to more than √N others is a hub whose individual connections
546
+ * carry ~no discriminative information; materialising or scoring them all would
547
+ * make single decisions scale with the corpus. */
546
548
  export function hubCap<T>(
547
549
  ctx: MindContext,
548
550
  ids: readonly T[],
@@ -581,16 +583,16 @@ export function contains(
581
583
  * the EXACT half's veto on calling them synonyms.
582
584
  *
583
585
  * Halos measure company, and the strongest company any two forms can keep is
584
- * standing next to each other: a question and its answer co-occur in every
585
- * episode that taught the pair, so their halos SHOULD be similar, and on a
586
- * conversational store they are (measured on the CONV fixture: consecutive
587
- * turns at 0.809 against a 0.516 concept threshold). A gate reading halo
588
- * cosine alone therefore reads adjacency as synonymy and revoices an answer
589
- * in the words of the question it answers — "it hangs in madrid" spliced back
590
- * into "where is it kept now". The distributional layer cannot tell the two
591
- * relations apart, because to it they are the same observation; the exact
592
- * half can, for free, because it stored the edge. §4.1's division of labour
593
- * exactly: approximate proposes, exact decides.
586
+ * standing next to each other: a question and its answer co-occur in every
587
+ * episode that taught the pair, so their halos SHOULD be similar, and on a
588
+ * conversational store they are (measured on the CONV fixture: consecutive
589
+ * turns at 0.809 against a 0.516 concept threshold). A gate reading halo cosine
590
+ * alone therefore reads adjacency as synonymy and revoices an answer in the
591
+ * words of the question it answers — "it hangs in madrid" spliced back into
592
+ * "where is it kept now". The distributional layer cannot tell the two
593
+ * relations apart, because to it they are the same observation; the exact half
594
+ * can, for free, because it stored the edge. halo-sketch.md's division of
595
+ * labour exactly: approximate proposes, exact decides.
594
596
  *
595
597
  * Read LIMITed in both directions at the hub bound — a common continuation's
596
598
  * fan-in is corpus-sized, and no single decision may scale with it. */
@@ -745,19 +747,18 @@ export function chooseNext(
745
747
  // NO consensusFloor gate here (tried and reverted — see
746
748
  // test/40-choosenext-scale-guard.test.mjs): that floor is calibrated for
747
749
  // POOLED, IDF-weighted CLIMB VOTES (recallByResonance, commitVotes), where
748
- // each corroborating region contributes at most ln N and the floor grows
749
- // with N exactly as that per-region ceiling does (HOW_IT_WORKS.md §8.6).
750
- // `bestSupport` here is a different kind of quantity — a raw prevCount of
751
- // how many training contexts predicted ONE destination, bounded by how
752
- // often that specific fact was retold, never by corpus size N. Gating an
753
- // N-invariant count against an N-growing threshold guarantees failure
754
- // once N is large enough, discarding genuinely, structurally dominant
755
- // edges (observed: a fact corroborated 2-to-1-1-1 refused at N≈325K,
756
- // falling back to a noisy concept-hop). The loop above already IS the
757
- // "genuinely competing" test: a tie leaves first-inserted as the pick
758
- // (test/30's own pinned behaviour); a strict winner is real evidence
759
- // regardless of corpus scale. Matches HOW_IT_WORKS.md §25's own
760
- // chooseNext pseudocode, which has no such floor.
750
+ // each corroborating region contributes at most ln N and the floor grows with
751
+ // N exactly as that per-region ceiling does (thresholds.md). `bestSupport`
752
+ // here is a different kind of quantity — a raw prevCount of how many training
753
+ // contexts predicted ONE destination, bounded by how often that specific fact
754
+ // was retold, never by corpus size N. Gating an N-invariant count against an
755
+ // N-growing threshold guarantees failure once N is large enough, discarding
756
+ // genuinely, structurally dominant edges (observed: a fact corroborated
757
+ // 2-to-1-1-1 refused at N≈325K, falling back to a noisy concept-hop). The
758
+ // loop above already IS the "genuinely competing" test: a tie leaves
759
+ // first-inserted as the pick (test/30's own pinned behaviour); a strict
760
+ // winner is real evidence regardless of corpus scale. Matches `chooseNext`'s
761
+ // own pseudocode, which has no such floor.
761
762
 
762
763
  // Trace is built lazily — the filter + map below only execute when a
763
764
  // trace listener is attached, so the common (no-trace) path pays only
@@ -838,12 +839,13 @@ function rItemShort(
838
839
  * W-window it spells is contained by more places than the hub bound allows,
839
840
  * i.e. the whole query is corpus-global scaffolding.
840
841
  *
841
- * WHAT IT IS FOR. Several mechanisms ground a query through the literal
842
- * spans it did NOT explain, and those spans are the whole of their evidence.
843
- * When every one of them is a hub, the query says nothing the corpus can be
844
- * held to, and grounding it means picking one of thousands of continuations
845
- * it gives no evidence for — a fabrication whatever the answer happens to be.
846
- * Answering with silence there is the honest degradation contract (§2.13).
842
+ * WHAT IT IS FOR. Several mechanisms ground a query through the literal spans
843
+ * it
844
+ * did NOT explain, and those spans are the whole of their evidence. When every
845
+ * one of them is a hub, the query says nothing the corpus can be held to, and
846
+ * grounding it means picking one of thousands of continuations it gives no
847
+ * evidence for — a fabrication whatever the answer happens to be. Answering
848
+ * with silence there is the honest degradation contract (INVARIANTS.md).
847
849
  *
848
850
  * MEASURED SEPARATION (trained store, hubBound 571) — this is categorical,
849
851
  * not marginal, and it is why the predicate lives here rather than being
@@ -860,11 +862,11 @@ function rItemShort(
860
862
  * evidence and sit on the SAME side as the correct ones, so this predicate
861
863
  * is not what makes them silent and cannot be credited for them.
862
864
  *
863
- * NO NEW THRESHOLD (§2.2): `hubBound` is the √N reading of "hub" used
864
- * everywhere, and the containment read is clamped to it exactly as every
865
- * other fan-out read is (§2.8). A query with no stored window at all is NOT
866
- * scaffolding-only — it has no evidence either way, and its callers already
867
- * refuse it on their own terms. */
865
+ * NO NEW THRESHOLD (thresholds.md): `hubBound` is the √N reading of "hub" used
866
+ * everywhere, and the containment read is clamped to it exactly as every other
867
+ * fan-out read is (bounded-reads.md). A query with no stored window at all is
868
+ * NOT scaffolding-only — it has no evidence either way, and its callers already
869
+ * refuse it on their own terms. */
868
870
  export function allWindowsAreScaffolding(
869
871
  ctx: MindContext,
870
872
  query: Uint8Array,
@@ -916,19 +918,19 @@ export function allWindowsAreScaffolding(
916
918
  * by climbing containment then parents. Nothing is added to the write side;
917
919
  * this reads an index training already built.
918
920
  *
919
- * BOUNDED (§2.8), AND WITH NO NEW THRESHOLD. The window whose containment is
920
- * SMALLEST carries the most evidence, and one saturated at `hubBound` carries
921
- * none — that is the same √N reading of "hub" the rest of the mind uses, not
922
- * a tuned knob. The upward walk spends a budget of `hubBound` nodes and
923
- * fans out by W, so a hub query enumerates nothing and the caller stays
924
- * silent rather than guessing (§2.13). Measured on the trained store: the
925
- * photosynthesis form at a one-byte truncation picks a window with 52
926
- * containers, visits 446 nodes, and yields exactly ONE candidate that
927
- * survives the caller's byte compare — the form itself.
921
+ * BOUNDED (bounded-reads.md), AND WITH NO NEW THRESHOLD. The window whose
922
+ * containment is SMALLEST carries the most evidence, and one saturated at
923
+ * `hubBound` carries none — that is the same √N reading of "hub" the rest of
924
+ * the mind uses, not a tuned knob. The upward walk spends a budget of
925
+ * `hubBound` nodes and fans out by W, so a hub query enumerates nothing and the
926
+ * caller stays silent rather than guessing (INVARIANTS.md). Measured on the
927
+ * trained store: the photosynthesis form at a one-byte truncation picks a
928
+ * window with 52 containers, visits 446 nodes, and yields exactly ONE candidate
929
+ * that survives the caller's byte compare — the form itself.
928
930
  *
929
- * These are PROPOSALS only. Every candidate still faces the byte-exact
930
- * prefix compare and all three guards below, so a wrong proposal costs one
931
- * bounded read and can never be voiced (§2.3). */
931
+ * These are PROPOSALS only. Every candidate still faces the byte-exact prefix
932
+ * compare and all three guards below, so a wrong proposal costs one bounded
933
+ * read and can never be voiced (exact-vs-approximate.md). */
932
934
  export function formsOpenedBy(
933
935
  ctx: MindContext,
934
936
  query: Uint8Array,
package/src/mind/types.ts CHANGED
@@ -297,10 +297,10 @@ export type AItem =
297
297
  export interface MindContext extends GraphSearchHost {
298
298
  store: Store;
299
299
  /** The work accumulator for the inference call in flight, or null when
300
- * nothing is profiling — see src/meter.ts. WRITE-ONLY from the engine's
301
- * point of view: no inference decision may read a counter, or the
302
- * determinism contract (AGENTS §2.1) is gone. Every call site is
303
- * `ctx.meter?.x++`, so an unprofiled response allocates nothing. */
300
+ * nothing is profiling — see src/meter.ts. WRITE-ONLY from the engine's point
301
+ * of view: no inference decision may read a counter, or determinism is gone
302
+ * (determinism.md). Every call site is `ctx.meter?.x++`, so an unprofiled
303
+ * response allocates nothing. */
304
304
  meter: Meter | null;
305
305
  space: Space;
306
306
  alphabet: Alphabet;
package/src/store.ts CHANGED
@@ -541,16 +541,16 @@ export interface Store {
541
541
  // selected by identity hash so the choice is a property of each constituent
542
542
  // and never of where it sits in the fold.
543
543
  //
544
- // DURABLE DERIVED STATE, NOT A CACHE. §2.12 permits a cache to cost only
544
+ // DURABLE DERIVED STATE, NOT A CACHE. caches.md permits a cache to cost only
545
545
  // speed; this decides which terms enter a halo — a learned relation — so an
546
- // eviction would change the geometry rather than slow it down. It is
546
+ // eviction would change the geometry rather than slow it down. It is
547
547
  // therefore written like the canon index: computed once, kept, never
548
- // budgeted. Soundness rests on the set being INTRINSIC — minimality,
549
- // `len ≥ W` and non-domination are properties of the node's own subtree and
550
- // do not move as the corpus grows. The one corpus-dependent reading, the
551
- // hub exclusion, is deliberately NOT stored: it is applied by the caller at
552
- // pour time over the ≤ k candidates, which is the drift companyProfile
553
- // already documents as benign and one-directional.
548
+ // budgeted. Soundness rests on the set being INTRINSIC — minimality, `len ≥
549
+ // W` and non-domination are properties of the node's own subtree and do not
550
+ // move as the corpus grows. The one corpus-dependent reading, the hub
551
+ // exclusion, is deliberately NOT stored: it is applied by the caller at pour
552
+ // time over the ≤ k candidates, which is the drift companyProfile already
553
+ // documents as benign and one-directional.
554
554
  //
555
555
  // Backends that do not implement the pair leave both absent; companyProfile
556
556
  // then recomputes the sketch per pour and simply loses the amortisation.
@@ -1789,18 +1789,18 @@ export abstract class AbstractStore implements Store {
1789
1789
  * remainders must fit the budget. Scattered differences leave a wide
1790
1790
  * middle and are rejected.
1791
1791
  *
1792
- * Every read here is CAPPED (§2.8). It used to open with
1793
- * `bytesPrefix(k, Number.MAX_SAFE_INTEGER)` — the ALL sentinel, i.e. the
1794
- * full materialising `bytes()` read — on the deposit hot path, and only
1795
- * then compare lengths. So a candidate the length test was about to reject
1796
- * had already been reconstructed byte for byte. The LENGTHS decide first
1797
- * instead, from the `contentLen` memo the interning order has already built
1798
- * bottom-up, and the target's length is itself read under a cap: a target
1799
- * longer than `la + W` is rejected without touching one of its bytes.
1800
- * Same semantics — the old capped `b` read would have produced
1801
- * `a.length + W + 1` here and failed the very same test — strictly fewer
1802
- * byte reads. The `+ 1` on each byte cap keeps `_prefix`'s
1803
- * "complete reconstruction" test true, so the results still cache. */
1792
+ * Every read here is CAPPED (bounded-reads.md). It used to open with
1793
+ * `bytesPrefix(k, Number.MAX_SAFE_INTEGER)` — the ALL sentinel, i.e. the full
1794
+ * materialising `bytes()` read — on the deposit hot path, and only then
1795
+ * compare lengths. So a candidate the length test was about to reject had
1796
+ * already been reconstructed byte for byte. The LENGTHS decide first instead,
1797
+ * from the `contentLen` memo the interning order has already built bottom-up,
1798
+ * and the target's length is itself read under a cap: a target longer than
1799
+ * `la + W` is rejected without touching one of its bytes. Same semantics —
1800
+ * the old capped `b` read would have produced `a.length + W + 1` here and
1801
+ * failed the very same test — strictly fewer byte reads. The `+ 1` on each
1802
+ * byte cap keeps `_prefix`'s "complete reconstruction" test true, so the
1803
+ * results still cache. */
1804
1804
  private differsByOneWindow(
1805
1805
  kids: NodeId[],
1806
1806
  targetId: NodeId,
@@ -361,7 +361,7 @@ test("a halo accumulates poured signatures and gates on mass", async () => {
361
361
  });
362
362
 
363
363
  // A multi-turn conversation is deposited as ACCUMULATED-CONTEXT episodes — the
364
- // pattern HOW_IT_WORKS §19a prescribes and example/train.ts uses:
364
+ // pattern example/train.ts uses:
365
365
  // (t0) → t1
366
366
  // (t0 + t1) → t2
367
367
  // (t0 + t1 + t2) → t3
@@ -5,7 +5,7 @@
5
5
  // parents, or (halo > 0 ∧ already an edge source). Pure answers do
6
6
  // not qualify — they are destinations, not sources.
7
7
  //
8
- // All phrases verified via instrumentation first (see MISTAKES.md).
8
+ // All phrases verified via instrumentation first.
9
9
 
10
10
  import { test } from "node:test";
11
11
  import assert from "node:assert/strict";
@@ -10,23 +10,22 @@
10
10
  // already requires strict dominance; a tie leaves first-inserted as the
11
11
  // pick, exactly the "no real winner" case a floor would matter for.
12
12
  //
13
- // But chooseNext ALSO gated this pick on `bestSupport < consensusFloor(N)`
14
- // once the corpus scale crosses atomIsHub's threshold (traverse.ts:541-546)
15
- // — reusing the SAME ln(N)+0.5 floor recallByResonance and commitVotes use
16
- // for POOLED, IDF-weighted CLIMB VOTES (each region worth up to ln N, so a
17
- // sum exceeding ln N + 0.5 is more than any one region could say alone —
18
- // HOW_IT_WORKS.md §8.6). `prevCount(candidate)` is a different kind of
19
- // quantity: a raw count of how many training contexts independently
20
- // predicted ONE destination, bounded by how many times that specific fact
21
- // was retold — NOT by corpus size N. Gating an N-invariant count against
22
- // an N-growing threshold guarantees failure once N is large enough
23
- // (verified live: N≈325K gives a floor of ≈13.19, so a genuinely dominant
24
- // but only-doubly-attested fact like "capital of France → Paris" was
25
- // refused, falling back to a noisy concept-hop that produced the wrong
26
- // answer). HOW_IT_WORKS.md's own canonical chooseNext pseudocode (§25)
27
- // has NO such floor — it's undocumented implementation drift, not a
28
- // deliberate design surface. Fix: remove the gate; chooseNext's existing
29
- // strict-dominance loop already IS the "genuinely competing" test.
13
+ // But chooseNext ALSO gated this pick on `bestSupport < consensusFloor(N)` once
14
+ // the corpus scale crosses atomIsHub's threshold (traverse.ts:541-546) —
15
+ // reusing the SAME ln(N)+0.5 floor recallByResonance and commitVotes use for
16
+ // POOLED, IDF-weighted CLIMB VOTES (each region worth up to ln N, so a sum
17
+ // exceeding ln N + 0.5 is more than any one region could say alone —
18
+ // thresholds.md). `prevCount(candidate)` is a different kind of quantity: a raw
19
+ // count of how many training contexts independently predicted ONE destination,
20
+ // bounded by how many times that specific fact was retold — NOT by corpus size
21
+ // N. Gating an N-invariant count against an N-growing threshold guarantees
22
+ // failure once N is large enough (verified live: N≈325K gives a floor of
23
+ // ≈13.19, so a genuinely dominant but only-doubly-attested fact like "capital
24
+ // of France → Paris" was refused, falling back to a noisy concept-hop that
25
+ // produced the wrong answer). The canonical `chooseNext` pseudocode has NO such
26
+ // floor — it is undocumented implementation drift, not a deliberate design
27
+ // surface. Fix: remove the gate; chooseNext's existing strict-dominance loop
28
+ // already IS the "genuinely competing" test.
30
29
 
31
30
  import { test } from "node:test";
32
31
  import assert from "node:assert/strict";
@@ -3,13 +3,13 @@
3
3
  // separated from the query only by material that does not change what the
4
4
  // text SAYS, is the SAME learnt form and grounds through its own edge.
5
5
  //
6
- // "Material that does not change what it says" has ONE definition here, and
7
- // it is read from the corpus, never tuned (AGENTS §2.7, corpus-global
6
+ // "Material that does not change what it says" has ONE definition here, and it
7
+ // is read from the corpus, never tuned (commonality.md, corpus-global
8
8
  // population): a span is EXPLAINED when it is sub-quantum (< W — typographic
9
- // glue) or every W-window in it is COMMON by the store's own climb (the
10
- // ascent saturates, or it reaches a majority of contexts). A window that
11
- // reaches NOTHING is novel content and is never explained — the reading that
12
- // separates a droppable "the process of " from a load-bearing "heavy ".
9
+ // glue) or every W-window in it is COMMON by the store's own climb (the ascent
10
+ // saturates, or it reaches a majority of contexts). A window that reaches
11
+ // NOTHING is novel content and is never explained — the reading that separates
12
+ // a droppable "the process of " from a load-bearing "heavy ".
13
13
  //
14
14
  // THE GAP THIS CLOSES (measured on the 17.9M-node trained store). The query
15
15
  // `Who wrote Romeo and Juliet?` against the trained `Who wrote "Romeo and
@@ -1,9 +1,10 @@
1
1
  // 70-prefix-completion.test.mjs — a query that IS the opening of one trained
2
2
  // form is completed by that form's remainder; anything less is refused.
3
3
  //
4
- // WHAT THE MECHANISM DOES (src/mind/prefix-completion.ts): when every other
5
- // tier has declined, scan the candidate list recall's refusal path has ALREADY
6
- // fetched and look for a trained form whose bytes literally BEGIN with the whole
4
+ // WHAT THE MECHANISM DOES (src/mind/mechanisms/prefix-completion.ts): when
5
+ // every other tier has declined, scan the candidate list recall's refusal path
6
+ // has ALREADY fetched and look for a trained form whose bytes literally BEGIN
7
+ // with the whole
7
8
  // query. The answer is that form's own remainder — never an invention.
8
9
  //
9
10
  // WHY IT IS NEEDED, measured on the 15.7M-node trained store:
@@ -98,9 +98,9 @@ test("a proper prefix reaches its trained form through the window supply", async
98
98
  "a FORM is grounded whole, never a slice cut at the query's end",
99
99
  );
100
100
 
101
- // HONEST DEGRADATION (§2.13). A query with no discriminative window must
102
- // propose nothing rather than guess — silence is the correct answer, and a
103
- // supply that widened until it found something would be the real defect.
101
+ // HONEST DEGRADATION (INVARIANTS.md). A query with no discriminative window
102
+ // must propose nothing rather than guess — silence is the correct answer, and
103
+ // a supply that widened until it found something would be the real defect.
104
104
  const hub = enc("The ");
105
105
  assert.equal(
106
106
  prefixCompletion(m, hub, formsOpenedBy(m, hub)),
@@ -2,16 +2,16 @@
2
2
  // ABSTAIN when every literal span it did not substitute is corpus-global
3
3
  // scaffolding.
4
4
  //
5
- // THE DEFECT THIS PINS. A bridge grounds through the literal spans it did NOT
6
- // substitute; those anchors are the whole of its evidence. The anchor scan
5
+ // THE DEFECT THIS PINS. A bridge grounds through the literal spans it did NOT
6
+ // substitute; those anchors are the whole of its evidence. The anchor scan
7
7
  // ranked them by containment but rejected only the ones with ZERO containers,
8
8
  // so a query made entirely of scaffolding still bridged — the single
9
9
  // substituted span carried the whole semantic load, and the answer was voiced
10
- // with confidence. Measured on the trained store (hubBound 571): "What is the
10
+ // with confidence. Measured on the trained store (hubBound 571): "What is the
11
11
  // capital of" has 19 anchors, ALL saturated ("What":572, "hat ":572, "at i":572
12
- // …), and answered with an unrelated trained context about an integral. That
13
- // breaks honest silence (§2.13), which is worse than a gap: a gap is visible, a
14
- // fabrication is not.
12
+ // …), and answered with an unrelated trained context about an integral. That
13
+ // breaks honest silence (INVARIANTS.md), which is worse than a gap: a gap is
14
+ // visible, a fabrication is not.
15
15
  //
16
16
  // WHY THIS IS NOT A PROBE-SHAPED PATCH. The gate was falsified against the
17
17
  // queries the bridge answers CORRECTLY before it was written, and every one of
@@ -966,11 +966,11 @@ test("F1: every turn of a trained conversation is answered exactly", async () =>
966
966
  test("F1b: attaching a trace changes no answer — the audit layer is inert", async () => {
967
967
  // The mind's ONLY text-shaped code lives in the rationale/trace payloads:
968
968
  // attention.ts's `dec` helper decodes bytes and collapses whitespace so an
969
- // audit line is readable, and frame-filler builds diagnostic strings the
970
- // same way. Neither may ever reach a decision — nothing in the core knows
971
- // what "whitespace" is (see canon.ts's header, and AGENTS §2.11: profile
972
- // and trace must not move an answer). Asserted here rather than assumed,
973
- // because the formatting sits inside the same functions that decide.
969
+ // audit line is readable, and frame-filler builds diagnostic strings the same
970
+ // way. Neither may ever reach a decision — nothing in the core knows what
971
+ // "whitespace" is (see canon.ts's header, and memoization.md: profile and
972
+ // trace must not move an answer). Asserted here rather than assumed, because
973
+ // the formatting sits inside the same functions that decide.
974
974
  const pairs = [
975
975
  [
976
976
  "who painted the weeping woman",
@@ -32,8 +32,7 @@
32
32
  //
33
33
  // TO REPRODUCE THE REAL FAILURE: build the same chain, then ingest ~6,000
34
34
  // deposits produced by the Taskmaster adapter (example/train_base/corpora/
35
- // taskmaster.ts) from
36
- // TM-2/TM-3/TM-4, and ask the two-hop question. See FINDINGS.md §A1/§A4.
35
+ // taskmaster.ts) from TM-2/TM-3/TM-4, and ask the two-hop question.
37
36
 
38
37
  import { test } from "node:test";
39
38
  import assert from "node:assert/strict";
@@ -93,10 +92,10 @@ test("each hop still answers on its own — the substrate is intact", async () =
93
92
 
94
93
  test("a two-hop query composes or stays silent — it never fabricates", async () => {
95
94
  // THE CONTRACT. Three outcomes are conceivable and only two are acceptable:
96
- // compose -> the answer contains Paris
97
- // silence -> the empty answer, which is honest (AGENTS §2.13)
98
- // fabricate-> an assembly carrying content from an unrelated deposit
99
- // The third is what a store past the real-text ceiling actually does.
95
+ // compose -> the answer contains Paris silence -> the empty answer, which is
96
+ // honest (INVARIANTS.md) fabricate-> an assembly carrying content from an
97
+ // unrelated deposit The third is what a store past the real-text ceiling
98
+ // actually does.
100
99
  const mind = await storeWithDistractors();
101
100
  const answer = await mind.respondText(TWO_HOP);
102
101
 
@@ -1,6 +1,6 @@
1
1
  // 88 — the dependency footprint is a PRODUCT PROPERTY, so it is tested.
2
2
  //
3
- // AGENTS.md §6: "do not add runtime dependencies casually — the near-zero-
3
+ // AGENTS.md §7: "do not add runtime dependencies casually — the near-zero-
4
4
  // dependency footprint is a product feature." A feature stated only in prose
5
5
  // erodes; this suite pins it at the two places it can actually break.
6
6
  //
@@ -1,20 +1,21 @@
1
1
  // 89-completion-recursion.test.mjs — the completion recursion must be
2
2
  // OUTPUT-SENSITIVE.
3
3
  //
4
- // AGENTS §2.8: "No per-query read may grow with the corpus." §2.8 enforces
5
- // that per READ (nextFirst, bytesPrefix, …), and every one of those caps holds.
6
- // What no guard covered is the NUMBER of reads: `recompleteNode`
4
+ // bounded-reads.md: "No per-query read may grow with the corpus." That law is
5
+ // enforced per READ (nextFirst, bytesPrefix, …), and every one of those caps
6
+ // holds. What no guard covered is the NUMBER of reads: `recompleteNode`
7
7
  // (src/mind/graph-search.ts) re-covers a produced node by calling `solve`
8
- // recursively, and each nested solve builds its own agenda and chart. Its own
8
+ // recursively, and each nested solve builds its own agenda and chart. Its own
9
9
  // doc states the intent —
10
10
  //
11
11
  // "its cost tracks the ANSWER's own structure, not how densely the corpus
12
12
  // interconnects the nodes passed through"
13
13
  //
14
14
  // — but argues termination from "Distinct node ids are finite and each finished
15
- // completion is memoised". Finite-in-the-corpus is exactly the bound §2.8
16
- // forbids, and the recursion is emitted at `cost: 0` while the nested cover's
17
- // own `cost` is computed and discarded, so A* has no gradient against depth.
15
+ // completion is memoised". Finite-in-the-corpus is exactly the bound
16
+ // bounded-reads.md forbids, and the recursion is emitted at `cost: 0` while the
17
+ // nested cover's own `cost` is computed and discarded, so A* has no gradient
18
+ // against depth.
18
19
  //
19
20
  // MEASURED on a trained store (18,938,834 nodes, edgeSourceCount 796,528):
20
21
  // `respond("Hi")` reached recursion depth 331 and 9.1 GB RSS in 56 s without
@@ -221,8 +222,9 @@ test("completion recursion: per-query work does not grow with the corpus", async
221
222
  } (agenda pops) — target ≪ 1 (sublinear in the corpus)`,
222
223
  );
223
224
 
224
- // THE LAW. Same answer, more corpus, so cost must not move. k ≈ 0 is flat,
225
- // k ≈ 1 is linear in the corpus — the bound §2.8 forbids outright.
225
+ // THE LAW. Same answer, more corpus, so cost must not move. k ≈ 0 is flat, k
226
+ // ≈
227
+ // 1 is linear in the corpus — the bound bounded-reads.md forbids outright.
226
228
  //
227
229
  // `searches` counts nested solve() calls, which is the recursion itself and
228
230
  // nothing else, so it gets 14-scaling.test.mjs's stricter 0.6 bar. Measured
@@ -234,17 +236,18 @@ test("completion recursion: per-query work does not grow with the corpus", async
234
236
  `nested solve() builds its own agenda and chart, so this is the ` +
235
237
  `completion recursion doing work the answer never asked for`,
236
238
  );
237
- // A LOOSER BAR, FOR A REASON. `searchPops` aggregates the TOP-LEVEL cover's
239
+ // A LOOSER BAR, FOR A REASON. `searchPops` aggregates the TOP-LEVEL cover's
238
240
  // agenda too, and that one legitimately carries some corpus sensitivity: a
239
241
  // bigger store recognises more sites inside the same query, so more items are
240
- // admissible. Only outright linear growth is the forbidden case (§2.8), so
241
- // this asserts the law itself, k < 1, rather than the stricter 0.6 that suits
242
- // a counter the fix governs end to end. Measured 1.40 unfixed, 0.54 fixed.
242
+ // admissible. Only outright linear growth is the forbidden case
243
+ // (bounded-reads.md), so this asserts the law itself, k < 1, rather than the
244
+ // stricter 0.6 that suits a counter the fix governs end to end. Measured 1.40
245
+ // unfixed, 0.54 fixed.
243
246
  assert.ok(
244
247
  kPops < 1,
245
248
  `agenda pops grew with exponent k=${kPops.toFixed(2)} in corpus size (${
246
249
  pops.join(" → ")
247
250
  }) for a byte-identical answer — k≈1 is work LINEAR in the corpus, which ` +
248
- `is the bound §2.8 forbids outright`,
251
+ `is the bound bounded-reads.md forbids outright`,
249
252
  );
250
253
  });
@@ -7,10 +7,10 @@
7
7
  // substring search, so it needs the candidate's bytes; it used to reconstruct
8
8
  // them in FULL via `read(ctx, answer)`, whose maxLen defaults to ALL.
9
9
  //
10
- // AGENTS §2.8, prefix-capped reads: "a candidate that exceeds the cap is
10
+ // bounded-reads.md, prefix-capped reads: "a candidate that exceeds the cap is
11
11
  // rejected without reconstructing it — the weave, the junction walks and the
12
12
  // bridge all read this way, and uncapped reads there cost seconds per query on
13
- // a large store." This probe was the exception, and it runs hubBound(ctx) = √N
13
+ // a large store." This probe was the exception: it runs hubBound(ctx) = √N
14
14
  // times PER SITE.
15
15
  //
16
16
  // The probe's corpus-scale cost was once claimed from a trained-store
@@ -18,14 +18,15 @@
18
18
  // prompt" — but that number was measured on a `respond()` query, where the
19
19
  // probe does NOT execute (`answeredSpans` is empty there, so the enclosing
20
20
  // guard returns first). It is therefore not attributable to the probe and is
21
- // not repeated here (§2.16: a comment asserting a measurement inherits Gate 1).
21
+ // not repeated here (a comment asserting a measurement inherits Gate 1).
22
22
  // The probe runs only on a multi-turn `respondTurn` response; its benefit there
23
23
  // is still unmeasured.
24
24
  //
25
- // WHAT THIS PINS. The cap cannot reduce the read COUNT — only a semantic change
26
- // could (see below). It bounds each read by the QUERY, which is what §2.8 asks
27
- // and what rescues a SHORT query: candidates averaged 231 B reconstructed
28
- // against a 3-byte prompt. So the invariant here is per-read SIZE.
25
+ // WHAT THIS PINS. The cap cannot reduce the read COUNT — only a semantic change
26
+ // could (see below). It bounds each read by the QUERY, which is what
27
+ // bounded-reads.md asks and what rescues a SHORT query: candidates averaged 231
28
+ // B reconstructed against a 3-byte prompt. So the invariant here is per-read
29
+ // SIZE.
29
30
  //
30
31
  // It is measured by calling `resolveConnectors` DIRECTLY and diffing the meter
31
32
  // across it. A whole-response counter cannot express this: `bytesRead` sums
@@ -121,7 +122,8 @@ test("connector probe reads by the query, not by the learnt continuation", async
121
122
  `the connector probe averaged ${perRead.toFixed(0)} B per read for a ` +
122
123
  `${QUERY.length} B query — a candidate longer than the query cannot ` +
123
124
  `occur inside it, so it must be rejected on an overflow probe of ` +
124
- `${QUERY.length + 1} B, not reconstructed in full (AGENTS §2.8)`,
125
+ `${QUERY.length + 1} B, not reconstructed in full ` +
126
+ `(bounded-reads.md)`,
125
127
  );
126
128
  } finally {
127
129
  mind.endResponse();