@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.
- package/AGENTS.md +7 -7
- package/dist/src/alu/src/index.d.ts +1 -1
- package/dist/src/alu/src/index.js +1 -1
- package/dist/src/alu/src/parser.js +2 -6
- package/dist/src/alu/src/resonance.d.ts +13 -0
- package/dist/src/alu/src/resonance.js +41 -0
- package/dist/src/alu/test/alu.test.js +39 -0
- package/dist/src/bytes.d.ts +6 -2
- package/dist/src/bytes.js +10 -4
- package/dist/src/canon.js +44 -0
- package/dist/src/geometry.d.ts +19 -1
- package/dist/src/geometry.js +125 -141
- package/dist/src/meter.d.ts +33 -0
- package/dist/src/meter.js +34 -1
- package/dist/src/mind/articulation.js +14 -1
- package/dist/src/mind/attention.d.ts +12 -0
- package/dist/src/mind/attention.js +44 -16
- package/dist/src/mind/bridge.js +3 -3
- package/dist/src/mind/derivation.d.ts +40 -0
- package/dist/src/mind/derivation.js +34 -0
- package/dist/src/mind/evidence.d.ts +24 -0
- package/dist/src/mind/evidence.js +90 -0
- package/dist/src/mind/graph-search.d.ts +89 -15
- package/dist/src/mind/graph-search.js +345 -174
- package/dist/src/mind/learning.js +1 -1
- package/dist/src/mind/mechanisms/cover.d.ts +19 -3
- package/dist/src/mind/mechanisms/cover.js +142 -61
- package/dist/src/mind/mechanisms/recall.js +10 -3
- package/dist/src/mind/mind.d.ts +6 -0
- package/dist/src/mind/mind.js +5 -2
- package/dist/src/mind/pipeline.d.ts +5 -1
- package/dist/src/mind/pipeline.js +220 -90
- package/dist/src/mind/primitives.d.ts +25 -5
- package/dist/src/mind/primitives.js +107 -44
- package/dist/src/mind/reasoning.d.ts +18 -4
- package/dist/src/mind/reasoning.js +487 -328
- package/dist/src/mind/recognition.js +29 -13
- package/dist/src/mind/resonance.js +1 -11
- package/dist/src/mind/traverse.d.ts +45 -5
- package/dist/src/mind/traverse.js +285 -8
- package/dist/src/mind/types.d.ts +16 -1
- package/dist/src/store-sqlite.d.ts +25 -0
- package/dist/src/store-sqlite.js +89 -1
- package/dist/src/store.d.ts +48 -4
- package/dist/src/store.js +86 -6
- package/docs/INDEX.md +20 -19
- package/docs/INVARIANTS.md +17 -16
- package/docs/architecture/bounded-reads.md +1 -1
- package/docs/architecture/caches.md +5 -4
- package/docs/architecture/closure.md +45 -5
- package/docs/architecture/cost-model.md +16 -0
- package/docs/architecture/evidence.md +113 -0
- package/docs/architecture/exact-vs-approximate.md +10 -9
- package/docs/architecture/factored-machinery.md +14 -13
- package/docs/architecture/fold-contract.md +51 -1
- package/docs/architecture/mechanism-market.md +21 -0
- package/docs/architecture/memoization.md +3 -3
- package/docs/architecture/meter.md +2 -1
- package/docs/architecture/saturation.md +12 -0
- package/docs/architecture/store.md +25 -2
- package/docs/failures/tempting-but-wrong.md +13 -2
- package/docs/harness/gates.md +12 -10
- package/docs/mechanisms/cover.md +23 -6
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alu/README.md +10 -2
- package/src/alu/src/index.ts +1 -0
- package/src/alu/src/parser.ts +6 -6
- package/src/alu/src/resonance.ts +42 -0
- package/src/alu/test/alu.test.ts +40 -0
- package/src/bytes.ts +13 -3
- package/src/canon.ts +40 -0
- package/src/geometry.ts +183 -154
- package/src/meter.ts +34 -1
- package/src/mind/articulation.ts +14 -2
- package/src/mind/attention.ts +47 -25
- package/src/mind/bridge.ts +3 -3
- package/src/mind/derivation.ts +77 -0
- package/src/mind/evidence.ts +107 -0
- package/src/mind/graph-search.ts +449 -221
- package/src/mind/learning.ts +1 -7
- package/src/mind/match.ts +1 -2
- package/src/mind/mechanisms/cast.ts +1 -2
- package/src/mind/mechanisms/cover.ts +207 -87
- package/src/mind/mechanisms/extraction.ts +1 -2
- package/src/mind/mechanisms/prefix-completion.ts +1 -1
- package/src/mind/mechanisms/recall.ts +17 -5
- package/src/mind/mechanisms/reference.ts +1 -1
- package/src/mind/mind.ts +9 -30
- package/src/mind/pipeline.ts +263 -104
- package/src/mind/primitives.ts +119 -43
- package/src/mind/reasoning.ts +611 -419
- package/src/mind/recognition.ts +24 -9
- package/src/mind/resonance.ts +2 -16
- package/src/mind/trace.ts +1 -1
- package/src/mind/traverse.ts +321 -8
- package/src/mind/types.ts +15 -11
- package/src/store-sqlite.ts +92 -1
- package/src/store.ts +113 -7
- package/test/105-derive-through-reports-its-refusal.test.mjs +8 -5
- package/test/106-the-join-fires.test.mjs +21 -0
- package/test/111-the-cover-assembly-is-counted.test.mjs +8 -5
- package/test/128-the-leads-somewhere-pair-agrees.test.mjs +18 -12
- package/test/136-the-two-named-limits.test.mjs +3 -2
- package/test/137-the-law-lives-once-and-below.test.mjs +21 -0
- package/test/148-exact-shortcuts-agree.test.mjs +188 -0
- package/test/149-the-closure-engine.test.mjs +138 -0
- package/test/150-the-join-is-output-sensitive.test.mjs +66 -0
- package/test/151-the-cover-pays-for-what-it-reaches.test.mjs +142 -0
- package/test/152-the-read-side-names-as-the-write-side.test.mjs +146 -0
- package/test/153-a-cheaper-bound-is-looked-at-first.test.mjs +155 -0
- package/test/154-the-question-names-the-step.test.mjs +281 -0
- package/test/24-generalization.test.mjs +32 -0
- package/test/36-bloom.test.mjs +53 -0
- package/test/37-cluster-dispersion-fusion.test.mjs +75 -0
- package/test/48-recognise-turn-connective.test.mjs +3 -2
- package/test/55-cost-meter.test.mjs +4 -4
- 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
|
|
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
|
package/src/mind/articulation.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
131
|
+
noLicence(),
|
|
120
132
|
ans.leaves,
|
|
121
133
|
ans.splits,
|
|
122
134
|
substitutions,
|
package/src/mind/attention.ts
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
2108
|
-
|
|
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
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
)
|
|
2118
|
-
|
|
2119
|
-
|
|
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;
|
package/src/mind/bridge.ts
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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
|
-
// (
|
|
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).
|
package/src/mind/derivation.ts
CHANGED
|
@@ -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
|
+
}
|