@hviana/sema 0.3.0 → 0.4.0
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 +71 -5
- package/dist/src/derive/src/deduction.d.ts +12 -1
- package/dist/src/derive/src/deduction.js +5 -1
- package/dist/src/derive/src/index.d.ts +1 -0
- package/dist/src/geometry.d.ts +3 -30
- package/dist/src/geometry.js +330 -82
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/meter.d.ts +171 -0
- package/dist/src/meter.js +269 -0
- package/dist/src/mind/attention.d.ts +0 -4
- package/dist/src/mind/attention.js +251 -23
- package/dist/src/mind/bridge.d.ts +10 -1
- package/dist/src/mind/bridge.js +179 -10
- package/dist/src/mind/canonical.d.ts +6 -1
- package/dist/src/mind/canonical.js +6 -1
- package/dist/src/mind/graph-search.d.ts +9 -0
- package/dist/src/mind/graph-search.js +59 -19
- package/dist/src/mind/junction.d.ts +10 -0
- package/dist/src/mind/junction.js +14 -0
- package/dist/src/mind/match.d.ts +40 -0
- package/dist/src/mind/match.js +125 -1
- package/dist/src/mind/mechanisms/cast.js +63 -6
- package/dist/src/mind/mechanisms/extraction.d.ts +0 -34
- package/dist/src/mind/mechanisms/extraction.js +1 -88
- package/dist/src/mind/mechanisms/recall.d.ts +3 -0
- package/dist/src/mind/mechanisms/recall.js +77 -14
- package/dist/src/mind/mind.d.ts +55 -4
- package/dist/src/mind/mind.js +110 -91
- package/dist/src/mind/pipeline-mechanism.d.ts +33 -3
- package/dist/src/mind/pipeline-mechanism.js +179 -10
- package/dist/src/mind/pipeline.js +52 -10
- package/dist/src/mind/primitives.d.ts +11 -15
- package/dist/src/mind/primitives.js +47 -28
- package/dist/src/mind/reasoning.d.ts +7 -1
- package/dist/src/mind/reasoning.js +40 -8
- package/dist/src/mind/recognition.js +93 -20
- package/dist/src/mind/traverse.d.ts +11 -0
- package/dist/src/mind/traverse.js +58 -5
- package/dist/src/mind/types.d.ts +28 -5
- package/dist/src/store.d.ts +15 -0
- package/dist/src/store.js +91 -6
- package/package.json +1 -1
- package/src/derive/src/deduction.ts +15 -0
- package/src/derive/src/index.ts +1 -0
- package/src/geometry.ts +350 -122
- package/src/index.ts +1 -0
- package/src/meter.ts +333 -0
- package/src/mind/attention.ts +268 -31
- package/src/mind/bridge.ts +187 -10
- package/src/mind/canonical.ts +6 -1
- package/src/mind/graph-search.ts +60 -21
- package/src/mind/junction.ts +12 -0
- package/src/mind/match.ts +146 -1
- package/src/mind/mechanisms/cast.ts +62 -6
- package/src/mind/mechanisms/extraction.ts +2 -103
- package/src/mind/mechanisms/recall.ts +84 -17
- package/src/mind/mind.ts +139 -98
- package/src/mind/pipeline-mechanism.ts +203 -13
- package/src/mind/pipeline.ts +65 -8
- package/src/mind/primitives.ts +49 -33
- package/src/mind/reasoning.ts +39 -7
- package/src/mind/recognition.ts +89 -19
- package/src/mind/traverse.ts +59 -5
- package/src/mind/types.ts +28 -5
- package/src/store.ts +75 -6
- package/test/14-scaling.test.mjs +17 -7
- package/test/31-audit.test.mjs +4 -1
- package/test/33-multi-candidate.test.mjs +13 -1
- package/test/36-already-answered-fusion.test.mjs +10 -3
- package/test/46-recognise-multibyte-edge.test.mjs +3 -3
- package/test/53-cross-region-probe-instrumentation.test.mjs +36 -6
- package/test/55-cost-meter.test.mjs +284 -0
- package/test/56-bridge-identity-admission.test.mjs +209 -0
- package/test/57-fusion-order.test.mjs +104 -0
- package/test/58-subquantum-sites.test.mjs +112 -0
- package/test/59-fold-invariance.test.mjs +226 -0
package/src/mind/types.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { BoundedMap, Store } from "../store.js";
|
|
|
9
9
|
import type { Space } from "../sema.js";
|
|
10
10
|
import type { Alphabet } from "../alphabet.js";
|
|
11
11
|
import type { MindConfig } from "../config.js";
|
|
12
|
+
import type { Meter } from "../meter.js";
|
|
12
13
|
import type {
|
|
13
14
|
ComputedResult,
|
|
14
15
|
DerivationItem,
|
|
@@ -19,7 +20,7 @@ import type {
|
|
|
19
20
|
Site,
|
|
20
21
|
} from "./graph-search.js";
|
|
21
22
|
import type { Rationale } from "./rationale.js";
|
|
22
|
-
import type {
|
|
23
|
+
import type { Grid, StableFold } from "../geometry.js";
|
|
23
24
|
|
|
24
25
|
/** One {@link MindContext._depositTrees} entry — see that field's doc. */
|
|
25
26
|
export interface DepositCacheEntry {
|
|
@@ -27,8 +28,6 @@ export interface DepositCacheEntry {
|
|
|
27
28
|
* strictly increasing proper offsets, each a previously-deposited
|
|
28
29
|
* whole-context length. Empty for a first-seen (single-turn) input. */
|
|
29
30
|
boundaries: number[];
|
|
30
|
-
/** Plain-fold pyramid (first-seen inputs only). */
|
|
31
|
-
pyramid?: FoldPyramid;
|
|
32
31
|
/** Stable-prefix segment folds (grown-context inputs only). */
|
|
33
32
|
stable?: StableFold;
|
|
34
33
|
/** The continuation bytes this ctxInput was paired with in ingestPair, if
|
|
@@ -59,6 +58,10 @@ export type Input = string | Uint8Array | Grid | Grid[];
|
|
|
59
58
|
/** The host capabilities GraphSearch consults during a cover. MindContext
|
|
60
59
|
* extends this so the Mind can pass itself as the host. */
|
|
61
60
|
export interface GraphSearchHost {
|
|
61
|
+
/** Work accumulator, or null/absent when nothing is profiling — see
|
|
62
|
+
* src/meter.ts. Declared here (not only on MindContext) so the graph
|
|
63
|
+
* search can report its chart effort without importing mind code. */
|
|
64
|
+
readonly meter?: Meter | null;
|
|
62
65
|
resolve(bytes: Uint8Array): number | null;
|
|
63
66
|
recogniseSpan?(bytes: Uint8Array): {
|
|
64
67
|
sites: ReadonlyArray<Site>;
|
|
@@ -173,6 +176,19 @@ export interface RegionVote {
|
|
|
173
176
|
* to "one region" just because it collapsed to one pooled axiom.
|
|
174
177
|
* Defaults to 1 when absent. */
|
|
175
178
|
absorbed?: number;
|
|
179
|
+
/** The SEPARATE query places this vote's evidence occupies, when that is
|
|
180
|
+
* more than the one contiguous run [start, end]. A cross-region junction
|
|
181
|
+
* vote is pooled as a single synthetic region spanning its endpoints and
|
|
182
|
+
* the gap between them, so `[start, end]` reads as ONE place — yet the
|
|
183
|
+
* vote exists precisely because two non-adjacent regions each voted and
|
|
184
|
+
* only their conjunction resolved. Cluster counting (Attention.clusters)
|
|
185
|
+
* asks "how many separate places in the query corroborate this?", and
|
|
186
|
+
* answering it from the merged span makes every joint binding look like a
|
|
187
|
+
* single local neighbourhood; fusion's dispersion gate then drops it
|
|
188
|
+
* unless it also explains most of the whole query, which a binding inside
|
|
189
|
+
* a MULTI-topic query structurally cannot. Absent for an ordinary
|
|
190
|
+
* per-region vote, where the merged span already is the truth. */
|
|
191
|
+
parts?: readonly (readonly [number, number])[];
|
|
176
192
|
}
|
|
177
193
|
|
|
178
194
|
/** The structural gate that first decided an {@link edgeAncestors} climb was
|
|
@@ -237,6 +253,12 @@ export type AItem =
|
|
|
237
253
|
|
|
238
254
|
export interface MindContext extends GraphSearchHost {
|
|
239
255
|
store: Store;
|
|
256
|
+
/** The work accumulator for the inference call in flight, or null when
|
|
257
|
+
* nothing is profiling — see src/meter.ts. WRITE-ONLY from the engine's
|
|
258
|
+
* point of view: no inference decision may read a counter, or the
|
|
259
|
+
* determinism contract (AGENTS §2.1) is gone. Every call site is
|
|
260
|
+
* `ctx.meter?.x++`, so an unprofiled response allocates nothing. */
|
|
261
|
+
meter: Meter | null;
|
|
240
262
|
space: Space;
|
|
241
263
|
alphabet: Alphabet;
|
|
242
264
|
cfg: MindConfig;
|
|
@@ -294,8 +316,9 @@ export interface MindContext extends GraphSearchHost {
|
|
|
294
316
|
* folds with the SAME stable-prefix fold query-time perception uses
|
|
295
317
|
* (structural train/inference agreement, load-bearing for recall),
|
|
296
318
|
* reusing every already-folded segment via `stable` (see StableFold) —
|
|
297
|
-
* O(turn) per deposit instead of O(context). A first-seen input
|
|
298
|
-
*
|
|
319
|
+
* O(turn) per deposit instead of O(context). A first-seen input takes the
|
|
320
|
+
* same fold with no boundaries at all, and caches the segments it produced
|
|
321
|
+
* so a later turn of the same conversation reuses them. Purely a
|
|
299
322
|
* performance cache for the FOLD STATE; the boundaries are semantic but
|
|
300
323
|
* derived only from the deposit sequence itself (an evicted chain falls
|
|
301
324
|
* back to plain-fold behavior, exactly the pre-boundary shape). */
|
package/src/store.ts
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
import { addInto, copy, dot, normalize, Vec } from "./vec.js";
|
|
32
32
|
import { DEFAULT_CONFIG, type StoreConfig } from "./config.js";
|
|
33
33
|
import { identityBar } from "./geometry.js";
|
|
34
|
+
import type { Meter } from "./meter.js";
|
|
34
35
|
|
|
35
36
|
/** A node id: a dense, non-negative integer assigned in creation order. */
|
|
36
37
|
export type NodeId = number;
|
|
@@ -226,6 +227,14 @@ export class BoundedMap<K, V> {
|
|
|
226
227
|
export interface Store {
|
|
227
228
|
readonly D: number;
|
|
228
229
|
|
|
230
|
+
/** The work accumulator for the inference call in flight, or null. The
|
|
231
|
+
* Mind attaches one per profiled response and detaches it after (see
|
|
232
|
+
* src/meter.ts). A store MUST only ever write to it — no read may reach
|
|
233
|
+
* a decision, or determinism is gone. An adapter that adds reads of its
|
|
234
|
+
* own should bump the matching counter; one that does not simply reports
|
|
235
|
+
* less, never wrongly. */
|
|
236
|
+
meter: Meter | null;
|
|
237
|
+
|
|
229
238
|
// ── the DAG (hash-consed) ──────────────────────────────────────────────
|
|
230
239
|
/** Insert a leaf, returning its content id. Idempotent. */
|
|
231
240
|
putLeaf(bytes: Uint8Array, gist: Vec): Promise<NodeId>;
|
|
@@ -1010,6 +1019,7 @@ export abstract class AbstractStore implements Store {
|
|
|
1010
1019
|
// ── DAG traversal ──────────────────────────────────────────────────────
|
|
1011
1020
|
|
|
1012
1021
|
get(id: NodeId): NodeRec | null {
|
|
1022
|
+
if (this.meter) this.meter.nodeRecords++;
|
|
1013
1023
|
// Byte leaves are implicit — fabricate from the id.
|
|
1014
1024
|
if (id < 0) {
|
|
1015
1025
|
return { id, leaf: new Uint8Array([-(id + 1)]), kids: null };
|
|
@@ -1030,7 +1040,20 @@ export abstract class AbstractStore implements Store {
|
|
|
1030
1040
|
* counter is what keeps that degradation observable. */
|
|
1031
1041
|
danglingReads = 0;
|
|
1032
1042
|
|
|
1043
|
+
/** {@link Store.meter} — the per-response work accumulator, or null when
|
|
1044
|
+
* nothing is profiling. Every read below bumps it through `?.`, so an
|
|
1045
|
+
* unprofiled store pays one null check per read and allocates nothing. */
|
|
1046
|
+
meter: Meter | null = null;
|
|
1047
|
+
|
|
1033
1048
|
bytes(id: NodeId): Uint8Array {
|
|
1049
|
+
if (this.meter) {
|
|
1050
|
+
this.meter.byteReads++;
|
|
1051
|
+
// Charged BEFORE the fast path returns, and from the cache entry when
|
|
1052
|
+
// there is one: the volume a caller pulled through is the cost signal
|
|
1053
|
+
// (an unbounded read is unbounded whether or not it was cached).
|
|
1054
|
+
const c = this._bytesCache.get(id);
|
|
1055
|
+
if (c) this.meter.bytesRead += c.length;
|
|
1056
|
+
}
|
|
1034
1057
|
// Fast path.
|
|
1035
1058
|
const hit = this._bytesCache.get(id);
|
|
1036
1059
|
if (hit) return hit;
|
|
@@ -1082,7 +1105,9 @@ export abstract class AbstractStore implements Store {
|
|
|
1082
1105
|
cache.set(nid, out);
|
|
1083
1106
|
}
|
|
1084
1107
|
|
|
1085
|
-
|
|
1108
|
+
const out = cache.get(id) ?? _ZERO;
|
|
1109
|
+
if (this.meter) this.meter.bytesRead += out.length;
|
|
1110
|
+
return out;
|
|
1086
1111
|
}
|
|
1087
1112
|
|
|
1088
1113
|
/** First `maxLen` bytes of a node. Walks only the leftmost branch,
|
|
@@ -1093,14 +1118,27 @@ export abstract class AbstractStore implements Store {
|
|
|
1093
1118
|
* may be shared with the byte cache and with other callers — treat them
|
|
1094
1119
|
* as read-only. Mutating one would corrupt every subsequent read. */
|
|
1095
1120
|
bytesPrefix(id: NodeId, maxLen: number): Uint8Array {
|
|
1096
|
-
if (maxLen <= 0) return _ZERO;
|
|
1097
|
-
|
|
1098
1121
|
// A FULL read (the ALL sentinel) routes through bytes(), whose
|
|
1099
1122
|
// reconstruction enters the byte-budget cache. Without this, the mind's
|
|
1100
1123
|
// read() — which always passes ALL — re-walked the DAG and re-concatenated
|
|
1101
1124
|
// on EVERY repeated read of an uncached branch, bypassing the cache that
|
|
1102
1125
|
// exists precisely for those reconstructions.
|
|
1103
1126
|
if (maxLen >= 0x7fffffff) return this.bytes(id);
|
|
1127
|
+
// METERING BOUNDARY: this public entry point is charged ONCE per logical
|
|
1128
|
+
// read; the walk below recurses through `_prefix`, which is not charged.
|
|
1129
|
+
// Counting the recursion instead made a single read of an N-byte branch
|
|
1130
|
+
// report as N reads (one per node descended), so `byteReads` measured
|
|
1131
|
+
// tree size rather than read requests — it read as ~1 byte per read.
|
|
1132
|
+
if (this.meter) this.meter.byteReads++;
|
|
1133
|
+
const out = this._prefix(id, maxLen);
|
|
1134
|
+
if (this.meter) this.meter.bytesRead += out.length;
|
|
1135
|
+
return out;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/** {@link bytesPrefix}'s recursive body — uncharged; see the metering
|
|
1139
|
+
* boundary note there. */
|
|
1140
|
+
private _prefix(id: NodeId, maxLen: number): Uint8Array {
|
|
1141
|
+
if (maxLen <= 0) return _ZERO;
|
|
1104
1142
|
|
|
1105
1143
|
// Full-cache hit: bytes() already reconstructed the whole node.
|
|
1106
1144
|
const full = this._bytesCache.get(id);
|
|
@@ -1125,7 +1163,7 @@ export abstract class AbstractStore implements Store {
|
|
|
1125
1163
|
let got = 0;
|
|
1126
1164
|
for (const k of kids) {
|
|
1127
1165
|
if (got >= maxLen) break;
|
|
1128
|
-
const child = this.
|
|
1166
|
+
const child = this._prefix(k, maxLen - got);
|
|
1129
1167
|
parts.push(child);
|
|
1130
1168
|
got += child.length;
|
|
1131
1169
|
}
|
|
@@ -1133,6 +1171,7 @@ export abstract class AbstractStore implements Store {
|
|
|
1133
1171
|
}
|
|
1134
1172
|
|
|
1135
1173
|
contentLen(id: NodeId, cap = Infinity): number {
|
|
1174
|
+
if (this.meter) this.meter.lenReads++;
|
|
1136
1175
|
if (id < 0) return 1; // implicit single-byte leaf
|
|
1137
1176
|
const hit = this._lenCache.get(id);
|
|
1138
1177
|
if (hit !== undefined) return hit; // exact — valid under any cap
|
|
@@ -1159,6 +1198,7 @@ export abstract class AbstractStore implements Store {
|
|
|
1159
1198
|
// ── Content-addressed lookup ───────────────────────────────────────────
|
|
1160
1199
|
|
|
1161
1200
|
findLeaf(bytes: Uint8Array): NodeId | null {
|
|
1201
|
+
if (this.meter) this.meter.leafLookups++;
|
|
1162
1202
|
if (bytes.length === 1) return -(bytes[0] + 1);
|
|
1163
1203
|
const key = keyOf(bytes);
|
|
1164
1204
|
const cached = this._leafKey.get(key);
|
|
@@ -1169,6 +1209,7 @@ export abstract class AbstractStore implements Store {
|
|
|
1169
1209
|
}
|
|
1170
1210
|
|
|
1171
1211
|
findBranch(kids: NodeId[]): NodeId | null {
|
|
1212
|
+
if (this.meter) this.meter.branchLookups++;
|
|
1172
1213
|
const key = kids.join(",");
|
|
1173
1214
|
const cached = this._branchKey.get(key);
|
|
1174
1215
|
if (cached !== undefined) return cached;
|
|
@@ -1189,18 +1230,22 @@ export abstract class AbstractStore implements Store {
|
|
|
1189
1230
|
// ── Structural parents ─────────────────────────────────────────────────
|
|
1190
1231
|
|
|
1191
1232
|
parents(id: NodeId): NodeId[] {
|
|
1233
|
+
if (this.meter) this.meter.parentReads++;
|
|
1192
1234
|
return this._dbGetParents(id);
|
|
1193
1235
|
}
|
|
1194
1236
|
|
|
1195
1237
|
parentsFirst(id: NodeId, limit: number): NodeId[] {
|
|
1238
|
+
if (this.meter) this.meter.parentReads++;
|
|
1196
1239
|
return this._dbGetParentsFirst(id, limit);
|
|
1197
1240
|
}
|
|
1198
1241
|
|
|
1199
1242
|
hasParents(id: NodeId): boolean {
|
|
1243
|
+
if (this.meter) this.meter.parentProbes++;
|
|
1200
1244
|
return this._dbGetParentsFirst(id, 1).length > 0;
|
|
1201
1245
|
}
|
|
1202
1246
|
|
|
1203
1247
|
chainRun(id: NodeId): readonly NodeId[] {
|
|
1248
|
+
if (this.meter) this.meter.chainRuns++;
|
|
1204
1249
|
const hit = this._chainMemo.get(id);
|
|
1205
1250
|
if (hit !== undefined) return hit;
|
|
1206
1251
|
const run = this._chainWalk(id, CHAIN_DEPTH_CAP);
|
|
@@ -1236,12 +1281,14 @@ export abstract class AbstractStore implements Store {
|
|
|
1236
1281
|
}
|
|
1237
1282
|
|
|
1238
1283
|
hasContainers(child: NodeId): boolean {
|
|
1284
|
+
if (this.meter) this.meter.containerProbes++;
|
|
1239
1285
|
if (this._dbContainExists(child)) return true;
|
|
1240
1286
|
const buf = this._containBuf.get(child);
|
|
1241
1287
|
return buf !== undefined && buf.size > 0;
|
|
1242
1288
|
}
|
|
1243
1289
|
|
|
1244
1290
|
containersSlice(child: NodeId, offset: number, limit: number): NodeId[] {
|
|
1291
|
+
if (this.meter) this.meter.containerReads++;
|
|
1245
1292
|
const out = this._dbGetContainParentsSlice(child, offset, limit);
|
|
1246
1293
|
if (out.length >= limit) return out;
|
|
1247
1294
|
// Buffered adds page in AFTER the stored ones. A buffered parent that is
|
|
@@ -1262,6 +1309,7 @@ export abstract class AbstractStore implements Store {
|
|
|
1262
1309
|
}
|
|
1263
1310
|
|
|
1264
1311
|
containers(child: NodeId): NodeId[] {
|
|
1312
|
+
if (this.meter) this.meter.containerReads++;
|
|
1265
1313
|
const stored = this._dbGetContainParents(child);
|
|
1266
1314
|
const buf = this._containBuf.get(child);
|
|
1267
1315
|
if (stored.length === 0) return buf ? [...buf] : [];
|
|
@@ -1662,9 +1710,13 @@ export abstract class AbstractStore implements Store {
|
|
|
1662
1710
|
const cache = this._resonateCache;
|
|
1663
1711
|
if (cache) {
|
|
1664
1712
|
const hit = cache.get(rk);
|
|
1665
|
-
if (hit !== undefined)
|
|
1713
|
+
if (hit !== undefined) {
|
|
1714
|
+
if (this.meter) this.meter.annCacheHits++;
|
|
1715
|
+
return hit;
|
|
1716
|
+
}
|
|
1666
1717
|
}
|
|
1667
1718
|
|
|
1719
|
+
if (this.meter) this.meter.annQueries++;
|
|
1668
1720
|
const clusters = this._vecContentClusterCount();
|
|
1669
1721
|
const results = this._vecContentQuery(
|
|
1670
1722
|
normalize(copy(v)),
|
|
@@ -1682,6 +1734,10 @@ export abstract class AbstractStore implements Store {
|
|
|
1682
1734
|
if (out.length >= k) break;
|
|
1683
1735
|
}
|
|
1684
1736
|
|
|
1737
|
+
// Vectors the index actually scored for THIS descent — the counter that
|
|
1738
|
+
// exposes a query whose ANN cost is growing with the corpus.
|
|
1739
|
+
if (this.meter) this.meter.annVectorReads += this._vecContentLastReads();
|
|
1740
|
+
|
|
1685
1741
|
const rc = this._resonateCache ??= new Map();
|
|
1686
1742
|
if (rc.size >= RESONATE_CACHE_MAX) rc.clear();
|
|
1687
1743
|
rc.set(rk, out);
|
|
@@ -1924,29 +1980,35 @@ export abstract class AbstractStore implements Store {
|
|
|
1924
1980
|
}
|
|
1925
1981
|
|
|
1926
1982
|
next(id: NodeId): NodeId[] {
|
|
1983
|
+
if (this.meter) this.meter.edgeReads++;
|
|
1927
1984
|
return this._dbGetNextEdges(id);
|
|
1928
1985
|
}
|
|
1929
1986
|
|
|
1930
1987
|
/** {@link Store.hasNext} — one indexed point probe, never a range read. */
|
|
1931
1988
|
hasNext(id: NodeId): boolean {
|
|
1989
|
+
if (this.meter) this.meter.edgeProbes++;
|
|
1932
1990
|
return this._dbEdgeSrcExists(id);
|
|
1933
1991
|
}
|
|
1934
1992
|
|
|
1935
1993
|
prev(id: NodeId): NodeId[] {
|
|
1994
|
+
if (this.meter) this.meter.prevReads++;
|
|
1936
1995
|
return this._dbGetPrevEdges(id);
|
|
1937
1996
|
}
|
|
1938
1997
|
|
|
1939
1998
|
nextFirst(id: NodeId, limit: number): NodeId[] {
|
|
1999
|
+
if (this.meter) this.meter.edgeReads++;
|
|
1940
2000
|
return this._dbGetNextEdgesFirst(id, limit);
|
|
1941
2001
|
}
|
|
1942
2002
|
|
|
1943
2003
|
prevFirst(id: NodeId, limit: number): NodeId[] {
|
|
2004
|
+
if (this.meter) this.meter.prevReads++;
|
|
1944
2005
|
return this._dbGetPrevEdgesFirst(id, limit);
|
|
1945
2006
|
}
|
|
1946
2007
|
|
|
1947
2008
|
/** {@link Store.prevCount}. Subclasses with an indexed reverse-edge count
|
|
1948
2009
|
* should override; this default materialises (correct, not optimal). */
|
|
1949
2010
|
prevCount(id: NodeId): number {
|
|
2011
|
+
if (this.meter) this.meter.prevProbes++;
|
|
1950
2012
|
return this._dbGetPrevEdges(id).length;
|
|
1951
2013
|
}
|
|
1952
2014
|
|
|
@@ -1964,11 +2026,13 @@ export abstract class AbstractStore implements Store {
|
|
|
1964
2026
|
// ── Halos ──────────────────────────────────────────────────────────────
|
|
1965
2027
|
|
|
1966
2028
|
haloMass(id: NodeId): number {
|
|
2029
|
+
if (this.meter) this.meter.haloProbes++;
|
|
1967
2030
|
const r = this._dbGetHalo(id);
|
|
1968
2031
|
return r ? r.mass : 0;
|
|
1969
2032
|
}
|
|
1970
2033
|
|
|
1971
2034
|
halo(id: NodeId): Vec | null {
|
|
2035
|
+
if (this.meter) this.meter.haloReads++;
|
|
1972
2036
|
const cached = this._haloNorm.get(id);
|
|
1973
2037
|
if (cached !== undefined) return copy(cached);
|
|
1974
2038
|
const r = this._dbGetHalo(id);
|
|
@@ -1982,6 +2046,7 @@ export abstract class AbstractStore implements Store {
|
|
|
1982
2046
|
/** {@link Store.hasHalo} — MUST mirror {@link halo}'s null condition
|
|
1983
2047
|
* exactly (row present AND mass ≥ minHaloMass), minus the decode. */
|
|
1984
2048
|
hasHalo(id: NodeId): boolean {
|
|
2049
|
+
if (this.meter) this.meter.haloProbes++;
|
|
1985
2050
|
const r = this._dbGetHalo(id);
|
|
1986
2051
|
return r !== null && r.mass >= this.minHaloMass;
|
|
1987
2052
|
}
|
|
@@ -2018,9 +2083,13 @@ export abstract class AbstractStore implements Store {
|
|
|
2018
2083
|
const cache = this._resonateHaloCache;
|
|
2019
2084
|
if (cache) {
|
|
2020
2085
|
const hit = cache.get(rk);
|
|
2021
|
-
if (hit !== undefined)
|
|
2086
|
+
if (hit !== undefined) {
|
|
2087
|
+
if (this.meter) this.meter.annCacheHits++;
|
|
2088
|
+
return hit;
|
|
2089
|
+
}
|
|
2022
2090
|
}
|
|
2023
2091
|
|
|
2092
|
+
if (this.meter) this.meter.haloQueries++;
|
|
2024
2093
|
const results = this._vecHaloQuery(
|
|
2025
2094
|
normalize(copy(v)),
|
|
2026
2095
|
k * this.overfetch,
|
package/test/14-scaling.test.mjs
CHANGED
|
@@ -376,16 +376,26 @@ test("inference: input is processed at a roughly constant KB/s (linear, not quad
|
|
|
376
376
|
|
|
377
377
|
await mind.respond(queries[queries.length - 1]); // warm up
|
|
378
378
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
379
|
+
// INTERLEAVED sampling — round-robin across sizes, not size-at-a-time.
|
|
380
|
+
// The inputs and the store are deterministic, so the WORK per query is
|
|
381
|
+
// fixed; only wall clock varies. Sampling one size to completion before
|
|
382
|
+
// moving on let a single GC/JIT episode land on one size's ENTIRE sample
|
|
383
|
+
// block, so even `fastest` (min of 5) came back ~2.7x high for that size
|
|
384
|
+
// alone — a bimodal reading that made the throughput-band ratio below trip
|
|
385
|
+
// intermittently (observed on both the 1KB point and, more weakly, before
|
|
386
|
+
// any of this file's callers changed). Round-robin spreads every episode
|
|
387
|
+
// across all sizes, so `fastest` sees at least one clean sample each.
|
|
388
|
+
// This does not weaken the assertion — it removes the sampling artefact
|
|
389
|
+
// the assertion was reacting to.
|
|
390
|
+
const samples = queries.map(() => []);
|
|
391
|
+
for (let r = 0; r < 5; r++) {
|
|
392
|
+
for (let i = 0; i < queries.length; i++) {
|
|
383
393
|
const t0 = performance.now();
|
|
384
|
-
await mind.respond(
|
|
385
|
-
samples.push(performance.now() - t0);
|
|
394
|
+
await mind.respond(queries[i]);
|
|
395
|
+
samples[i].push(performance.now() - t0);
|
|
386
396
|
}
|
|
387
|
-
times.push(fastest(samples));
|
|
388
397
|
}
|
|
398
|
+
const times = samples.map(fastest);
|
|
389
399
|
await mind.store.close();
|
|
390
400
|
|
|
391
401
|
const rates = bytes.map((b, i) => (b / 1024) / (times[i] / 1000)); // KB/s
|
package/test/31-audit.test.mjs
CHANGED
|
@@ -235,8 +235,11 @@ test("H2: the ALU meaning-memo does not change answers across repeat responds",
|
|
|
235
235
|
// ═══════════════════════════════════════════════════════════════════════
|
|
236
236
|
|
|
237
237
|
test("I1: containsSpan rejects a sparse subsequence that isSpanShaped accepts", async () => {
|
|
238
|
+
// The span-shape family lives in the shared match/project family
|
|
239
|
+
// (match.ts), not inside a mechanism: pipeline-mechanism.ts and
|
|
240
|
+
// reasoning.ts both consume it, so a mechanism may not own it.
|
|
238
241
|
const { containsSpan, isSpanShaped } = await import(
|
|
239
|
-
"../dist/src/mind/
|
|
242
|
+
"../dist/src/mind/match.js"
|
|
240
243
|
);
|
|
241
244
|
const m = newMind();
|
|
242
245
|
await m.ingest([["what is ice?", "ice is frozen water"]]);
|
|
@@ -142,7 +142,19 @@ test("3 — a near-tie between the winner and runner-up emits narrowDecision", a
|
|
|
142
142
|
["steel is hard so steel is strong", "strong"],
|
|
143
143
|
["water is frigid so water is freezing", "freezing"],
|
|
144
144
|
]);
|
|
145
|
-
|
|
145
|
+
// The near-tie has to be REAL: two candidates whose accounted spans are the
|
|
146
|
+
// same, separated by a single move. `steel is hard so steel is` is that —
|
|
147
|
+
// extraction and recall both explain the whole prefix (grades 21011 and
|
|
148
|
+
// 21010, margin 1).
|
|
149
|
+
//
|
|
150
|
+
// The old fixture, `steel is frigid so steel is ???`, was a near-tie only
|
|
151
|
+
// while CAST could not fire on it: the counterfactual subject and the seat
|
|
152
|
+
// it fills sit on opposite sides of one recurring structure, and CAST's
|
|
153
|
+
// subject gate used to reject any point whose alignment continued PAST the
|
|
154
|
+
// seat — which that very recurrence guarantees. With CAST firing, it leaves
|
|
155
|
+
// only "???" unexplained and wins by 22008; a decisive win is the opposite
|
|
156
|
+
// of what this test pins, and test 3b already covers it.
|
|
157
|
+
const { steps } = await trace(m, "steel is hard so steel is");
|
|
146
158
|
const narrow = stepsNamed(steps, "narrowDecision");
|
|
147
159
|
assert.equal(narrow.length, 1, "expected exactly one narrowDecision step");
|
|
148
160
|
assert.ok(/margin \d+ grade-unit/.test(narrow[0].note), narrow[0].note);
|
|
@@ -69,9 +69,16 @@ test("fuseAttention: a root whose continuation is already answered in the query
|
|
|
69
69
|
// topic with no such embedded answer.
|
|
70
70
|
const q = enc("greet reply-greet then red then circle");
|
|
71
71
|
const roots = await m.climbAttention(q, 24);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
// Roots are identified by the ANCHOR they climbed to, never by the query
|
|
73
|
+
// offset of the region that got there: which region wins the climb is a
|
|
74
|
+
// property of the evidence, and the fold's cuts are content-defined, so an
|
|
75
|
+
// offset test would be asserting where a cut happened to fall. (It did:
|
|
76
|
+
// this once read `r.start === 0`, and passed only while the region reaching
|
|
77
|
+
// `greet` began at byte 0 — it now begins at 6, on the same anchor.)
|
|
78
|
+
const anchorOf = (r) => dec(m.store.bytesPrefix(r.anchor, 1e9));
|
|
79
|
+
const greet = roots.find((r) => anchorOf(r) === "greet");
|
|
80
|
+
const redCircle = roots.find((r) => anchorOf(r) === "red circle");
|
|
81
|
+
assert.ok(greet, "expected a root anchored on 'greet'");
|
|
75
82
|
assert.ok(redCircle, "expected the 'red circle' binding root");
|
|
76
83
|
|
|
77
84
|
const guide = gistOf(m, q);
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// must recover a trained form whose edge misalignment is MORE than one byte
|
|
3
3
|
// (the shipped ±1 fix only covers a single stray edge byte).
|
|
4
4
|
//
|
|
5
|
-
// bytesToTree's
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// bytesToTree's fold is CONTENT-DEFINED: a chunk's own boundary is a cut the
|
|
6
|
+
// bytes chose (see geometry.ts's contentBoundaries), so it moves with the
|
|
7
|
+
// content rather than with an absolute offset. A query whose
|
|
8
8
|
// recognised span sits at a different local offset than the trained deposit
|
|
9
9
|
// did — e.g. extra leading whitespace, which canon deliberately preserves
|
|
10
10
|
// verbatim at the edges, only collapsing INTERIOR whitespace — shifts every
|
|
@@ -47,6 +47,27 @@ const SYN_ATTR_CORPUS = [
|
|
|
47
47
|
["blue square", "answer delta"],
|
|
48
48
|
];
|
|
49
49
|
|
|
50
|
+
// Structural-resonance is the LAST rung: it runs only when every DAG tier —
|
|
51
|
+
// exact junction, single- and double-synonym — found no container at all.
|
|
52
|
+
// SYN_ATTR_CORPUS cannot reach it, and no longer pretends to: every attribute
|
|
53
|
+
// there shares a context ("is a color" / "is a shape") with its neighbours, so
|
|
54
|
+
// the single-synonym tier always finds a bridge and accepts before the ladder
|
|
55
|
+
// gets this far. (It once did reach resonance, when the junction search was
|
|
56
|
+
// phase-dependent and missed containers it should have found; asserting the
|
|
57
|
+
// weaker rung still fires would now be asserting a defect.)
|
|
58
|
+
//
|
|
59
|
+
// This corpus withholds exactly what the synonym tiers need: each attribute's
|
|
60
|
+
// context is unique, so no two attributes are halo siblings, and `red` and
|
|
61
|
+
// `square` never co-occur. `red then square` therefore exhausts the DAG tiers
|
|
62
|
+
// and reaches resonance for real — one probe margin-rejected, one ineligible.
|
|
63
|
+
const NO_BRIDGE_CORPUS = [
|
|
64
|
+
["red", "warm hue"],
|
|
65
|
+
["square", "four sides"],
|
|
66
|
+
["circle", "round form"],
|
|
67
|
+
["red circle", "answer alpha"],
|
|
68
|
+
["blue square", "answer delta"],
|
|
69
|
+
];
|
|
70
|
+
|
|
50
71
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
51
72
|
// 1. ordinary-region contrastive rival.
|
|
52
73
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -204,8 +225,8 @@ test("5. double-synonym tier: probe fields report single returned zero and doubl
|
|
|
204
225
|
|
|
205
226
|
test("6. structural-resonance: eligible probes report variants, merged proposals and an examined sequence with effectiveScore = annScore * semanticConfidence", async () => {
|
|
206
227
|
const m = mk(1);
|
|
207
|
-
await m.ingest(
|
|
208
|
-
const { steps } = await trace(m, "
|
|
228
|
+
await m.ingest(NO_BRIDGE_CORPUS);
|
|
229
|
+
const { steps } = await trace(m, "red then square");
|
|
209
230
|
await m.store.close();
|
|
210
231
|
|
|
211
232
|
const step = climbStep(steps);
|
|
@@ -262,10 +283,10 @@ test("6. structural-resonance: eligible probes report variants, merged proposals
|
|
|
262
283
|
|
|
263
284
|
test("7. resonance outcomes: ineligible reasons are named and a margin-rejected probe reports a sub-noise-floor margin", async () => {
|
|
264
285
|
const mIneligible = mk(1);
|
|
265
|
-
await mIneligible.ingest(
|
|
286
|
+
await mIneligible.ingest(NO_BRIDGE_CORPUS);
|
|
266
287
|
const { steps: ineligibleSteps } = await trace(
|
|
267
288
|
mIneligible,
|
|
268
|
-
"
|
|
289
|
+
"red then square",
|
|
269
290
|
);
|
|
270
291
|
await mIneligible.store.close();
|
|
271
292
|
|
|
@@ -286,9 +307,18 @@ test("7. resonance outcomes: ineligible reasons are named and a margin-rejected
|
|
|
286
307
|
assert.equal(p.outcome, "resonance-ineligible");
|
|
287
308
|
}
|
|
288
309
|
|
|
310
|
+
// A margin rejection needs resonance to actually PRODUCE a proposal and
|
|
311
|
+
// then find it indistinguishable from its runner-up. On the tight
|
|
312
|
+
// `red then square` the single surviving proposal clears the noise floor and
|
|
313
|
+
// is accepted; spreading the two attributes apart puts many more pairs in
|
|
314
|
+
// play, and the pair that reaches resonance proposes an ANN neighbour its
|
|
315
|
+
// rival matches — which is exactly the case this field exists to report.
|
|
289
316
|
const mMargin = mk(1);
|
|
290
|
-
await mMargin.ingest(
|
|
291
|
-
const { steps: marginSteps } = await trace(
|
|
317
|
+
await mMargin.ingest(NO_BRIDGE_CORPUS);
|
|
318
|
+
const { steps: marginSteps } = await trace(
|
|
319
|
+
mMargin,
|
|
320
|
+
"red and a very long interior gap before square",
|
|
321
|
+
);
|
|
292
322
|
await mMargin.store.close();
|
|
293
323
|
const marginStep = climbStep(marginSteps);
|
|
294
324
|
const marginRejected = (marginStep.data.crossRegion?.probes ?? []).filter(
|