mumpix 1.0.20 → 1.1.3
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/CHANGELOG.md +82 -11
- package/README.md +278 -8
- package/bin/mumpix.js +1 -405
- package/examples/agent-memory.js +1 -1
- package/examples/basic.js +1 -1
- package/examples/behavioral-primitives.js +50 -0
- package/examples/recall-quality-suite.js +156 -0
- package/examples/verified-mode.js +1 -1
- package/package.json +24 -13
- package/scripts/bench-semantic.cjs +76 -0
- package/scripts/test-license-modes.cjs +87 -0
- package/scripts/test-phase0.cjs +137 -0
- package/scripts/test-semantic.cjs +411 -0
- package/src/brp/index.js +1 -0
- package/src/collapse/index.js +1 -0
- package/src/core/MumpixDB.js +264 -323
- package/src/core/audit.js +1 -173
- package/src/core/auth.js +1 -232
- package/src/core/inverted-index.js +249 -0
- package/src/core/license.js +1 -267
- package/src/core/ml-dsa.mjs +1 -25
- package/src/core/ml-kem.mjs +1 -32
- package/src/core/recall.js +226 -112
- package/src/core/semantic-sidecar.js +312 -0
- package/src/core/store.js +365 -288
- package/src/core/wal-writer.js +83 -0
- package/src/index.js +20 -34
- package/src/integrations/developer-sdk.js +1 -165
- package/src/integrations/langchain-official.js +1 -0
- package/src/integrations/langchain.js +1 -131
- package/src/integrations/llamaindex-official.js +1 -0
- package/src/integrations/llamaindex.js +1 -86
- package/src/integrations/vector-sidecar.js +325 -0
- package/src/rlp/index.js +1 -0
- package/src/temporal/engine.js +1 -1894
- package/src/temporal/indexes.js +1 -178
- package/src/temporal/operators.js +1 -186
- package/scripts/postinstall-auth.js +0 -101
package/src/core/MumpixDB.js
CHANGED
|
@@ -1,329 +1,275 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { MumpixStore } = require("./store");
|
|
4
|
+
const { MumpixAudit } = require("./audit");
|
|
5
|
+
const { recall, recallMany: scoreRecallMany } = require("./recall");
|
|
6
|
+
const { LicenseManager } = require("./license");
|
|
7
|
+
const { getStoredLicenseKey } = require("./auth");
|
|
8
|
+
const { SemanticSidecar, loadDefaultEmbedder } = require("./semantic-sidecar");
|
|
9
|
+
const {
|
|
10
|
+
answerTemporalQuery,
|
|
11
|
+
materializeEvents,
|
|
12
|
+
materializeEventIndex,
|
|
13
|
+
extractEventsFromRecord,
|
|
14
|
+
} = require("../temporal/engine");
|
|
15
|
+
const { appendEventIndex } = require("../temporal/indexes");
|
|
16
|
+
|
|
17
|
+
const VALID_CONSISTENCY = ["eventual", "strict", "verified"];
|
|
18
|
+
|
|
19
|
+
function envFlag(name) {
|
|
20
|
+
const value = process.env[name];
|
|
21
|
+
return value != null && value !== "" && /^(1|true|yes|on)$/i.test(String(value).trim());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function telemetryEnabled(opts = {}) {
|
|
25
|
+
return typeof opts.telemetry === "boolean" ? opts.telemetry : envFlag("MUMPIX_TELEMETRY");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function storeOpenOptions(consistency, opts) {
|
|
29
|
+
return {
|
|
30
|
+
consistency,
|
|
31
|
+
lock: opts.lock,
|
|
32
|
+
lockStaleMs: opts.lockStaleMs,
|
|
33
|
+
walFlushIntervalMs: opts.walFlushIntervalMs,
|
|
34
|
+
walMaxPendingBytes: opts.walMaxPendingBytes,
|
|
35
|
+
recallIndex: opts.recallIndex,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function semanticOptions(opts = {}) {
|
|
40
|
+
const enabled = opts.recall === "semantic" || opts.semantic === true;
|
|
41
|
+
if (!enabled) return null;
|
|
42
|
+
const embedder = opts.embedder || loadDefaultEmbedder();
|
|
43
|
+
const semanticFloor = Number.isFinite(opts.semanticFloor) ? Number(opts.semanticFloor) : 0.5;
|
|
44
|
+
return {
|
|
45
|
+
embedder,
|
|
46
|
+
semanticFloor,
|
|
47
|
+
vectorPath: opts.vectorPath,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
25
50
|
|
|
26
51
|
class MumpixDB {
|
|
27
|
-
/**
|
|
28
|
-
* @param {MumpixStore} store
|
|
29
|
-
* @param {MumpixAudit|null} audit
|
|
30
|
-
* @param {LicenseManager} license
|
|
31
|
-
* @param {object} opts
|
|
32
|
-
*/
|
|
33
52
|
constructor(store, audit, license, opts = {}) {
|
|
34
53
|
this._store = store;
|
|
35
|
-
this._audit = audit;
|
|
54
|
+
this._audit = audit;
|
|
36
55
|
this._opts = opts;
|
|
37
56
|
this._closed = false;
|
|
38
57
|
this._license = license;
|
|
39
58
|
this._temporalCache = null;
|
|
40
59
|
}
|
|
41
60
|
|
|
42
|
-
// ─────────────────────────────────────────
|
|
43
|
-
// Factory
|
|
44
|
-
// ─────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Open (or create) a Mumpix database.
|
|
48
|
-
*
|
|
49
|
-
* @param {string} filePath Path to .mumpix file
|
|
50
|
-
* @param {object} [opts]
|
|
51
|
-
* @param {string} [opts.consistency] 'eventual' | 'strict' | 'verified' (default: 'eventual')
|
|
52
|
-
* @param {Function} [opts.embedFn] async (texts: string[]) => number[][] — optional custom embeddings
|
|
53
|
-
* @param {string} [opts.licenseKey] Commercial license key for Pro/Enterprise features
|
|
54
|
-
* @returns {Promise<MumpixDB>}
|
|
55
|
-
*/
|
|
56
61
|
static async open(filePath, opts = {}) {
|
|
57
|
-
if (!filePath || typeof filePath !==
|
|
58
|
-
throw new TypeError(
|
|
62
|
+
if (!filePath || typeof filePath !== "string") {
|
|
63
|
+
throw new TypeError("Mumpix.open() requires a file path string");
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
const requestedConsistency = opts.consistency ||
|
|
62
|
-
if (!
|
|
63
|
-
throw new Error(
|
|
66
|
+
const requestedConsistency = opts.consistency || "eventual";
|
|
67
|
+
if (!VALID_CONSISTENCY.includes(requestedConsistency)) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Invalid consistency mode "${requestedConsistency}". Valid: ${VALID_CONSISTENCY.join(", ")}`
|
|
70
|
+
);
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
const store = new MumpixStore(filePath);
|
|
67
|
-
let
|
|
74
|
+
let audit = null;
|
|
75
|
+
|
|
68
76
|
try {
|
|
69
|
-
|
|
77
|
+
store.open(storeOpenOptions(requestedConsistency, opts));
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
const licenseKey = opts.licenseKey || getStoredLicenseKey() || null;
|
|
80
|
+
const license =
|
|
81
|
+
opts.licenseManager instanceof LicenseManager
|
|
82
|
+
? opts.licenseManager
|
|
83
|
+
: new LicenseManager(licenseKey);
|
|
75
84
|
license.setFileContext(store.header && store.header.fileId ? store.header.fileId : null);
|
|
76
|
-
|
|
77
|
-
if (resolvedLicenseKey && !licenseReady) {
|
|
78
|
-
console.warn(`[DEBUG] License rejected: ${license.validationError}`);
|
|
79
|
-
}
|
|
85
|
+
await license.init();
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// force eventual mode until the license is renewed.
|
|
84
|
-
let effectiveConsistency = requestedConsistency;
|
|
85
|
-
if (requestedConsistency !== 'eventual') {
|
|
87
|
+
let consistency = requestedConsistency;
|
|
88
|
+
if (requestedConsistency !== "eventual") {
|
|
86
89
|
try {
|
|
87
|
-
license.checkLimit(
|
|
90
|
+
license.checkLimit("mode", requestedConsistency);
|
|
88
91
|
license.assertActive(requestedConsistency);
|
|
89
|
-
} catch (
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
effectiveConsistency = 'eventual';
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const message = String((err && err.message) || err).toLowerCase();
|
|
94
|
+
const canDowngrade =
|
|
95
|
+
message.includes("expired") ||
|
|
96
|
+
message.includes("invalid for mode") ||
|
|
97
|
+
message.includes("license_validation_failed") ||
|
|
98
|
+
message.includes("quantum signature verification failed") ||
|
|
99
|
+
message.includes("lease window") ||
|
|
100
|
+
message.includes("too far in the future");
|
|
101
|
+
if (!canDowngrade) throw err;
|
|
102
|
+
consistency = "eventual";
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
if (
|
|
105
|
-
console.warn(`[DEBUG] Downgrading consistency to ${effectiveConsistency}`);
|
|
106
|
-
// Re-open with downgraded mode after releasing current FD/lock first.
|
|
106
|
+
if (consistency !== requestedConsistency) {
|
|
107
107
|
store.close();
|
|
108
|
-
|
|
108
|
+
store.open(storeOpenOptions(consistency, opts));
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
if (consistency === "verified") {
|
|
112
|
+
audit = new MumpixAudit(filePath);
|
|
113
|
+
audit.open();
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
const finalOpts = {
|
|
117
117
|
...opts,
|
|
118
|
-
|
|
118
|
+
telemetry: telemetryEnabled(opts),
|
|
119
|
+
consistency,
|
|
119
120
|
requestedConsistency,
|
|
120
|
-
downgradedConsistency:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
downgradedConsistency:
|
|
122
|
+
consistency !== requestedConsistency
|
|
123
|
+
? { from: requestedConsistency, to: consistency, reason: "license_not_active" }
|
|
124
|
+
: null,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const semantic = semanticOptions(finalOpts);
|
|
128
|
+
if (semantic) {
|
|
129
|
+
finalOpts.semanticRecall = new SemanticSidecar(store.filePath, semantic.embedder, {
|
|
130
|
+
vectorPath: semantic.vectorPath,
|
|
131
|
+
});
|
|
132
|
+
finalOpts.semanticFloor = semantic.semanticFloor;
|
|
133
|
+
await finalOpts.semanticRecall.reconcileRecords(store.all());
|
|
127
134
|
}
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
|
|
136
|
+
return new MumpixDB(store, audit, license, finalOpts);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
if (audit) {
|
|
139
|
+
try {
|
|
140
|
+
audit.close();
|
|
141
|
+
} catch (_) {}
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
store.close();
|
|
145
|
+
} catch (_) {}
|
|
146
|
+
throw err;
|
|
130
147
|
}
|
|
131
148
|
}
|
|
132
149
|
|
|
133
|
-
|
|
134
|
-
// Core API
|
|
135
|
-
// ─────────────────────────────────────────
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Store a memory.
|
|
139
|
-
*
|
|
140
|
-
* @param {string} content
|
|
141
|
-
* @returns {{ id: number, written: boolean, consistency: string }}
|
|
142
|
-
*/
|
|
143
|
-
async remember(content, opts = {}) {
|
|
150
|
+
async remember(input, meta = {}) {
|
|
144
151
|
this._assertOpen();
|
|
145
|
-
let text = content;
|
|
146
|
-
let meta = opts;
|
|
147
152
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
let content = input;
|
|
154
|
+
let cleanMeta = meta;
|
|
155
|
+
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
156
|
+
content = input.content;
|
|
157
|
+
cleanMeta = {
|
|
158
|
+
...meta,
|
|
159
|
+
workspace: input.workspace ?? meta.workspace,
|
|
160
|
+
repo: input.repo ?? meta.repo,
|
|
161
|
+
source: input.source ?? meta.source,
|
|
162
|
+
ts: input.ts ?? meta.ts,
|
|
156
163
|
};
|
|
157
164
|
}
|
|
158
165
|
|
|
159
|
-
if (typeof
|
|
160
|
-
throw new TypeError(
|
|
166
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
167
|
+
throw new TypeError("remember() requires a non-empty string or { content } object");
|
|
161
168
|
}
|
|
162
169
|
|
|
163
|
-
|
|
164
|
-
this.
|
|
165
|
-
|
|
166
|
-
const record = this._store.write(text, meta);
|
|
170
|
+
this._license.checkLimit("records", this._store.records.length);
|
|
171
|
+
const record = this._store.write(content, cleanMeta);
|
|
167
172
|
this._appendTemporalRecord(record);
|
|
173
|
+
await this._appendSemanticRecord(record);
|
|
174
|
+
if (this._audit) this._audit.logWrite(record);
|
|
175
|
+
return { written: true, id: record.id, consistency: this._store.header.consistency };
|
|
176
|
+
}
|
|
168
177
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
178
|
+
async rememberAll(items) {
|
|
179
|
+
this._assertOpen();
|
|
180
|
+
if (!Array.isArray(items)) throw new TypeError("rememberAll() expects an array");
|
|
181
|
+
this._license.checkLimit("records", this._store.records.length + items.length);
|
|
172
182
|
|
|
173
|
-
|
|
183
|
+
const normalized = items.map((item) => {
|
|
184
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
185
|
+
return item;
|
|
186
|
+
}
|
|
187
|
+
return { content: item };
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const records = this._store.writeBatch(normalized);
|
|
191
|
+
for (const record of records) {
|
|
192
|
+
this._appendTemporalRecord(record);
|
|
193
|
+
await this._appendSemanticRecord(record);
|
|
194
|
+
if (this._audit) this._audit.logWrite(record);
|
|
195
|
+
}
|
|
196
|
+
return records.map((record) => ({
|
|
174
197
|
written: true,
|
|
175
198
|
id: record.id,
|
|
176
199
|
consistency: this._store.header.consistency,
|
|
177
|
-
};
|
|
200
|
+
}));
|
|
178
201
|
}
|
|
179
202
|
|
|
180
|
-
/**
|
|
181
|
-
* Recall the most semantically relevant memory for a query.
|
|
182
|
-
*
|
|
183
|
-
* @param {string} query
|
|
184
|
-
* @param {object} [opts]
|
|
185
|
-
* @param {object} [opts.filter] Pre-filter records: fn(record) → bool
|
|
186
|
-
* @param {number} [opts.since] Only consider records newer than this timestamp
|
|
187
|
-
* @param {string} [opts.mode] 'hybrid' | 'exact' | 'semantic'
|
|
188
|
-
* @returns {string|null}
|
|
189
|
-
*/
|
|
190
203
|
async recall(query, opts = {}) {
|
|
191
204
|
this._assertOpen();
|
|
192
|
-
if (typeof query !==
|
|
193
|
-
throw new TypeError(
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const records = this._store.all();
|
|
197
|
-
if (!records.length) return null;
|
|
198
|
-
|
|
199
|
-
const t0 = Date.now();
|
|
200
|
-
const result = await recall(query, records, {
|
|
201
|
-
embedFn: this._opts.embedFn,
|
|
202
|
-
...opts,
|
|
203
|
-
});
|
|
204
|
-
const latencyMs = Date.now() - t0;
|
|
205
|
-
|
|
206
|
-
if (this._audit) {
|
|
207
|
-
this._audit.logRecall(query, result, latencyMs);
|
|
205
|
+
if (typeof query !== "string" || !query.trim()) {
|
|
206
|
+
throw new TypeError("recall() requires a non-empty string");
|
|
208
207
|
}
|
|
209
208
|
|
|
210
|
-
|
|
209
|
+
const start = Date.now();
|
|
210
|
+
const hits = await this._rank(query, 1, opts);
|
|
211
|
+
const hit = hits[0] || null;
|
|
212
|
+
const latency = Date.now() - start;
|
|
213
|
+
if (this._audit) this._audit.logRecall(query, hit, latency);
|
|
214
|
+
return hit ? hit.content : null;
|
|
211
215
|
}
|
|
212
216
|
|
|
213
|
-
/**
|
|
214
|
-
* Return the top-k most relevant memories.
|
|
215
|
-
*
|
|
216
|
-
* @param {string} query
|
|
217
|
-
* @param {number} [k=5]
|
|
218
|
-
* @param {object} [opts] Same opts as recall()
|
|
219
|
-
* @returns {Array<{ id, content, ts, score }>}
|
|
220
|
-
*/
|
|
221
217
|
async recallMany(query, k = 5, opts = {}) {
|
|
222
218
|
this._assertOpen();
|
|
223
|
-
if (typeof query !==
|
|
224
|
-
throw new TypeError(
|
|
219
|
+
if (typeof query !== "string" || !query.trim()) {
|
|
220
|
+
throw new TypeError("recallMany() requires a non-empty string");
|
|
225
221
|
}
|
|
226
222
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
k,
|
|
233
|
-
...opts,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
return results.map(r => ({
|
|
237
|
-
id: r.id,
|
|
238
|
-
content: r.content,
|
|
239
|
-
ts: r.ts,
|
|
240
|
-
score: r._score,
|
|
223
|
+
return (await this._rank(query, k, opts)).map((record) => ({
|
|
224
|
+
id: record.id,
|
|
225
|
+
content: record.content,
|
|
226
|
+
ts: record.ts,
|
|
227
|
+
score: record._score,
|
|
241
228
|
}));
|
|
242
229
|
}
|
|
243
230
|
|
|
244
|
-
/**
|
|
245
|
-
* List all stored memories.
|
|
246
|
-
*
|
|
247
|
-
* @returns {Array<{ id, content, ts }>}
|
|
248
|
-
*/
|
|
249
231
|
async list() {
|
|
250
232
|
this._assertOpen();
|
|
251
|
-
return this._store.all().map(
|
|
252
|
-
id:
|
|
253
|
-
content:
|
|
254
|
-
ts:
|
|
233
|
+
return this._store.all().map((record) => ({
|
|
234
|
+
id: record.id,
|
|
235
|
+
content: record.content,
|
|
236
|
+
ts: record.ts,
|
|
255
237
|
}));
|
|
256
238
|
}
|
|
257
239
|
|
|
258
|
-
/**
|
|
259
|
-
* Materialize normalized temporal events from WAL-backed records.
|
|
260
|
-
*
|
|
261
|
-
* @param {object} [opts]
|
|
262
|
-
* @param {string} [opts.query] Optional query to shortlist candidate records first
|
|
263
|
-
* @param {number} [opts.topK=50]
|
|
264
|
-
* @returns {Array<object>}
|
|
265
|
-
*/
|
|
266
240
|
async events(opts = {}) {
|
|
267
241
|
this._assertOpen();
|
|
268
242
|
const records = this._store.all();
|
|
269
243
|
if (!records.length) return [];
|
|
270
|
-
|
|
271
|
-
if (
|
|
272
|
-
return this._getTemporalCache().events;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (opts.query && typeof opts.query === 'string' && opts.query.trim()) {
|
|
244
|
+
if (!opts.query) return this._getTemporalCache().events;
|
|
245
|
+
if (opts.query && typeof opts.query === "string" && opts.query.trim()) {
|
|
276
246
|
const topK = Number.isFinite(opts.topK) ? Number(opts.topK) : 50;
|
|
277
|
-
const
|
|
247
|
+
const hits = await scoreRecallMany(opts.query, records, {
|
|
278
248
|
embedFn: this._opts.embedFn,
|
|
279
249
|
k: topK,
|
|
280
250
|
});
|
|
281
|
-
return materializeEvents(
|
|
251
|
+
return materializeEvents(hits);
|
|
282
252
|
}
|
|
283
|
-
|
|
284
253
|
return this._getTemporalCache().events;
|
|
285
254
|
}
|
|
286
255
|
|
|
287
|
-
/**
|
|
288
|
-
* Return derived event indexes built from WAL-backed records.
|
|
289
|
-
*
|
|
290
|
-
* @param {object} [opts]
|
|
291
|
-
* @param {string} [opts.query]
|
|
292
|
-
* @param {number} [opts.topK=50]
|
|
293
|
-
* @returns {object}
|
|
294
|
-
*/
|
|
295
256
|
async eventIndex(opts = {}) {
|
|
296
257
|
this._assertOpen();
|
|
297
258
|
const records = this._store.all();
|
|
298
|
-
if (!records.length)
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (!opts.query) {
|
|
303
|
-
return this._getTemporalCache();
|
|
304
|
-
}
|
|
305
|
-
|
|
259
|
+
if (!records.length) return materializeEventIndex([]);
|
|
260
|
+
if (!opts.query) return this._getTemporalCache();
|
|
306
261
|
const topK = Number.isFinite(opts.topK) ? Number(opts.topK) : 50;
|
|
307
|
-
const
|
|
262
|
+
const hits = await scoreRecallMany(opts.query, records, {
|
|
308
263
|
embedFn: this._opts.embedFn,
|
|
309
264
|
k: topK,
|
|
310
265
|
});
|
|
311
|
-
return materializeEventIndex(
|
|
266
|
+
return materializeEventIndex(hits);
|
|
312
267
|
}
|
|
313
268
|
|
|
314
|
-
/**
|
|
315
|
-
* Run a deterministic temporal query over WAL-backed records.
|
|
316
|
-
*
|
|
317
|
-
* @param {string} query
|
|
318
|
-
* @param {object} [opts]
|
|
319
|
-
* @param {number} [opts.topK=50]
|
|
320
|
-
* @param {Date|string|number} [opts.now]
|
|
321
|
-
* @returns {object}
|
|
322
|
-
*/
|
|
323
269
|
async temporalQuery(query, opts = {}) {
|
|
324
270
|
this._assertOpen();
|
|
325
|
-
if (typeof query !==
|
|
326
|
-
throw new TypeError(
|
|
271
|
+
if (typeof query !== "string" || !query.trim()) {
|
|
272
|
+
throw new TypeError("temporalQuery() requires a non-empty string");
|
|
327
273
|
}
|
|
328
274
|
|
|
329
275
|
const records = this._store.all();
|
|
@@ -333,157 +279,148 @@ class MumpixDB {
|
|
|
333
279
|
kind: null,
|
|
334
280
|
value: null,
|
|
335
281
|
evidence: [],
|
|
336
|
-
warnings: [
|
|
282
|
+
warnings: ["No records available."],
|
|
337
283
|
parsed_query: null,
|
|
338
284
|
events: [],
|
|
339
285
|
};
|
|
340
286
|
}
|
|
341
287
|
|
|
342
288
|
const topK = Number.isFinite(opts.topK) ? Number(opts.topK) : 50;
|
|
343
|
-
const
|
|
289
|
+
const hits = await scoreRecallMany(query, records, {
|
|
344
290
|
embedFn: this._opts.embedFn,
|
|
345
291
|
k: topK,
|
|
346
292
|
});
|
|
347
|
-
|
|
348
293
|
const now =
|
|
349
|
-
opts.now instanceof Date
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
294
|
+
opts.now instanceof Date
|
|
295
|
+
? opts.now
|
|
296
|
+
: typeof opts.now === "string" || typeof opts.now === "number"
|
|
297
|
+
? new Date(opts.now)
|
|
298
|
+
: new Date();
|
|
299
|
+
return answerTemporalQuery(query, hits, { now });
|
|
354
300
|
}
|
|
355
301
|
|
|
356
|
-
/**
|
|
357
|
-
* Delete all memories.
|
|
358
|
-
*
|
|
359
|
-
* @returns {{ cleared: boolean, count: number }}
|
|
360
|
-
*/
|
|
361
302
|
async clear() {
|
|
362
303
|
this._assertOpen();
|
|
363
304
|
const count = this._store.clear();
|
|
364
305
|
this._invalidateTemporalCache();
|
|
365
|
-
|
|
366
|
-
if (this._audit)
|
|
367
|
-
this._audit.logClear(count);
|
|
368
|
-
}
|
|
369
|
-
|
|
306
|
+
if (this._opts.semanticRecall) this._opts.semanticRecall.clear();
|
|
307
|
+
if (this._audit) this._audit.logClear(count);
|
|
370
308
|
return { cleared: true, count };
|
|
371
309
|
}
|
|
372
310
|
|
|
373
|
-
/**
|
|
374
|
-
* Get audit log entries (Verified mode only).
|
|
375
|
-
*
|
|
376
|
-
* @returns {Array<object>}
|
|
377
|
-
*/
|
|
378
311
|
async audit() {
|
|
379
312
|
this._assertOpen();
|
|
380
313
|
if (!this._audit) {
|
|
381
|
-
throw new Error(
|
|
314
|
+
throw new Error(
|
|
315
|
+
`audit() requires consistency: "verified". Current mode: ${this._store.header.consistency}`
|
|
316
|
+
);
|
|
382
317
|
}
|
|
383
318
|
return this._audit.all();
|
|
384
319
|
}
|
|
385
320
|
|
|
386
|
-
/**
|
|
387
|
-
* Get audit log summary (Verified mode only).
|
|
388
|
-
*/
|
|
389
321
|
async auditSummary() {
|
|
390
322
|
this._assertOpen();
|
|
391
|
-
if (!this._audit)
|
|
392
|
-
throw new Error('auditSummary() requires consistency: "verified"');
|
|
393
|
-
}
|
|
323
|
+
if (!this._audit) throw new Error('auditSummary() requires consistency: "verified"');
|
|
394
324
|
return this._audit.summary();
|
|
395
325
|
}
|
|
396
326
|
|
|
397
|
-
/**
|
|
398
|
-
* Export audit log as NDJSON string (Verified mode only).
|
|
399
|
-
*/
|
|
400
327
|
async exportAudit() {
|
|
401
328
|
this._assertOpen();
|
|
402
|
-
if (!this._audit)
|
|
403
|
-
throw new Error('exportAudit() requires consistency: "verified"');
|
|
404
|
-
}
|
|
329
|
+
if (!this._audit) throw new Error('exportAudit() requires consistency: "verified"');
|
|
405
330
|
return this._audit.export();
|
|
406
331
|
}
|
|
407
332
|
|
|
408
|
-
/**
|
|
409
|
-
* Database metadata and stats.
|
|
410
|
-
*/
|
|
411
333
|
async stats() {
|
|
412
334
|
this._assertOpen();
|
|
413
335
|
return this._store.stats();
|
|
414
336
|
}
|
|
415
337
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
// Sync usage metrics in background on close
|
|
423
|
-
this._license.syncUsage(this._store.stats()).catch(() => { });
|
|
424
|
-
|
|
425
|
-
this._store.close();
|
|
426
|
-
if (this._audit) this._audit.close();
|
|
427
|
-
this._closed = true;
|
|
338
|
+
async flush() {
|
|
339
|
+
this._assertOpen();
|
|
340
|
+
this._store.flush();
|
|
341
|
+
if (this._opts.semanticRecall) this._opts.semanticRecall.compact();
|
|
342
|
+
return { flushed: true };
|
|
428
343
|
}
|
|
429
344
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
345
|
+
async rebuildIndex() {
|
|
346
|
+
this._assertOpen();
|
|
347
|
+
return this._store.rebuildIndex();
|
|
348
|
+
}
|
|
433
349
|
|
|
434
|
-
|
|
435
|
-
* Add multiple memories at once.
|
|
436
|
-
*
|
|
437
|
-
* @param {string[]} items
|
|
438
|
-
*/
|
|
439
|
-
async rememberAll(items) {
|
|
350
|
+
async indexStats() {
|
|
440
351
|
this._assertOpen();
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
352
|
+
return this._store.indexStats();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async close() {
|
|
356
|
+
if (this._closed) return;
|
|
357
|
+
try {
|
|
358
|
+
if (this._opts.telemetry && this._license.hasVerifiedLicense()) {
|
|
359
|
+
await this._license.syncUsage(this._store.stats());
|
|
360
|
+
}
|
|
361
|
+
} finally {
|
|
362
|
+
if (this._opts.semanticRecall) {
|
|
363
|
+
try {
|
|
364
|
+
this._opts.semanticRecall.compact();
|
|
365
|
+
} catch (_) {}
|
|
366
|
+
}
|
|
367
|
+
this._store.close();
|
|
368
|
+
if (this._audit) this._audit.close();
|
|
369
|
+
this._closed = true;
|
|
445
370
|
}
|
|
446
|
-
return results;
|
|
447
371
|
}
|
|
448
372
|
|
|
449
|
-
/**
|
|
450
|
-
* Check if any memory semantically matches a query.
|
|
451
|
-
*
|
|
452
|
-
* @param {string} query
|
|
453
|
-
* @returns {boolean}
|
|
454
|
-
*/
|
|
455
373
|
async has(query) {
|
|
456
|
-
|
|
457
|
-
return result !== null;
|
|
374
|
+
return (await this.recall(query)) !== null;
|
|
458
375
|
}
|
|
459
376
|
|
|
460
|
-
/**
|
|
461
|
-
* Return the number of stored memories.
|
|
462
|
-
*/
|
|
463
377
|
get size() {
|
|
464
378
|
return this._store.records.length;
|
|
465
379
|
}
|
|
466
380
|
|
|
467
|
-
/**
|
|
468
|
-
* Consistency mode of this instance.
|
|
469
|
-
*/
|
|
470
381
|
get consistency() {
|
|
471
382
|
return this._store.header.consistency;
|
|
472
383
|
}
|
|
473
384
|
|
|
474
|
-
/**
|
|
475
|
-
* File path of the underlying .mumpix file.
|
|
476
|
-
*/
|
|
477
385
|
get path() {
|
|
478
386
|
return this._store.filePath;
|
|
479
387
|
}
|
|
480
388
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
389
|
+
async _rank(query, k, opts = {}) {
|
|
390
|
+
const allRecords = this._store.all();
|
|
391
|
+
if (!allRecords.length) return [];
|
|
392
|
+
|
|
393
|
+
let candidates = null;
|
|
394
|
+
if (opts.recallIndex !== false && typeof this._store.indexedCandidates === "function") {
|
|
395
|
+
candidates = this._store.indexedCandidates(query, opts.candidateCap || 400);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const records = candidates && candidates.length ? candidates : allRecords;
|
|
399
|
+
const ranked = await scoreRecallMany(query, records, {
|
|
400
|
+
embedFn: this._opts.embedFn,
|
|
401
|
+
k,
|
|
402
|
+
...opts,
|
|
403
|
+
});
|
|
404
|
+
const verified = ranked.filter((record) => this._store._verifyHash(record));
|
|
405
|
+
if (verified.length || !this._opts.semanticRecall || opts.semantic === false) return verified;
|
|
406
|
+
|
|
407
|
+
const semanticHits = await this._opts.semanticRecall.query(
|
|
408
|
+
query,
|
|
409
|
+
k,
|
|
410
|
+
Number.isFinite(opts.semanticFloor) ? Number(opts.semanticFloor) : this._opts.semanticFloor
|
|
411
|
+
);
|
|
412
|
+
return semanticHits.map((hit) => ({
|
|
413
|
+
id: hit.id,
|
|
414
|
+
content: hit.content,
|
|
415
|
+
ts: hit.ts,
|
|
416
|
+
h: this._store.records.find((record) => record.id === hit.id)?.h,
|
|
417
|
+
_score: hit._score,
|
|
418
|
+
_via: "semantic",
|
|
419
|
+
}));
|
|
420
|
+
}
|
|
484
421
|
|
|
485
422
|
_assertOpen() {
|
|
486
|
-
if (this._closed) throw new Error(
|
|
423
|
+
if (this._closed) throw new Error("Database is closed. Call Mumpix.open() again.");
|
|
487
424
|
this._license.assertActive(this._store.header.consistency);
|
|
488
425
|
}
|
|
489
426
|
|
|
@@ -493,8 +430,8 @@ class MumpixDB {
|
|
|
493
430
|
|
|
494
431
|
_temporalSignature() {
|
|
495
432
|
const records = this._store.records || [];
|
|
496
|
-
const
|
|
497
|
-
return `${records.length}:${
|
|
433
|
+
const tail = records.length ? records[records.length - 1] : null;
|
|
434
|
+
return `${records.length}:${tail ? tail.id : 0}:${tail ? tail.ts : 0}`;
|
|
498
435
|
}
|
|
499
436
|
|
|
500
437
|
_getTemporalCache() {
|
|
@@ -502,7 +439,6 @@ class MumpixDB {
|
|
|
502
439
|
if (this._temporalCache && this._temporalCache.signature === signature) {
|
|
503
440
|
return this._temporalCache.index;
|
|
504
441
|
}
|
|
505
|
-
|
|
506
442
|
const index = materializeEventIndex(this._store.all());
|
|
507
443
|
this._temporalCache = { signature, index };
|
|
508
444
|
return index;
|
|
@@ -514,6 +450,11 @@ class MumpixDB {
|
|
|
514
450
|
appendEventIndex(this._temporalCache.index, events);
|
|
515
451
|
this._temporalCache.signature = this._temporalSignature();
|
|
516
452
|
}
|
|
453
|
+
|
|
454
|
+
async _appendSemanticRecord(record) {
|
|
455
|
+
if (!this._opts.semanticRecall) return;
|
|
456
|
+
await this._opts.semanticRecall.addRecord(record);
|
|
457
|
+
}
|
|
517
458
|
}
|
|
518
459
|
|
|
519
460
|
module.exports = { MumpixDB };
|