@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
package/src/meter.ts CHANGED
@@ -143,6 +143,13 @@ export class Meter {
143
143
  perceivedBytes = 0;
144
144
  /** `perceive` calls served from the per-response / conversation memo. */
145
145
  perceiveHits = 0;
146
+ /** Bytes walked by the IDENTITY fold (`exactNode` — the fold's shape read
147
+ * for its node id, no vectors). Perception's content-addressed half: what
148
+ * used to show up in `perceivedBytes` when every resolve folded vectors. */
149
+ identityBytes = 0;
150
+ /** Branches the read side named by their BYTES — the flat node the write
151
+ * side reused when the children named none (primitives.ts `branchNaming`). */
152
+ flatBranchNames = 0;
146
153
  /** `recognise` calls that actually ran. */
147
154
  recognitions = 0;
148
155
  /** Bytes recognised by those calls. */
@@ -201,6 +208,10 @@ export class Meter {
201
208
  searchPops = 0;
202
209
  /** Chart items pushed by those searches. */
203
210
  searchPushes = 0;
211
+ /** Chart items popped DOMINATED — a span's form or completion that could
212
+ * only yield completions of that span costing at least one already yielded
213
+ * (graph-search.ts `buildSearch`), so no rule was generated from it. */
214
+ searchDominated = 0;
204
215
 
205
216
  // ── Mind: the mechanism market ──────────────────────────────────────────
206
217
  /** `floor()` calls that returned a bound (the mechanism could fire). */
@@ -209,6 +220,10 @@ export class Meter {
209
220
  mechanismSkips = 0;
210
221
  /** `run()` calls — the ones the floor pruning let through. */
211
222
  mechanismRuns = 0;
223
+ /** Mechanisms skipped because a mechanism floored lower, run AHEAD of them
224
+ * (pipeline.ts, "a cheaper bound is looked at before a dearer one is paid
225
+ * for"), already reached a grade their floor cannot beat. */
226
+ mechanismsBounded = 0;
212
227
  /** Candidates the decider weighed. */
213
228
  candidates = 0;
214
229
 
@@ -216,7 +231,7 @@ export class Meter {
216
231
  //
217
232
  // The join's outcome was observable ONLY through the rationale, and the
218
233
  // rationale PERTURBS the search (measured: appending text to a refusal note
219
- // changed a traced answer). These four counters are the untraced view — the
234
+ // changed a traced answer). These five counters are the untraced view — the
220
235
  // same surface every other work counter uses, incremented where the decision
221
236
  // is made, never behind a trace guard.
222
237
  /** `deriveThrough` yielded — a fact was reached through the subject the query
@@ -229,6 +244,11 @@ export class Meter {
229
244
  joinNoKey = 0;
230
245
  /** Refused: the fact contains no entity that leads anywhere. */
231
246
  joinNoEntity = 0;
247
+ /** Facts the join was priced for — the facts a lightest derivation stood on
248
+ * (graph-search.ts `solve`), each once. The output-sensitivity of the join
249
+ * in one number: it tracks the answer's facts, never how many facts the
250
+ * exploration reached (`test/150`). */
251
+ joinFacts = 0;
232
252
  /** `recompleteNode` re-covered a produced form — the descent that decomposes
233
253
  * a completion by ITS OWN kids. Without this the descent is invisible: a
234
254
  * caller could see the chain's result but not whether the recomposition
@@ -245,6 +265,19 @@ export class Meter {
245
265
  /** Times the reasoner pivoted on a span its answer contains and stepped
246
266
  * across that fact. */
247
267
  pivotSteps = 0;
268
+ /** `chooseNext` picks the question NAMED — a continuation one of whose own
269
+ * establishing contexts the question (plus the node) wholly witnesses
270
+ * (traverse.ts, the exact tier). */
271
+ askedContinuations = 0;
272
+ /** Predecessor rows that exact tier read, against its shared √N budget. */
273
+ askedPredecessorReads = 0;
274
+ /** Times the tier abstained on a read it could not trust: the continuations
275
+ * came back at the √N cap, or its predecessor budget ran out before every
276
+ * continuation was asked about — the distributional ladder decided. */
277
+ askedReadsSaturated = 0;
278
+ /** Cover sites dropped as FRAGMENTS whose several continuations the question
279
+ * names none of (mechanisms/cover.ts). */
280
+ unaskedFragments = 0;
248
281
  /** Canon probes REFUSED because the canon budget ran out — the one thing the
249
282
  * budget does that nothing could see. The budget itself is derived
250
283
  * (`bytes.length · chainReach(W)²`, recognition.ts), and the cheap exact route
@@ -4,12 +4,13 @@
4
4
  // using concept (halo) resonance to match the voices.
5
5
 
6
6
  import { Vec } from "../vec.js";
7
+ import { indexOf } from "../bytes.js";
7
8
  import type { MindContext } from "./types.js";
8
9
  import { spliceAll } from "./types.js";
9
10
  import { recognise } from "./recognition.js";
10
11
  import { answers, contains } from "./traverse.js";
11
12
  import { bestHaloMate } from "./match.js";
12
- import type { Site } from "./graph-search.js";
13
+ import { noLicence } from "./graph-search.js";
13
14
  import type { CandidateSpan } from "../derive/src/index.js";
14
15
  import { coverSequence } from "../derive/src/index.js";
15
16
  import { rItem, rNode, traceDerivation } from "./trace.js";
@@ -75,8 +76,19 @@ export async function articulate(
75
76
  const found = bestHaloMate(ctx, h, voices, (v) => v.halo);
76
77
  if (!found) continue;
77
78
  const voice = found.item;
79
+ // THE ASKER'S WORDING ALREADY HOLDING THE FORM is not a re-voicing of it:
80
+ // splicing it in would add the asker's other words to the answer. That is
81
+ // what `contains` asks of the DAG, and the DAG can miss it — the fold cuts
82
+ // `eva director` as `eva d|irector`, so `eva` is no structural child of it
83
+ // — so the question is also asked of the bytes, the reading that cannot
84
+ // miss. Measured (test/106's chain): the answer's `eva`, a halo mate of the
85
+ // asker's `eva director` because both lead to the same fact, was revoiced
86
+ // as "The director of eva director is Gustaf Molander."
87
+ const formBytes = store.bytesPrefix(s.payload, voice.bytes.length + 1);
78
88
  if (
79
89
  voice.node === s.payload || contains(ctx, voice.node, s.payload) ||
90
+ (formBytes.length <= voice.bytes.length &&
91
+ indexOf(voice.bytes, formBytes, 0) >= 0) ||
80
92
  answers(ctx, voice.node, s.payload)
81
93
  ) {
82
94
  continue;
@@ -116,7 +128,7 @@ export async function articulate(
116
128
  const solved = ctx.search.cover(
117
129
  answer.length,
118
130
  voicedSites,
119
- new Map(),
131
+ noLicence(),
120
132
  ans.leaves,
121
133
  ans.splits,
122
134
  substitutions,
@@ -34,14 +34,7 @@ import {
34
34
  estimatorNoise,
35
35
  type StructuralPart,
36
36
  } from "../geometry.js";
37
- import {
38
- foldTree,
39
- gistOf,
40
- latin1Key,
41
- perceive,
42
- read,
43
- resolve,
44
- } from "./primitives.js";
37
+ import { foldTree, gistOf, perceive, read, resolve } from "./primitives.js";
45
38
  import { recognise } from "./recognition.js";
46
39
  import { leafIdRun } from "./canonical.js";
47
40
  import {
@@ -63,10 +56,10 @@ import {
63
56
  walkCache,
64
57
  } from "./junction.js";
65
58
  import type { Vec } from "../vec.js";
66
- import { indexOf } from "../bytes.js";
59
+ import { indexOf, latin1 } from "../bytes.js";
67
60
  import { restates } from "./derivation.js";
68
61
  import type { RationaleItem } from "./rationale.js";
69
- import { rDeriv, rItem, rNode, traceDerivation } from "./trace.js";
62
+ import { rItem, rNode, traceDerivation } from "./trace.js";
70
63
 
71
64
  // ═══════════════════════════════════════════════════════════════════════════
72
65
  // climbConsensus / inspectRationale instrumentation.
@@ -501,7 +494,7 @@ export async function climbAttentionAll(
501
494
  // Content-keyed memo — works for both single-turn respond() and multi-turn
502
495
  // respondTurn().
503
496
  if (ctx.climbMemo) {
504
- const contentKey = latin1Key(query);
497
+ const contentKey = latin1(query);
505
498
  const modeKey = `${k}:${mode}`;
506
499
  let byRead = ctx.climbMemo.get(contentKey);
507
500
  if (byRead === undefined) {
@@ -1629,7 +1622,10 @@ export function poolVotes(
1629
1622
  * Strict `<` (not `<=`): verified against gap 3.1's own "gender equality"
1630
1623
  * root, whose two genuine clusters sit EXACTLY W bytes apart — `<= W`
1631
1624
  * would wrongly merge them into one and break that pinned requirement. */
1632
- function countClusters(spans: readonly [number, number][], W: number): number {
1625
+ export function countClusters(
1626
+ spans: readonly [number, number][],
1627
+ W: number,
1628
+ ): number {
1633
1629
  if (spans.length === 0) return 0;
1634
1630
  const sorted = [...spans].sort((a, b) => a[0] - b[0]);
1635
1631
  let clusters = 1;
@@ -2103,20 +2099,46 @@ export function canonicalChunkId(
2103
2099
  if (len < 2) return flatId;
2104
2100
 
2105
2101
  // Within one window, the widest reach is still the right CANONICAL
2106
- // identity — a chunk's anchor should be its most general stable form.
2107
- let bestId = flatId;
2108
- let bestReach = edgeAncestors(ctx, flatId, N, reachMemo);
2102
+ // identity — a chunk's anchor should be its most general stable form. A
2103
+ // SATURATED reach is the widest there is: its count is wherever the climb
2104
+ // happened to stop deciding, so an unsaturated prefix never out-widens it
2105
+ // by comparing against that count. (It used to: on test/24's gap-3.1
2106
+ // fixture the region `ace and ` had its saturated window replaced by the
2107
+ // unsaturated 2-byte prefix `e `, and that sub-window anchor voted.)
2108
+ //
2109
+ // So a window's anchor is its SHORTEST saturated form when any saturates,
2110
+ // and its widest-reaching form otherwise — and the forms are climbed
2111
+ // shortest first, stopping at the first that saturates: the forms longer
2112
+ // than it cannot change the anchor. Measured over 8 queries on the
2113
+ // 31.7M-node store: 339 of 347 windows saturate, 312 already at their
2114
+ // 2-byte prefix, while the full window and the 3-byte prefix — climbed
2115
+ // first, to √N contexts each — were 89% of the visits.
2116
+ const forms: number[] = [flatId];
2109
2117
  for (let k2 = 1; k2 < len; k2++) {
2110
- const shortIds = ids.slice(0, len - k2);
2111
- const shortId = ctx.store.findBranch(shortIds);
2112
- if (shortId === null) continue;
2113
- const shortReach = edgeAncestors(ctx, shortId, N, reachMemo);
2114
- if (
2115
- shortReach.saturated ||
2116
- shortReach.contextsReached > bestReach.contextsReached
2117
- ) {
2118
- bestId = shortId;
2119
- bestReach = shortReach;
2118
+ const shortId = ctx.store.findBranch(ids.slice(0, len - k2));
2119
+ if (shortId !== null) forms.push(shortId);
2120
+ }
2121
+ const reaches: AncestorReach[] = new Array(forms.length);
2122
+ let bestId: number | null = null;
2123
+ let bestReach: AncestorReach | null = null;
2124
+ for (let f = forms.length - 1; f >= 0; f--) {
2125
+ const r = edgeAncestors(ctx, forms[f], N, reachMemo);
2126
+ if (r.saturated) {
2127
+ bestId = forms[f];
2128
+ bestReach = r;
2129
+ break;
2130
+ }
2131
+ reaches[f] = r;
2132
+ }
2133
+ if (bestId === null || bestReach === null) {
2134
+ // None saturates: the widest reach, the longer form on a tie.
2135
+ bestId = forms[0];
2136
+ bestReach = reaches[0];
2137
+ for (let f = 1; f < forms.length; f++) {
2138
+ if (reaches[f].contextsReached > bestReach.contextsReached) {
2139
+ bestId = forms[f];
2140
+ bestReach = reaches[f];
2141
+ }
2120
2142
  }
2121
2143
  }
2122
2144
  if (fallback === null) fallback = bestId;
@@ -94,7 +94,7 @@ import { cosine, type Vec } from "../vec.js";
94
94
  import { conceptThreshold, dominates, significanceBar } from "../geometry.js";
95
95
  import { bytesEqual, indexOf } from "../bytes.js";
96
96
  import type { MindContext } from "./types.js";
97
- import { foldTree, perceive, read } from "./primitives.js";
97
+ import { exactNode, perceive, read } from "./primitives.js";
98
98
  import { chainReach, leafIdRun } from "./canonical.js";
99
99
  import {
100
100
  allWindowsAreScaffolding,
@@ -491,7 +491,7 @@ async function bridgeImpl(
491
491
  ) continue;
492
492
  let use = sid;
493
493
  if (!ctx.store.hasNext(use)) {
494
- const folded = foldTree(ctx, perceive(ctx, tb), 0).node;
494
+ const folded = exactNode(ctx, tb);
495
495
  if (folded === null || folded === sid || !ctx.store.hasNext(folded)) {
496
496
  continue;
497
497
  }
@@ -508,7 +508,7 @@ async function bridgeImpl(
508
508
  // be a FLAT content twin whose continuation edge lives on the
509
509
  // fold-shaped deposit node with the same bytes — the same twin split
510
510
  // canonResolve bridges by re-folding (primitives.ts) — but the re-fold
511
- // (a full perceive of the candidate's bytes) is paid only for proposals
511
+ // (an identity fold of the candidate's bytes) is paid only for proposals
512
512
  // that could align at all: alignment can only seed at a picked anchor
513
513
  // window occurring literally in the candidate (measured: unconditional
514
514
  // re-folds multiplied the refusal-path latency several-fold).
@@ -489,3 +489,80 @@ export async function closure(
489
489
  d = next;
490
490
  }
491
491
  }
492
+
493
+ // ── The engine: one closure over layers ─────────────────────────────────────
494
+
495
+ /** A LAYER of the closure — a named producer of continuations for one state.
496
+ * The layer OFFERS (its {@link Offer} is the whole of what it decides); the
497
+ * law admits; the engine sequences. The hooks are the layer's own
498
+ * instrumentation, called by the engine at the moments the law defines. */
499
+ export interface ClosureLayer {
500
+ /** The layer's name — its rationale scope and its meter phase. */
501
+ readonly name: string;
502
+ /** Whether the layer has anything to offer this state at all. A layer that
503
+ * does not engage is not ENTERED: no offer is asked and no work is charged
504
+ * to it. Omitted: it always engages. */
505
+ readonly engages?: (d: DerivationState) => boolean;
506
+ /** The layer's continuations, one at a time ({@link Offer}'s contract). */
507
+ readonly offer: Offer;
508
+ /** Called for each step the law admits, with the state before and after. */
509
+ readonly onTaken?: (
510
+ before: DerivationState,
511
+ after: DerivationState,
512
+ witnesses: ReadonlyArray<Witness>,
513
+ ) => void;
514
+ /** Called when the law refused the continuation the layer offered. */
515
+ readonly onRefused?: (at: DerivationState) => void;
516
+ /** Called once the layer's walk has ended — the layer offered nothing more,
517
+ * or the law refused — with the state it began from and the one it reached. */
518
+ readonly onEnd?: (from: DerivationState, to: DerivationState) => void;
519
+ }
520
+
521
+ /** THE CLOSURE ENGINE — close a derivation under the law, layer by layer.
522
+ *
523
+ * Each layer is walked to its own end ({@link closure}: the layer offers, the
524
+ * law admits or refuses), and the state it reaches is the state the next layer
525
+ * is offered against. Layers are PHASES, in the order given, never revisited:
526
+ * a later layer composes what an earlier one produced, and nothing it adds is
527
+ * re-offered to the earlier one.
528
+ *
529
+ * THE LAW'S FIRST CLAUSE IS APPLIED HERE, ONCE. A FIXED state admits no
530
+ * transition (`admissible` refuses it before anything else), so no layer is
531
+ * entered at all — the decision a caller used to spell as "skip the walk for a
532
+ * complete grounding, and skip the fusion too" is the law's, not the caller's,
533
+ * and it is taken before any layer pays for an offer it could never have
534
+ * taken.
535
+ *
536
+ * `enter` wraps each entered layer's walk — the caller's instrumentation (a
537
+ * meter phase), which this module cannot import: it reads nothing but the
538
+ * state and the layers. */
539
+ export async function closeOver(
540
+ d: DerivationState,
541
+ query: Uint8Array,
542
+ W: number,
543
+ layers: ReadonlyArray<ClosureLayer>,
544
+ enter?: (
545
+ name: string,
546
+ walk: () => Promise<DerivationState>,
547
+ ) => Promise<DerivationState>,
548
+ ): Promise<DerivationState> {
549
+ for (const layer of layers) {
550
+ if (d.fixed) return d;
551
+ if (layer.engages !== undefined && !layer.engages(d)) continue;
552
+ const from = d;
553
+ const walk = async (): Promise<DerivationState> => {
554
+ const to = await closure(
555
+ from,
556
+ query,
557
+ W,
558
+ layer.offer,
559
+ layer.onTaken,
560
+ layer.onRefused,
561
+ );
562
+ layer.onEnd?.(from, to);
563
+ return to;
564
+ };
565
+ d = enter === undefined ? await walk() : await enter(layer.name, walk);
566
+ }
567
+ return d;
568
+ }
@@ -0,0 +1,107 @@
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
+
26
+ import { latin1 } from "../bytes.js";
27
+
28
+ /** Where each W-window of one source first occurs. Built once per source and
29
+ * reused across every form asked against it. */
30
+ export type WindowIndex = Map<string, number>;
31
+
32
+ export function windowIndex(bytes: Uint8Array, W: number): WindowIndex {
33
+ const index: WindowIndex = new Map();
34
+ for (let o = 0; o + W <= bytes.length; o++) {
35
+ const key = latin1(bytes.subarray(o, o + W));
36
+ if (!index.has(key)) index.set(key, o);
37
+ }
38
+ return index;
39
+ }
40
+
41
+ /** The windows of `index` (over `bytes`) that `spoken` does not hold — what
42
+ * of the question no product of the derivation has restated yet. The keys
43
+ * keep their positions in `bytes`, so a witnessing span still names the
44
+ * asker's bytes. */
45
+ export function unspoken(
46
+ index: WindowIndex,
47
+ spoken: ReadonlyArray<WindowIndex>,
48
+ ): WindowIndex {
49
+ const out: WindowIndex = new Map();
50
+ for (const [key, at] of index) {
51
+ if (!spoken.some((s) => s.has(key))) out.set(key, at);
52
+ }
53
+ return out;
54
+ }
55
+
56
+ /** How a form is witnessed by a list of sources. */
57
+ export interface Witnessing {
58
+ /** Every byte of the form lies in a window some source holds. */
59
+ complete: boolean;
60
+ /** The windows SOURCE 0 alone supplied — windows no later source holds — as
61
+ * merged spans of source 0. With the question as source 0, this is what the
62
+ * asker said about the form that the derivation did not already have. */
63
+ spans: Array<[number, number]>;
64
+ /** Bytes of source 0 inside `spans`. */
65
+ bytes: number;
66
+ }
67
+
68
+ /** Witness `form` against `sources` (their window indexes, same order). A
69
+ * window is credited to the LAST source holding it — so source 0 is credited
70
+ * only with what nothing else at hand supplies. */
71
+ export function witness(
72
+ form: Uint8Array,
73
+ indexes: ReadonlyArray<WindowIndex>,
74
+ W: number,
75
+ ): Witnessing {
76
+ const none: Witnessing = { complete: false, spans: [], bytes: 0 };
77
+ if (form.length < W || indexes.length === 0) return none;
78
+ const covered = new Uint8Array(form.length);
79
+ const own: Array<[number, number]> = [];
80
+ for (let o = 0; o + W <= form.length; o++) {
81
+ const key = latin1(form.subarray(o, o + W));
82
+ let at = -1;
83
+ let from = -1;
84
+ for (let s = indexes.length - 1; s >= 0; s--) {
85
+ const p = indexes[s].get(key);
86
+ if (p !== undefined) {
87
+ at = p;
88
+ from = s;
89
+ break;
90
+ }
91
+ }
92
+ if (from < 0) continue;
93
+ covered.fill(1, o, o + W);
94
+ if (from === 0) own.push([at, at + W]);
95
+ }
96
+ for (let i = 0; i < form.length; i++) if (!covered[i]) return none;
97
+ own.sort((a, b) => a[0] - b[0]);
98
+ const spans: Array<[number, number]> = [];
99
+ for (const [s, e] of own) {
100
+ const last = spans[spans.length - 1];
101
+ if (last !== undefined && s <= last[1]) last[1] = Math.max(last[1], e);
102
+ else spans.push([s, e]);
103
+ }
104
+ let bytes = 0;
105
+ for (const [s, e] of spans) bytes += e - s;
106
+ return { complete: true, spans, bytes };
107
+ }