@hviana/sema 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/AGENTS.md +7 -7
  2. package/dist/src/alu/src/index.d.ts +1 -1
  3. package/dist/src/alu/src/index.js +1 -1
  4. package/dist/src/alu/src/parser.js +2 -6
  5. package/dist/src/alu/src/resonance.d.ts +13 -0
  6. package/dist/src/alu/src/resonance.js +41 -0
  7. package/dist/src/alu/test/alu.test.js +39 -0
  8. package/dist/src/bytes.d.ts +6 -2
  9. package/dist/src/bytes.js +10 -4
  10. package/dist/src/canon.js +44 -0
  11. package/dist/src/geometry.d.ts +19 -1
  12. package/dist/src/geometry.js +125 -141
  13. package/dist/src/meter.d.ts +33 -0
  14. package/dist/src/meter.js +34 -1
  15. package/dist/src/mind/articulation.js +14 -1
  16. package/dist/src/mind/attention.d.ts +12 -0
  17. package/dist/src/mind/attention.js +44 -16
  18. package/dist/src/mind/bridge.js +3 -3
  19. package/dist/src/mind/derivation.d.ts +40 -0
  20. package/dist/src/mind/derivation.js +34 -0
  21. package/dist/src/mind/evidence.d.ts +24 -0
  22. package/dist/src/mind/evidence.js +90 -0
  23. package/dist/src/mind/graph-search.d.ts +89 -15
  24. package/dist/src/mind/graph-search.js +345 -174
  25. package/dist/src/mind/learning.js +1 -1
  26. package/dist/src/mind/mechanisms/cover.d.ts +19 -3
  27. package/dist/src/mind/mechanisms/cover.js +142 -61
  28. package/dist/src/mind/mechanisms/recall.js +10 -3
  29. package/dist/src/mind/mind.d.ts +6 -0
  30. package/dist/src/mind/mind.js +5 -2
  31. package/dist/src/mind/pipeline.d.ts +5 -1
  32. package/dist/src/mind/pipeline.js +220 -90
  33. package/dist/src/mind/primitives.d.ts +25 -5
  34. package/dist/src/mind/primitives.js +107 -44
  35. package/dist/src/mind/reasoning.d.ts +18 -4
  36. package/dist/src/mind/reasoning.js +487 -328
  37. package/dist/src/mind/recognition.js +29 -13
  38. package/dist/src/mind/resonance.js +1 -11
  39. package/dist/src/mind/traverse.d.ts +45 -5
  40. package/dist/src/mind/traverse.js +285 -8
  41. package/dist/src/mind/types.d.ts +16 -1
  42. package/dist/src/store-sqlite.d.ts +25 -0
  43. package/dist/src/store-sqlite.js +89 -1
  44. package/dist/src/store.d.ts +48 -4
  45. package/dist/src/store.js +86 -6
  46. package/docs/INDEX.md +20 -19
  47. package/docs/INVARIANTS.md +17 -16
  48. package/docs/architecture/bounded-reads.md +1 -1
  49. package/docs/architecture/caches.md +5 -4
  50. package/docs/architecture/closure.md +45 -5
  51. package/docs/architecture/cost-model.md +16 -0
  52. package/docs/architecture/evidence.md +113 -0
  53. package/docs/architecture/exact-vs-approximate.md +10 -9
  54. package/docs/architecture/factored-machinery.md +14 -13
  55. package/docs/architecture/fold-contract.md +51 -1
  56. package/docs/architecture/mechanism-market.md +21 -0
  57. package/docs/architecture/memoization.md +3 -3
  58. package/docs/architecture/meter.md +2 -1
  59. package/docs/architecture/saturation.md +12 -0
  60. package/docs/architecture/store.md +25 -2
  61. package/docs/failures/tempting-but-wrong.md +13 -2
  62. package/docs/harness/gates.md +12 -10
  63. package/docs/mechanisms/cover.md +23 -6
  64. package/jsr.json +1 -1
  65. package/package.json +1 -1
  66. package/src/alu/README.md +10 -2
  67. package/src/alu/src/index.ts +1 -0
  68. package/src/alu/src/parser.ts +6 -6
  69. package/src/alu/src/resonance.ts +42 -0
  70. package/src/alu/test/alu.test.ts +40 -0
  71. package/src/bytes.ts +13 -3
  72. package/src/canon.ts +40 -0
  73. package/src/geometry.ts +183 -154
  74. package/src/meter.ts +34 -1
  75. package/src/mind/articulation.ts +14 -2
  76. package/src/mind/attention.ts +47 -25
  77. package/src/mind/bridge.ts +3 -3
  78. package/src/mind/derivation.ts +77 -0
  79. package/src/mind/evidence.ts +107 -0
  80. package/src/mind/graph-search.ts +449 -221
  81. package/src/mind/learning.ts +1 -7
  82. package/src/mind/match.ts +1 -2
  83. package/src/mind/mechanisms/cast.ts +1 -2
  84. package/src/mind/mechanisms/cover.ts +207 -87
  85. package/src/mind/mechanisms/extraction.ts +1 -2
  86. package/src/mind/mechanisms/prefix-completion.ts +1 -1
  87. package/src/mind/mechanisms/recall.ts +17 -5
  88. package/src/mind/mechanisms/reference.ts +1 -1
  89. package/src/mind/mind.ts +9 -30
  90. package/src/mind/pipeline.ts +263 -104
  91. package/src/mind/primitives.ts +119 -43
  92. package/src/mind/reasoning.ts +611 -419
  93. package/src/mind/recognition.ts +24 -9
  94. package/src/mind/resonance.ts +2 -16
  95. package/src/mind/trace.ts +1 -1
  96. package/src/mind/traverse.ts +321 -8
  97. package/src/mind/types.ts +15 -11
  98. package/src/store-sqlite.ts +92 -1
  99. package/src/store.ts +113 -7
  100. package/test/105-derive-through-reports-its-refusal.test.mjs +8 -5
  101. package/test/106-the-join-fires.test.mjs +21 -0
  102. package/test/111-the-cover-assembly-is-counted.test.mjs +8 -5
  103. package/test/128-the-leads-somewhere-pair-agrees.test.mjs +18 -12
  104. package/test/136-the-two-named-limits.test.mjs +3 -2
  105. package/test/137-the-law-lives-once-and-below.test.mjs +21 -0
  106. package/test/148-exact-shortcuts-agree.test.mjs +188 -0
  107. package/test/149-the-closure-engine.test.mjs +138 -0
  108. package/test/150-the-join-is-output-sensitive.test.mjs +66 -0
  109. package/test/151-the-cover-pays-for-what-it-reaches.test.mjs +142 -0
  110. package/test/152-the-read-side-names-as-the-write-side.test.mjs +146 -0
  111. package/test/153-a-cheaper-bound-is-looked-at-first.test.mjs +155 -0
  112. package/test/154-the-question-names-the-step.test.mjs +281 -0
  113. package/test/24-generalization.test.mjs +32 -0
  114. package/test/36-bloom.test.mjs +53 -0
  115. package/test/37-cluster-dispersion-fusion.test.mjs +75 -0
  116. package/test/48-recognise-turn-connective.test.mjs +3 -2
  117. package/test/55-cost-meter.test.mjs +4 -4
  118. package/test/90-connector-read-cap.test.mjs +7 -7
@@ -9,12 +9,12 @@
9
9
  import { isChunk } from "../sema.js";
10
10
  import { lightestDerivation, } from "../derive/src/index.js";
11
11
  import { composeStructuralGist, consensusFloor, dominates, estimatorNoise, } from "../geometry.js";
12
- import { foldTree, gistOf, latin1Key, perceive, read, resolve, } from "./primitives.js";
12
+ import { foldTree, gistOf, perceive, read, resolve } from "./primitives.js";
13
13
  import { recognise } from "./recognition.js";
14
14
  import { leafIdRun } from "./canonical.js";
15
15
  import { atomIsHub, corpusN, edgeAncestors, hubBound, sharedReachMemo, } from "./traverse.js";
16
16
  import { cachedRead, junctionContainersFrom, junctionSeeds, junctionSynonyms, loadJunctionSynonymSides, walkCache, } from "./junction.js";
17
- import { indexOf } from "../bytes.js";
17
+ import { indexOf, latin1 } from "../bytes.js";
18
18
  import { restates } from "./derivation.js";
19
19
  import { rItem, rNode, traceDerivation } from "./trace.js";
20
20
  function newTraceDraft(perceivedCount) {
@@ -74,7 +74,7 @@ export async function climbAttentionAll(ctx, query, k, mode = "inverse") {
74
74
  // Content-keyed memo — works for both single-turn respond() and multi-turn
75
75
  // respondTurn().
76
76
  if (ctx.climbMemo) {
77
- const contentKey = latin1Key(query);
77
+ const contentKey = latin1(query);
78
78
  const modeKey = `${k}:${mode}`;
79
79
  let byRead = ctx.climbMemo.get(contentKey);
80
80
  if (byRead === undefined) {
@@ -1125,7 +1125,7 @@ export function poolVotes(ctx, regionVotes, sat, N, td) {
1125
1125
  * Strict `<` (not `<=`): verified against gap 3.1's own "gender equality"
1126
1126
  * root, whose two genuine clusters sit EXACTLY W bytes apart — `<= W`
1127
1127
  * would wrongly merge them into one and break that pinned requirement. */
1128
- function countClusters(spans, W) {
1128
+ export function countClusters(spans, W) {
1129
1129
  if (spans.length === 0)
1130
1130
  return 0;
1131
1131
  const sorted = [...spans].sort((a, b) => a[0] - b[0]);
@@ -1521,19 +1521,47 @@ export function canonicalChunkId(ctx, regionBytes, N, reachMemo) {
1521
1521
  if (len < 2)
1522
1522
  return flatId;
1523
1523
  // Within one window, the widest reach is still the right CANONICAL
1524
- // identity — a chunk's anchor should be its most general stable form.
1525
- let bestId = flatId;
1526
- let bestReach = edgeAncestors(ctx, flatId, N, reachMemo);
1524
+ // identity — a chunk's anchor should be its most general stable form. A
1525
+ // SATURATED reach is the widest there is: its count is wherever the climb
1526
+ // happened to stop deciding, so an unsaturated prefix never out-widens it
1527
+ // by comparing against that count. (It used to: on test/24's gap-3.1
1528
+ // fixture the region `ace and ` had its saturated window replaced by the
1529
+ // unsaturated 2-byte prefix `e `, and that sub-window anchor voted.)
1530
+ //
1531
+ // So a window's anchor is its SHORTEST saturated form when any saturates,
1532
+ // and its widest-reaching form otherwise — and the forms are climbed
1533
+ // shortest first, stopping at the first that saturates: the forms longer
1534
+ // than it cannot change the anchor. Measured over 8 queries on the
1535
+ // 31.7M-node store: 339 of 347 windows saturate, 312 already at their
1536
+ // 2-byte prefix, while the full window and the 3-byte prefix — climbed
1537
+ // first, to √N contexts each — were 89% of the visits.
1538
+ const forms = [flatId];
1527
1539
  for (let k2 = 1; k2 < len; k2++) {
1528
- const shortIds = ids.slice(0, len - k2);
1529
- const shortId = ctx.store.findBranch(shortIds);
1530
- if (shortId === null)
1531
- continue;
1532
- const shortReach = edgeAncestors(ctx, shortId, N, reachMemo);
1533
- if (shortReach.saturated ||
1534
- shortReach.contextsReached > bestReach.contextsReached) {
1535
- bestId = shortId;
1536
- bestReach = shortReach;
1540
+ const shortId = ctx.store.findBranch(ids.slice(0, len - k2));
1541
+ if (shortId !== null)
1542
+ forms.push(shortId);
1543
+ }
1544
+ const reaches = new Array(forms.length);
1545
+ let bestId = null;
1546
+ let bestReach = null;
1547
+ for (let f = forms.length - 1; f >= 0; f--) {
1548
+ const r = edgeAncestors(ctx, forms[f], N, reachMemo);
1549
+ if (r.saturated) {
1550
+ bestId = forms[f];
1551
+ bestReach = r;
1552
+ break;
1553
+ }
1554
+ reaches[f] = r;
1555
+ }
1556
+ if (bestId === null || bestReach === null) {
1557
+ // None saturates: the widest reach, the longer form on a tie.
1558
+ bestId = forms[0];
1559
+ bestReach = reaches[0];
1560
+ for (let f = 1; f < forms.length; f++) {
1561
+ if (reaches[f].contextsReached > bestReach.contextsReached) {
1562
+ bestId = forms[f];
1563
+ bestReach = reaches[f];
1564
+ }
1537
1565
  }
1538
1566
  }
1539
1567
  if (fallback === null)
@@ -92,7 +92,7 @@
92
92
  import { cosine } from "../vec.js";
93
93
  import { conceptThreshold, dominates, significanceBar } from "../geometry.js";
94
94
  import { bytesEqual, indexOf } from "../bytes.js";
95
- import { foldTree, perceive, read } from "./primitives.js";
95
+ import { exactNode, perceive, read } from "./primitives.js";
96
96
  import { chainReach, leafIdRun } from "./canonical.js";
97
97
  import { allWindowsAreScaffolding, corpusN, edgeAncestors, hubBound, sharedReachMemo, } from "./traverse.js";
98
98
  import { rItem, rNode } from "./trace.js";
@@ -438,7 +438,7 @@ async function bridgeImpl(ctx, query, proposed) {
438
438
  continue;
439
439
  let use = sid;
440
440
  if (!ctx.store.hasNext(use)) {
441
- const folded = foldTree(ctx, perceive(ctx, tb), 0).node;
441
+ const folded = exactNode(ctx, tb);
442
442
  if (folded === null || folded === sid || !ctx.store.hasNext(folded)) {
443
443
  continue;
444
444
  }
@@ -457,7 +457,7 @@ async function bridgeImpl(ctx, query, proposed) {
457
457
  // be a FLAT content twin whose continuation edge lives on the
458
458
  // fold-shaped deposit node with the same bytes — the same twin split
459
459
  // canonResolve bridges by re-folding (primitives.ts) — but the re-fold
460
- // (a full perceive of the candidate's bytes) is paid only for proposals
460
+ // (an identity fold of the candidate's bytes) is paid only for proposals
461
461
  // that could align at all: alignment can only seed at a picked anchor
462
462
  // window occurring literally in the candidate (measured: unconditional
463
463
  // re-folds multiplied the refusal-path latency several-fold).
@@ -204,3 +204,43 @@ export declare function closure(d: DerivationState, query: Uint8Array, W: number
204
204
  onTaken?: (before: DerivationState, after: DerivationState, witnesses: ReadonlyArray<Witness>) => void,
205
205
  /** Called when the law refused the continuation the layer offered. */
206
206
  onRefused?: (at: DerivationState) => void): Promise<DerivationState>;
207
+ /** A LAYER of the closure — a named producer of continuations for one state.
208
+ * The layer OFFERS (its {@link Offer} is the whole of what it decides); the
209
+ * law admits; the engine sequences. The hooks are the layer's own
210
+ * instrumentation, called by the engine at the moments the law defines. */
211
+ export interface ClosureLayer {
212
+ /** The layer's name — its rationale scope and its meter phase. */
213
+ readonly name: string;
214
+ /** Whether the layer has anything to offer this state at all. A layer that
215
+ * does not engage is not ENTERED: no offer is asked and no work is charged
216
+ * to it. Omitted: it always engages. */
217
+ readonly engages?: (d: DerivationState) => boolean;
218
+ /** The layer's continuations, one at a time ({@link Offer}'s contract). */
219
+ readonly offer: Offer;
220
+ /** Called for each step the law admits, with the state before and after. */
221
+ readonly onTaken?: (before: DerivationState, after: DerivationState, witnesses: ReadonlyArray<Witness>) => void;
222
+ /** Called when the law refused the continuation the layer offered. */
223
+ readonly onRefused?: (at: DerivationState) => void;
224
+ /** Called once the layer's walk has ended — the layer offered nothing more,
225
+ * or the law refused — with the state it began from and the one it reached. */
226
+ readonly onEnd?: (from: DerivationState, to: DerivationState) => void;
227
+ }
228
+ /** THE CLOSURE ENGINE — close a derivation under the law, layer by layer.
229
+ *
230
+ * Each layer is walked to its own end ({@link closure}: the layer offers, the
231
+ * law admits or refuses), and the state it reaches is the state the next layer
232
+ * is offered against. Layers are PHASES, in the order given, never revisited:
233
+ * a later layer composes what an earlier one produced, and nothing it adds is
234
+ * re-offered to the earlier one.
235
+ *
236
+ * THE LAW'S FIRST CLAUSE IS APPLIED HERE, ONCE. A FIXED state admits no
237
+ * transition (`admissible` refuses it before anything else), so no layer is
238
+ * entered at all — the decision a caller used to spell as "skip the walk for a
239
+ * complete grounding, and skip the fusion too" is the law's, not the caller's,
240
+ * and it is taken before any layer pays for an offer it could never have
241
+ * taken.
242
+ *
243
+ * `enter` wraps each entered layer's walk — the caller's instrumentation (a
244
+ * meter phase), which this module cannot import: it reads nothing but the
245
+ * state and the layers. */
246
+ export declare function closeOver(d: DerivationState, query: Uint8Array, W: number, layers: ReadonlyArray<ClosureLayer>, enter?: (name: string, walk: () => Promise<DerivationState>) => Promise<DerivationState>): Promise<DerivationState>;
@@ -334,3 +334,37 @@ onRefused) {
334
334
  d = next;
335
335
  }
336
336
  }
337
+ /** THE CLOSURE ENGINE — close a derivation under the law, layer by layer.
338
+ *
339
+ * Each layer is walked to its own end ({@link closure}: the layer offers, the
340
+ * law admits or refuses), and the state it reaches is the state the next layer
341
+ * is offered against. Layers are PHASES, in the order given, never revisited:
342
+ * a later layer composes what an earlier one produced, and nothing it adds is
343
+ * re-offered to the earlier one.
344
+ *
345
+ * THE LAW'S FIRST CLAUSE IS APPLIED HERE, ONCE. A FIXED state admits no
346
+ * transition (`admissible` refuses it before anything else), so no layer is
347
+ * entered at all — the decision a caller used to spell as "skip the walk for a
348
+ * complete grounding, and skip the fusion too" is the law's, not the caller's,
349
+ * and it is taken before any layer pays for an offer it could never have
350
+ * taken.
351
+ *
352
+ * `enter` wraps each entered layer's walk — the caller's instrumentation (a
353
+ * meter phase), which this module cannot import: it reads nothing but the
354
+ * state and the layers. */
355
+ export async function closeOver(d, query, W, layers, enter) {
356
+ for (const layer of layers) {
357
+ if (d.fixed)
358
+ return d;
359
+ if (layer.engages !== undefined && !layer.engages(d))
360
+ continue;
361
+ const from = d;
362
+ const walk = async () => {
363
+ const to = await closure(from, query, W, layer.offer, layer.onTaken, layer.onRefused);
364
+ layer.onEnd?.(from, to);
365
+ return to;
366
+ };
367
+ d = enter === undefined ? await walk() : await enter(layer.name, walk);
368
+ }
369
+ return d;
370
+ }
@@ -0,0 +1,24 @@
1
+ /** Where each W-window of one source first occurs. Built once per source and
2
+ * reused across every form asked against it. */
3
+ export type WindowIndex = Map<string, number>;
4
+ export declare function windowIndex(bytes: Uint8Array, W: number): WindowIndex;
5
+ /** The windows of `index` (over `bytes`) that `spoken` does not hold — what
6
+ * of the question no product of the derivation has restated yet. The keys
7
+ * keep their positions in `bytes`, so a witnessing span still names the
8
+ * asker's bytes. */
9
+ export declare function unspoken(index: WindowIndex, spoken: ReadonlyArray<WindowIndex>): WindowIndex;
10
+ /** How a form is witnessed by a list of sources. */
11
+ export interface Witnessing {
12
+ /** Every byte of the form lies in a window some source holds. */
13
+ complete: boolean;
14
+ /** The windows SOURCE 0 alone supplied — windows no later source holds — as
15
+ * merged spans of source 0. With the question as source 0, this is what the
16
+ * asker said about the form that the derivation did not already have. */
17
+ spans: Array<[number, number]>;
18
+ /** Bytes of source 0 inside `spans`. */
19
+ bytes: number;
20
+ }
21
+ /** Witness `form` against `sources` (their window indexes, same order). A
22
+ * window is credited to the LAST source holding it — so source 0 is credited
23
+ * only with what nothing else at hand supplies. */
24
+ export declare function witness(form: Uint8Array, indexes: ReadonlyArray<WindowIndex>, W: number): Witnessing;
@@ -0,0 +1,90 @@
1
+ // evidence.ts — what of a stored form the material at hand WITNESSES.
2
+ //
3
+ // A stored form is WITNESSED by some material when every one of its bytes lies
4
+ // inside a W-window that occurs literally in that material — in any order, at
5
+ // any place. It is the order-free reading of correspondence (`alignRuns` is
6
+ // its run-producing sibling, `junctionContainersFrom(…, unordered)` its
7
+ // container-finding one): a learnt form is evidenced by bytes that hold all of
8
+ // its windows, whichever way the asker happened to arrange them.
9
+ //
10
+ // The material is a LIST of sources because what a derivation has at hand is
11
+ // more than the question: the node it stands on is material too. A trained
12
+ // question `Edmond T. Gréville place of death` is in neither the asker's
13
+ // `Where was the place of death of the director of film Beat Girl?` nor the
14
+ // fact `The director of Beat Girl is Edmond T. Gréville.` the first hop reached
15
+ // — and both together hold every byte of it. The witness records which source
16
+ // held each window, so a consumer can tell the question's share (what the asker
17
+ // SAID about the form) from what the derivation itself brought.
18
+ //
19
+ // Exact, deterministic and linear: one window index per source, one probe per
20
+ // window of the form. Below one window a byte agreement is chance (the floor
21
+ // identityBar, attestedQ and the site test all draw), so a form shorter than W
22
+ // is never witnessed.
23
+ //
24
+ // Layering: bytes only — importable from traverse.ts and everything above it.
25
+ import { latin1 } from "../bytes.js";
26
+ export function windowIndex(bytes, W) {
27
+ const index = new Map();
28
+ for (let o = 0; o + W <= bytes.length; o++) {
29
+ const key = latin1(bytes.subarray(o, o + W));
30
+ if (!index.has(key))
31
+ index.set(key, o);
32
+ }
33
+ return index;
34
+ }
35
+ /** The windows of `index` (over `bytes`) that `spoken` does not hold — what
36
+ * of the question no product of the derivation has restated yet. The keys
37
+ * keep their positions in `bytes`, so a witnessing span still names the
38
+ * asker's bytes. */
39
+ export function unspoken(index, spoken) {
40
+ const out = new Map();
41
+ for (const [key, at] of index) {
42
+ if (!spoken.some((s) => s.has(key)))
43
+ out.set(key, at);
44
+ }
45
+ return out;
46
+ }
47
+ /** Witness `form` against `sources` (their window indexes, same order). A
48
+ * window is credited to the LAST source holding it — so source 0 is credited
49
+ * only with what nothing else at hand supplies. */
50
+ export function witness(form, indexes, W) {
51
+ const none = { complete: false, spans: [], bytes: 0 };
52
+ if (form.length < W || indexes.length === 0)
53
+ return none;
54
+ const covered = new Uint8Array(form.length);
55
+ const own = [];
56
+ for (let o = 0; o + W <= form.length; o++) {
57
+ const key = latin1(form.subarray(o, o + W));
58
+ let at = -1;
59
+ let from = -1;
60
+ for (let s = indexes.length - 1; s >= 0; s--) {
61
+ const p = indexes[s].get(key);
62
+ if (p !== undefined) {
63
+ at = p;
64
+ from = s;
65
+ break;
66
+ }
67
+ }
68
+ if (from < 0)
69
+ continue;
70
+ covered.fill(1, o, o + W);
71
+ if (from === 0)
72
+ own.push([at, at + W]);
73
+ }
74
+ for (let i = 0; i < form.length; i++)
75
+ if (!covered[i])
76
+ return none;
77
+ own.sort((a, b) => a[0] - b[0]);
78
+ const spans = [];
79
+ for (const [s, e] of own) {
80
+ const last = spans[spans.length - 1];
81
+ if (last !== undefined && s <= last[1])
82
+ last[1] = Math.max(last[1], e);
83
+ else
84
+ spans.push([s, e]);
85
+ }
86
+ let bytes = 0;
87
+ for (const [s, e] of spans)
88
+ bytes += e - s;
89
+ return { complete: true, spans, bytes };
90
+ }
@@ -46,6 +46,11 @@ export type GItem = {
46
46
  * consolidated, more-explanatory reading wins over leaving the parts split.
47
47
  * See {@link GraphSearch.fuse} and {@link GraphSearch.formRules}. */
48
48
  rcmp?: boolean;
49
+ /** Set on a concept hop's ASKING form: the edge-less form `node` whose halo
50
+ * target the caller has not granted yet, held at the hop's own cost and
51
+ * span. Popping it asks for the target; it leads nowhere itself, so it
52
+ * is never part of a final derivation (see {@link Licence}). */
53
+ ask?: boolean;
49
54
  } | {
50
55
  kind: "out";
51
56
  i: number;
@@ -145,6 +150,45 @@ export interface DerivationItem {
145
150
  * conclusion shape (the rules carry no label, so this classifies by structure,
146
151
  * the single place that maps rule geometry to a name). */
147
152
  export type DerivationMove = "axiom" | "follow-edge" | "concept-hop" | "voice" | "ground" | "splice-connector" | "split" | "fuse" | "recompose" | "derive-through" | "bridge" | "pool-vote" | "step";
153
+ /** THE ASYNC PREMISES A COVER USES, RESOLVED ONLY WHERE THE SEARCH REACHES THEM.
154
+ *
155
+ * Two of the cover's rules need data the synchronous search cannot fetch — a
156
+ * connector splice (a bridge between two answers) and a concept hop (an
157
+ * edge-less form's halo sibling) — and both used to be resolved for EVERY
158
+ * candidate before the search ran. A rule fires only from popped premises,
159
+ * and the agenda pops in cost order up to the goal, so most of that work fed
160
+ * rules the search never reached: measured on the 31.7M-node store, a 262-byte
161
+ * query recognised whole paid 23 bridges (143,520 junction pops, 7.3 s of its
162
+ * 7.9 s) for sub-forms whose rewrites cost a STEP each and so never left the
163
+ * agenda before the one-STEP goal — and then 1.2 s of halo reads for concept
164
+ * hops priced at CONCEPT, ten times that goal.
165
+ *
166
+ * So the caller OFFERS what it can resolve; where the search reaches an offered
167
+ * key it uses a GRANTED value and ASKS for an ungranted one — a connector when
168
+ * its splice's premises meet, a concept target when the hop's conclusion, held
169
+ * as an asking form at the hop's own cost, is popped. A run that asked
170
+ * nothing is the run every key resolved in advance would have made: the pops
171
+ * are decided only by the rules fired from popped items, a granted key fires
172
+ * the very rule it fired before, and an asking form that was never popped fired
173
+ * none. So its cover is final. A run that asked is PROVISIONAL: the caller
174
+ * grants the asked keys (the async reads the search cannot run) and covers
175
+ * again; every round grants at least one offered key, so the rounds end. */
176
+ export interface Licence<K, V> {
177
+ /** Every key the caller can resolve. */
178
+ readonly offered: ReadonlySet<K>;
179
+ /** The keys resolved so far: their value, or null (nothing learnt). */
180
+ readonly granted: ReadonlyMap<K, V | null>;
181
+ /** Filled by the search: the offered keys it reached ungranted. */
182
+ readonly asked: Set<K>;
183
+ }
184
+ /** Connectors, keyed `L,R` by answer-node ids, valued by the learnt glue. */
185
+ export type ConnectorLicence = Licence<string, Uint8Array>;
186
+ /** Concept hops, keyed by an edge-less form's node, valued by the node its
187
+ * halo sibling's edge leads to. */
188
+ export type ConceptLicence = Licence<number, number>;
189
+ /** Nothing offered: a cover with no async premises (a nested completion,
190
+ * articulation's revoicing). */
191
+ export declare function noLicence<K, V>(): Licence<K, V>;
148
192
  /** The lightest-derivation search over the Sema graph. One instance binds the
149
193
  * store, `maxGroup` (the fusible span ceiling), and the canonical
150
194
  * {@link resolve} callback; {@link cover} then solves one query. */
@@ -162,13 +206,14 @@ export declare class GraphSearch {
162
206
  * recursive completion), and chooseNext (distributional-evidence edge
163
207
  * disambiguation when a recognised form has multiple continuations). */
164
208
  host: GraphSearchHost);
165
- /** The nodes the QUERY canonically names — the same identity the store's keys
166
- * were written through. A byte-exact test is not enough: the query writes
167
- * `Eiffel Tower country` and the deposited node is `eiffel tower country`, so
168
- * a join that filters the query's own subject by RAW bytes re-admits it —
169
- * measured: that is the trap's wrong answer (`The capital of Eiffel Tower
170
- * country is Berlin.`). Cached by query identity, because the search is
171
- * reused across responses. */
209
+ /** The hub bound √N (bounded-reads.md) — the ONE fan-out cap, stated here
210
+ * rather than imported from `traverse.ts` because this module is
211
+ * deliberately host-based (it holds a bare Store, never a MindContext).
212
+ * That is the same write/read-side duplication convention canonical.ts's
213
+ * header documents: if the formula changes it must change in BOTH places.
214
+ * It is stated ONCE per side, though — the expression used to be spelled
215
+ * out at three call sites here, one of them inside a per-item rules
216
+ * generator, and they had already drifted on the `Math.max(2, …)` floor. */
172
217
  private hubBound;
173
218
  /** Explore the Sema graph for the lightest cover of the query and return its
174
219
  * chosen spans left-to-right — WITH the derivation's total weight (the g
@@ -185,8 +230,10 @@ export declare class GraphSearch {
185
230
  *
186
231
  * Any learnt connector between two rewrites is spliced IN by the in-search
187
232
  * connector rule (see {@link outRules}), so the returned spans already carry
188
- * it — there is no post-pass. */
189
- cover(queryLen: number, sites: ReadonlyArray<Site>, conceptTarget: ReadonlyMap<number, number>, leaves: ReadonlyArray<Leaf>, splits: ReadonlySet<number>, substitutions?: ReadonlyMap<number, Uint8Array>, connectors?: ReadonlyMap<string, Uint8Array>, computedResults?: ReadonlyArray<ComputedResult>,
233
+ * it — there is no post-pass. With a {@link ConnectorLicence}, a cover
234
+ * returned while `connectors.asked` is non-empty is PROVISIONAL: grant the
235
+ * asked pairs and cover again. */
236
+ cover(queryLen: number, sites: ReadonlyArray<Site>, concepts: ConceptLicence, leaves: ReadonlyArray<Leaf>, splits: ReadonlySet<number>, substitutions?: ReadonlyMap<number, Uint8Array>, connectors?: ConnectorLicence, computedResults?: ReadonlyArray<ComputedResult>,
190
237
  /** When given, receives each solved span's lightest derivation — the full
191
238
  * adapted A*LD proof tree as classified {@link DerivationStep}s — for the
192
239
  * TOP cover AND every nested completion the sink is threaded into (see
@@ -262,11 +309,12 @@ export declare class GraphSearch {
262
309
  private bridgeRule;
263
310
  /** The connector-SPLICE rule for an oriented (l, r) pair, or null when the
264
311
  * pair does not qualify — the ONE body behind {@link outRules}' two
265
- * mirror loops (this-as-left over resolved right partners, this-as-right
266
- * over resolved left partners). Fires only when both sides are
312
+ * mirror loops (this-as-left over offered right partners, this-as-right
313
+ * over offered left partners). Fires only when both sides are
267
314
  * recognised, r starts at or after l ends, and the gap between them is
268
315
  * empty or wholly recognised — never across the asker's own literal
269
- * separator. */
316
+ * separator — and the pair's connector is granted and learnt. A qualifying
317
+ * pair not yet granted is ASKED for (see {@link ConnectorLicence}). */
270
318
  private trySplice;
271
319
  /** form(i,j,node,via): follow the graph out of `node`, or (in articulation)
272
320
  * emit its substitute voice directly. */
@@ -307,6 +355,14 @@ export declare class GraphSearch {
307
355
  * outs of a long query re-cover each distinct node at most once); reset at the
308
356
  * top of {@link cover}. */
309
357
  private recompleteMemo;
358
+ /** {@link entityProposals}, per fact BYTES — a pure function of them while
359
+ * the store is read-only (one response), so a fact the chart reaches as
360
+ * several items (cover/fix variants, nested completions) is scanned once.
361
+ * Reset at the top of {@link cover}, like {@link recompleteMemo}. */
362
+ private entityMemo;
363
+ /** The joins licensed under one cover's {@link Licence}, kept across its
364
+ * re-covers (see {@link solve}). */
365
+ private readonly joinsKept;
310
366
  /** The derivation sink of the TOP cover, threaded into every nested
311
367
  * completion so a produced form's own recompositions are reported in the
312
368
  * same trace instead of vanishing after the first layer. Undefined when
@@ -321,6 +377,21 @@ export declare class GraphSearch {
321
377
  * most once per cover. A Set, not a flag, because it states WHICH node is
322
378
  * open — the invariant a reader needs to check the guard. */
323
379
  private recompleteOpen;
380
+ /** The ENTITIES a produced fact's bytes contain that lead somewhere — the
381
+ * candidates a join may travel through. The forms the fact CONTAINS, by the
382
+ * same recogniser the query went through (so the evidence standard is the
383
+ * query's), plus — because the recognition of a STORED WHOLE returns the
384
+ * whole and stops (measured: one site, the fact's own node, for `The
385
+ * director of Eva is Gustaf Molander.`) — the longest canonically-resolving
386
+ * form at each offset. A byte atom is never a subject. The scan runs only
387
+ * for a FORM (≥ W: per letter it measured 20-26 s in test/99), and each probe
388
+ * is a `canonResolve` whose exact tier decides a miss by its segment probe,
389
+ * without a fold. The admission predicate is the store's `leadsSomewhere`
390
+ * (edge or halo); the host lends its memoised form when it can.
391
+ *
392
+ * The SOURCE of each proposal travels with it, so a refusal names which path
393
+ * proposed the candidate. Memoised per fact bytes ({@link entityMemo}). */
394
+ private entityProposals;
324
395
  /** DERIVE-THROUGH — a RULE this module's DeductionSystem was missing. The
325
396
  * A*LD library is untouched: this is one more `premises → conclusion + cost`
326
397
  * rule in the system {@link buildSearch} hands to {@link lightestDerivation},
@@ -335,7 +406,10 @@ export declare class GraphSearch {
335
406
  * fact reached through it. On the ladder it is one STEP: a direct edge,
336
407
  * exactly as following a literal continuation is. Deterministic and
337
408
  * point-probed (`resolve` + `nextFirst`, no scan), so it adds no read that
338
- * grows with the corpus. The move is visible in the rationale as its own act
409
+ * grows with the corpus. Asked ONCE per fact a lightest derivation stood on
410
+ * (the join license, {@link solve}) — never per fact the exploration merely
411
+ * reached — so the number of facts it prices is the answer's, not the
412
+ * corpus's. The move is visible in the rationale as its own act
339
413
  * (`classifyMove` reports `derive-through`), distinct from the
340
414
  * byte-concatenating `fuse`/`splice` — and named `derive-through` rather than
341
415
  * `join` so it cannot be read as the confluence mechanism's `Provenance`. */
@@ -343,8 +417,8 @@ export declare class GraphSearch {
343
417
  /** out(i,j,bytes,…): index it for the binary rules, then offer splicing a
344
418
  * learnt connector (the in-search bridge), splitting (at a sub-leaf form
345
419
  * boundary), bridging (cover(i) ∧ this → cover(j)), fusing with an adjacent
346
- * finalised out, and — for a produced fact — JOINING the entity it contains
347
- * with the query's tail ({@link deriveThrough}). */
420
+ * finalised out, and — for a LICENSED fact — JOINING the entity it contains
421
+ * with the query's tail ({@link deriveThrough}, granted in {@link solve}). */
348
422
  private outRules;
349
423
  /** Whether the query span [from, to) is wholly covered by RECOGNISED outs —
350
424
  * the test that lets a connector jump across INTERIOR answers (an N-ary whole)