mumpix 1.0.18 → 1.0.19
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 +12 -0
- package/README.md +64 -0
- package/bin/mumpix.js +0 -0
- package/examples/temporal-memory.js +80 -0
- package/package.json +7 -3
- package/src/core/MumpixDB.js +158 -10
- package/src/core/license.js +16 -21
- package/src/core/store.js +109 -24
- package/src/index.js +8 -0
- package/src/temporal/engine.js +1894 -0
- package/src/temporal/indexes.js +178 -0
- package/src/temporal/operators.js +186 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.19 - 2026-04-13
|
|
4
|
+
|
|
5
|
+
### Internal & Sync
|
|
6
|
+
- Synchronized latest temporal engine and operator scripts from core module into the package tree.
|
|
7
|
+
- Configured embedded GGUF agent context window ceilings (8K) to prevent VRAM memory allocation conflicts with local LLMs (e.g., `mumpix-mlc-llm`).
|
|
8
|
+
|
|
9
|
+
## 1.0.18 - 2026-03-30
|
|
10
|
+
- General performance and sync updates
|
|
11
|
+
|
|
12
|
+
## 1.0.17 - 2026-03-24
|
|
13
|
+
- Core temporal sync patches
|
|
14
|
+
|
|
3
15
|
## 1.0.11 - 2026-03-03
|
|
4
16
|
|
|
5
17
|
## 1.0.12 - 2026-03-03
|
package/README.md
CHANGED
|
@@ -95,6 +95,69 @@ await db.stats();
|
|
|
95
95
|
await db.close();
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
### Temporal API
|
|
99
|
+
|
|
100
|
+
`mumpix` also exposes a deterministic temporal layer built on top of WAL-backed records.
|
|
101
|
+
|
|
102
|
+
This is meant to answer timeline questions by computing over normalized events, not by asking a model to guess from raw text.
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
const { Mumpix } = require('mumpix');
|
|
106
|
+
|
|
107
|
+
const db = await Mumpix.open('./agent.mumpix', { consistency: 'eventual' });
|
|
108
|
+
|
|
109
|
+
await db.remember({
|
|
110
|
+
content: 'I set up the smart thermostat on March 1st.',
|
|
111
|
+
workspace: 'home',
|
|
112
|
+
repo: 'infra-home',
|
|
113
|
+
source: 'ops-log',
|
|
114
|
+
ts: Date.parse('2026-03-03T09:10:00Z'),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await db.remember({
|
|
118
|
+
content: 'I set up the mesh network system on March 3rd.',
|
|
119
|
+
workspace: 'home',
|
|
120
|
+
repo: 'infra-home',
|
|
121
|
+
source: 'ops-log',
|
|
122
|
+
ts: Date.parse('2026-03-03T09:00:00Z'),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const events = await db.events();
|
|
126
|
+
const index = await db.eventIndex();
|
|
127
|
+
const result = await db.temporalQuery(
|
|
128
|
+
'Which device did I set up first, the smart thermostat or the mesh network system?'
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
console.log(events.length);
|
|
132
|
+
console.log(Array.from(index.byWorkspace.keys()));
|
|
133
|
+
console.log(result.value); // smart thermostat
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Available temporal package APIs:
|
|
137
|
+
|
|
138
|
+
- `db.events({ query?, topK? })`
|
|
139
|
+
- `db.eventIndex({ query?, topK? })`
|
|
140
|
+
- `db.temporalQuery(query, { topK?, now? })`
|
|
141
|
+
|
|
142
|
+
Low-level exports are also available for advanced use:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
const { temporal, temporalEngine, temporalIndexes } = require('mumpix');
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
These expose:
|
|
149
|
+
|
|
150
|
+
- deterministic event operators
|
|
151
|
+
- event materialization
|
|
152
|
+
- event indexing by title, type, workspace, repo, and source
|
|
153
|
+
|
|
154
|
+
Temporal records support scoped metadata on write:
|
|
155
|
+
|
|
156
|
+
- `workspace`
|
|
157
|
+
- `repo`
|
|
158
|
+
- `source`
|
|
159
|
+
- `ts`
|
|
160
|
+
|
|
98
161
|
### Consistency modes
|
|
99
162
|
|
|
100
163
|
| Mode | Behavior | Use case |
|
|
@@ -159,6 +222,7 @@ Examples:
|
|
|
159
222
|
```bash
|
|
160
223
|
node examples/langchain-adapter.js
|
|
161
224
|
node examples/llamaindex-adapter.js
|
|
225
|
+
node examples/temporal-memory.js
|
|
162
226
|
```
|
|
163
227
|
|
|
164
228
|
## CLI
|
package/bin/mumpix.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* examples/temporal-memory.js
|
|
5
|
+
*
|
|
6
|
+
* Run:
|
|
7
|
+
* node examples/temporal-memory.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { Mumpix } = require('../src/index');
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
const dbPath = path.join(os.tmpdir(), `mumpix-temporal-${process.pid}.mumpix`);
|
|
17
|
+
const db = await Mumpix.open(dbPath, { consistency: 'eventual' });
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
await db.rememberAll([
|
|
21
|
+
{
|
|
22
|
+
content: 'I set up the mesh network system on March 3rd.',
|
|
23
|
+
workspace: 'home',
|
|
24
|
+
repo: 'infra-home',
|
|
25
|
+
source: 'ops-log',
|
|
26
|
+
ts: Date.parse('2026-03-03T09:00:00Z'),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
content: 'I set up the smart thermostat on March 1st.',
|
|
30
|
+
workspace: 'home',
|
|
31
|
+
repo: 'infra-home',
|
|
32
|
+
source: 'ops-log',
|
|
33
|
+
ts: Date.parse('2026-03-03T09:10:00Z'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
content: 'I deployed the schema migration on January 10th.',
|
|
37
|
+
workspace: 'client-b',
|
|
38
|
+
repo: 'billing-api',
|
|
39
|
+
source: 'deploy-log',
|
|
40
|
+
ts: Date.parse('2026-01-10T09:00:00Z'),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
content: 'I noticed the payment queue was not functioning correctly on January 12th.',
|
|
44
|
+
workspace: 'client-b',
|
|
45
|
+
repo: 'billing-api',
|
|
46
|
+
source: 'incident-log',
|
|
47
|
+
ts: Date.parse('2026-01-12T09:00:00Z'),
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
const events = await db.events();
|
|
52
|
+
console.log('events:', events.map((event) => ({
|
|
53
|
+
title: event.title,
|
|
54
|
+
type: event.event_type,
|
|
55
|
+
date: event.event_date,
|
|
56
|
+
workspace: event.workspace,
|
|
57
|
+
repo: event.repo,
|
|
58
|
+
source: event.source,
|
|
59
|
+
})));
|
|
60
|
+
|
|
61
|
+
const index = await db.eventIndex();
|
|
62
|
+
console.log('workspaces indexed:', Array.from(index.byWorkspace.keys()));
|
|
63
|
+
|
|
64
|
+
const firstSetup = await db.temporalQuery('Which device did I set up first, the smart thermostat or the mesh network system?');
|
|
65
|
+
console.log('first setup:', firstSetup.value);
|
|
66
|
+
|
|
67
|
+
const daysBetween = await db.temporalQuery('How many days had passed between "schema migration" and "payment queue"?');
|
|
68
|
+
console.log('days between deploy and issue:', daysBetween.value);
|
|
69
|
+
} finally {
|
|
70
|
+
await db.close();
|
|
71
|
+
for (const suffix of ['', '.wal', '.lock']) {
|
|
72
|
+
try { fs.unlinkSync(dbPath + suffix); } catch (_) {}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main().catch((error) => {
|
|
78
|
+
console.error(error);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
});
|
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mumpix",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "MumpixDB reasoning ledger and structured memory engine for AI systems",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mumpix": "bin/mumpix.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
+
"build": "node ./scripts/build-package.cjs",
|
|
10
11
|
"postinstall": "node ./scripts/postinstall-auth.js",
|
|
11
12
|
"verify:claims": "node ./scripts/verify-claims.cjs",
|
|
12
13
|
"release:publish-and-deprecate": "node ./scripts/publish-and-deprecate.cjs",
|
|
13
14
|
"release:clean": "bash ./scripts/release-clean.sh",
|
|
14
|
-
"release:pack": "npm run release:clean && npm pack --cache /tmp/mumpix-npm-cache"
|
|
15
|
+
"release:pack": "npm run build && npm run release:clean && node ./scripts/obfuscate.cjs && npm pack --cache /tmp/mumpix-npm-cache ; node ./scripts/restore.cjs"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
17
18
|
"ai",
|
|
@@ -55,5 +56,8 @@
|
|
|
55
56
|
"CHANGELOG.md",
|
|
56
57
|
"README.md",
|
|
57
58
|
"LICENSE"
|
|
58
|
-
]
|
|
59
|
+
],
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"javascript-obfuscator": "^5.4.1"
|
|
62
|
+
}
|
|
59
63
|
}
|
package/src/core/MumpixDB.js
CHANGED
|
@@ -18,6 +18,8 @@ const { MumpixAudit } = require('./audit');
|
|
|
18
18
|
const { recall, recallMany } = require('./recall');
|
|
19
19
|
const { LicenseManager } = require('./license');
|
|
20
20
|
const { getStoredLicenseKey } = require('./auth');
|
|
21
|
+
const { answerTemporalQuery, materializeEvents, materializeEventIndex, extractEventsFromRecord } = require('../temporal/engine');
|
|
22
|
+
const { appendEventIndex } = require('../temporal/indexes');
|
|
21
23
|
|
|
22
24
|
const VALID_MODES = ['eventual', 'strict', 'verified'];
|
|
23
25
|
|
|
@@ -34,6 +36,7 @@ class MumpixDB {
|
|
|
34
36
|
this._opts = opts;
|
|
35
37
|
this._closed = false;
|
|
36
38
|
this._license = license;
|
|
39
|
+
this._temporalCache = null;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
// ─────────────────────────────────────────
|
|
@@ -63,16 +66,17 @@ class MumpixDB {
|
|
|
63
66
|
const store = new MumpixStore(filePath);
|
|
64
67
|
let auditLog = null;
|
|
65
68
|
try {
|
|
66
|
-
store.open({ consistency: requestedConsistency });
|
|
69
|
+
await store.open({ consistency: requestedConsistency });
|
|
67
70
|
|
|
68
71
|
// License initialization & check.
|
|
69
72
|
// Allow dependency injection for local verification and integration tests.
|
|
70
73
|
const resolvedLicenseKey = opts.licenseKey || getStoredLicenseKey() || null;
|
|
71
|
-
const license =
|
|
72
|
-
? opts.licenseManager
|
|
73
|
-
: new LicenseManager(resolvedLicenseKey);
|
|
74
|
+
const license = new LicenseManager(resolvedLicenseKey);
|
|
74
75
|
license.setFileContext(store.header && store.header.fileId ? store.header.fileId : null);
|
|
75
|
-
await license.init();
|
|
76
|
+
const licenseReady = await license.init();
|
|
77
|
+
if (resolvedLicenseKey && !licenseReady) {
|
|
78
|
+
console.warn(`[DEBUG] License rejected: ${license.validationError}`);
|
|
79
|
+
}
|
|
76
80
|
|
|
77
81
|
// Capability-gated mode check with graceful downgrade policy:
|
|
78
82
|
// if a paid mode (strict/verified) is requested with expired/invalid license,
|
|
@@ -84,6 +88,7 @@ class MumpixDB {
|
|
|
84
88
|
license.assertActive(requestedConsistency);
|
|
85
89
|
} catch (modeErr) {
|
|
86
90
|
const msg = String(modeErr && modeErr.message ? modeErr.message : modeErr).toLowerCase();
|
|
91
|
+
console.warn(`[DEBUG] Mode check failed: ${msg}`);
|
|
87
92
|
const downgradeable =
|
|
88
93
|
msg.includes('expired') ||
|
|
89
94
|
msg.includes('invalid for mode') ||
|
|
@@ -97,9 +102,10 @@ class MumpixDB {
|
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
if (effectiveConsistency !== requestedConsistency) {
|
|
105
|
+
console.warn(`[DEBUG] Downgrading consistency to ${effectiveConsistency}`);
|
|
100
106
|
// Re-open with downgraded mode after releasing current FD/lock first.
|
|
101
107
|
store.close();
|
|
102
|
-
store.open({ consistency: effectiveConsistency });
|
|
108
|
+
await store.open({ consistency: effectiveConsistency });
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
if (effectiveConsistency === 'verified') {
|
|
@@ -134,16 +140,31 @@ class MumpixDB {
|
|
|
134
140
|
* @param {string} content
|
|
135
141
|
* @returns {{ id: number, written: boolean, consistency: string }}
|
|
136
142
|
*/
|
|
137
|
-
async remember(content) {
|
|
143
|
+
async remember(content, opts = {}) {
|
|
138
144
|
this._assertOpen();
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
let text = content;
|
|
146
|
+
let meta = opts;
|
|
147
|
+
|
|
148
|
+
if (content && typeof content === 'object' && !Array.isArray(content)) {
|
|
149
|
+
text = content.content;
|
|
150
|
+
meta = {
|
|
151
|
+
...opts,
|
|
152
|
+
workspace: content.workspace ?? opts.workspace,
|
|
153
|
+
repo: content.repo ?? opts.repo,
|
|
154
|
+
source: content.source ?? opts.source,
|
|
155
|
+
ts: content.ts ?? opts.ts,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (typeof text !== 'string' || !text.trim()) {
|
|
160
|
+
throw new TypeError('remember() requires a non-empty string or { content } object');
|
|
141
161
|
}
|
|
142
162
|
|
|
143
163
|
// License Check for Record Limit
|
|
144
164
|
this._license.checkLimit('records', this._store.records.length);
|
|
145
165
|
|
|
146
|
-
const record = this._store.write(
|
|
166
|
+
const record = this._store.write(text, meta);
|
|
167
|
+
this._appendTemporalRecord(record);
|
|
147
168
|
|
|
148
169
|
if (this._audit) {
|
|
149
170
|
this._audit.logWrite(record);
|
|
@@ -234,6 +255,104 @@ class MumpixDB {
|
|
|
234
255
|
}));
|
|
235
256
|
}
|
|
236
257
|
|
|
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
|
+
async events(opts = {}) {
|
|
267
|
+
this._assertOpen();
|
|
268
|
+
const records = this._store.all();
|
|
269
|
+
if (!records.length) return [];
|
|
270
|
+
|
|
271
|
+
if (!opts.query) {
|
|
272
|
+
return this._getTemporalCache().events;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (opts.query && typeof opts.query === 'string' && opts.query.trim()) {
|
|
276
|
+
const topK = Number.isFinite(opts.topK) ? Number(opts.topK) : 50;
|
|
277
|
+
const shortlisted = await recallMany(opts.query, records, {
|
|
278
|
+
embedFn: this._opts.embedFn,
|
|
279
|
+
k: topK,
|
|
280
|
+
});
|
|
281
|
+
return materializeEvents(shortlisted);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return this._getTemporalCache().events;
|
|
285
|
+
}
|
|
286
|
+
|
|
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
|
+
async eventIndex(opts = {}) {
|
|
296
|
+
this._assertOpen();
|
|
297
|
+
const records = this._store.all();
|
|
298
|
+
if (!records.length) {
|
|
299
|
+
return materializeEventIndex([]);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!opts.query) {
|
|
303
|
+
return this._getTemporalCache();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const topK = Number.isFinite(opts.topK) ? Number(opts.topK) : 50;
|
|
307
|
+
const shortlisted = await recallMany(opts.query, records, {
|
|
308
|
+
embedFn: this._opts.embedFn,
|
|
309
|
+
k: topK,
|
|
310
|
+
});
|
|
311
|
+
return materializeEventIndex(shortlisted);
|
|
312
|
+
}
|
|
313
|
+
|
|
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
|
+
async temporalQuery(query, opts = {}) {
|
|
324
|
+
this._assertOpen();
|
|
325
|
+
if (typeof query !== 'string' || !query.trim()) {
|
|
326
|
+
throw new TypeError('temporalQuery() requires a non-empty string');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const records = this._store.all();
|
|
330
|
+
if (!records.length) {
|
|
331
|
+
return {
|
|
332
|
+
ok: false,
|
|
333
|
+
kind: null,
|
|
334
|
+
value: null,
|
|
335
|
+
evidence: [],
|
|
336
|
+
warnings: ['No records available.'],
|
|
337
|
+
parsed_query: null,
|
|
338
|
+
events: [],
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const topK = Number.isFinite(opts.topK) ? Number(opts.topK) : 50;
|
|
343
|
+
const shortlisted = await recallMany(query, records, {
|
|
344
|
+
embedFn: this._opts.embedFn,
|
|
345
|
+
k: topK,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const now =
|
|
349
|
+
opts.now instanceof Date ? opts.now :
|
|
350
|
+
(typeof opts.now === 'string' || typeof opts.now === 'number') ? new Date(opts.now) :
|
|
351
|
+
new Date();
|
|
352
|
+
|
|
353
|
+
return answerTemporalQuery(query, shortlisted, { now });
|
|
354
|
+
}
|
|
355
|
+
|
|
237
356
|
/**
|
|
238
357
|
* Delete all memories.
|
|
239
358
|
*
|
|
@@ -242,6 +361,7 @@ class MumpixDB {
|
|
|
242
361
|
async clear() {
|
|
243
362
|
this._assertOpen();
|
|
244
363
|
const count = this._store.clear();
|
|
364
|
+
this._invalidateTemporalCache();
|
|
245
365
|
|
|
246
366
|
if (this._audit) {
|
|
247
367
|
this._audit.logClear(count);
|
|
@@ -366,6 +486,34 @@ class MumpixDB {
|
|
|
366
486
|
if (this._closed) throw new Error('Database is closed. Call Mumpix.open() again.');
|
|
367
487
|
this._license.assertActive(this._store.header.consistency);
|
|
368
488
|
}
|
|
489
|
+
|
|
490
|
+
_invalidateTemporalCache() {
|
|
491
|
+
this._temporalCache = null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
_temporalSignature() {
|
|
495
|
+
const records = this._store.records || [];
|
|
496
|
+
const last = records.length ? records[records.length - 1] : null;
|
|
497
|
+
return `${records.length}:${last ? last.id : 0}:${last ? last.ts : 0}`;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
_getTemporalCache() {
|
|
501
|
+
const signature = this._temporalSignature();
|
|
502
|
+
if (this._temporalCache && this._temporalCache.signature === signature) {
|
|
503
|
+
return this._temporalCache.index;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const index = materializeEventIndex(this._store.all());
|
|
507
|
+
this._temporalCache = { signature, index };
|
|
508
|
+
return index;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
_appendTemporalRecord(record) {
|
|
512
|
+
if (!this._temporalCache) return;
|
|
513
|
+
const events = extractEventsFromRecord(record);
|
|
514
|
+
appendEventIndex(this._temporalCache.index, events);
|
|
515
|
+
this._temporalCache.signature = this._temporalSignature();
|
|
516
|
+
}
|
|
369
517
|
}
|
|
370
518
|
|
|
371
519
|
module.exports = { MumpixDB };
|
package/src/core/license.js
CHANGED
|
@@ -7,7 +7,7 @@ const https = require('https');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
|
|
9
9
|
// Default embedded public key (official issuer).
|
|
10
|
-
const DEFAULT_MASTER_PUBLIC_KEY_B64 = `
|
|
10
|
+
const DEFAULT_MASTER_PUBLIC_KEY_B64 = `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`;
|
|
11
11
|
|
|
12
12
|
function activeMasterPublicKeyB64() {
|
|
13
13
|
return String(process.env.MUMPIX_LICENSE_PUBLIC_KEY_B64 || DEFAULT_MASTER_PUBLIC_KEY_B64).trim();
|
|
@@ -36,10 +36,6 @@ class LicenseManager {
|
|
|
36
36
|
: 'free';
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// Capability matrix for Option B model.
|
|
40
|
-
// free/community: eventual only
|
|
41
|
-
// developer/teams: eventual + strict
|
|
42
|
-
// compliance/enterprise: eventual + strict + verified
|
|
43
39
|
_capsForTier(tier) {
|
|
44
40
|
const t = this._normalizeTier(tier);
|
|
45
41
|
if (t === 'monthly') return { modes: ['eventual', 'strict'] };
|
|
@@ -62,13 +58,10 @@ class LicenseManager {
|
|
|
62
58
|
|
|
63
59
|
_maxDaysForTier(tier) {
|
|
64
60
|
const t = this._normalizeTier(tier);
|
|
65
|
-
// Monthly subscriptions default to 45-day lease windows for offline/air-gapped tolerance.
|
|
66
61
|
if (t === 'monthly') return envInt('MUMPIX_LICENSE_MAX_DAYS_MONTHLY', 45);
|
|
67
62
|
if (t === 'standard') return envInt('MUMPIX_LICENSE_MAX_DAYS_STANDARD', 45);
|
|
68
63
|
if (t === 'developer') return envInt('MUMPIX_LICENSE_MAX_DAYS_DEVELOPER', 365);
|
|
69
64
|
if (t === 'teams') return envInt('MUMPIX_LICENSE_MAX_DAYS_TEAMS', 365);
|
|
70
|
-
// Enterprise / government / compliance can be long-lived for air-gapped installs.
|
|
71
|
-
// Defaults allow up to 100 years, and operators can tighten/expand via env.
|
|
72
65
|
if (t === 'enterprise') return envInt('MUMPIX_LICENSE_MAX_DAYS_ENTERPRISE', 36500);
|
|
73
66
|
if (t === 'government') return envInt('MUMPIX_LICENSE_MAX_DAYS_GOVERNMENT', 36500);
|
|
74
67
|
if (t === 'compliance' || t === 'verified' || t === 'pro') {
|
|
@@ -160,8 +153,8 @@ class LicenseManager {
|
|
|
160
153
|
globalThis.crypto = require('node:crypto').webcrypto;
|
|
161
154
|
}
|
|
162
155
|
const mlDsaPath = 'file://' + path.resolve(__dirname, 'ml-dsa.mjs');
|
|
163
|
-
const {
|
|
164
|
-
this._ml_dsa =
|
|
156
|
+
const { ml_dsa65 } = await import(mlDsaPath);
|
|
157
|
+
this._ml_dsa = ml_dsa65;
|
|
165
158
|
return await this.validate(this.key);
|
|
166
159
|
} catch (e) {
|
|
167
160
|
console.error('Mumpix Licensing Initialization Failed:', e.message);
|
|
@@ -177,12 +170,18 @@ class LicenseManager {
|
|
|
177
170
|
const payloadRaw = Buffer.from(payloadB64, 'base64').toString();
|
|
178
171
|
const payload = JSON.parse(payloadRaw);
|
|
179
172
|
|
|
180
|
-
// Verify Quantum-Safe Signature (ML-DSA-44)
|
|
181
173
|
const msg = Buffer.from(payloadB64, 'utf-8');
|
|
182
174
|
const sig = Buffer.from(signatureB64, 'base64');
|
|
183
|
-
const
|
|
175
|
+
const pubB64 = activeMasterPublicKeyB64();
|
|
176
|
+
const pub = Buffer.from(pubB64, 'base64');
|
|
177
|
+
|
|
178
|
+
console.log('[CRYPTO DEBUG] Payload Length:', msg.length);
|
|
179
|
+
console.log('[CRYPTO DEBUG] Sig Length:', sig.length);
|
|
180
|
+
console.log('[CRYPTO DEBUG] Pub Length:', pub.length);
|
|
181
|
+
console.log('[CRYPTO DEBUG] Pub Start (B64):', pubB64.substring(0, 20));
|
|
184
182
|
|
|
185
183
|
const isValid = this._ml_dsa.verify(sig, msg, pub);
|
|
184
|
+
console.log('[CRYPTO DEBUG] isValid:', isValid);
|
|
186
185
|
if (!isValid) throw new Error('Quantum signature verification failed');
|
|
187
186
|
|
|
188
187
|
this._validateLeaseWindow(payload);
|
|
@@ -191,8 +190,6 @@ class LicenseManager {
|
|
|
191
190
|
throw new Error('License key has expired');
|
|
192
191
|
}
|
|
193
192
|
|
|
194
|
-
// Optional binding for newly issued licenses.
|
|
195
|
-
// Backward-compatible: if fid missing from payload, accept legacy license.
|
|
196
193
|
if (payload.fid && this.fileId && String(payload.fid) !== String(this.fileId)) {
|
|
197
194
|
throw new Error('License key is bound to a different file');
|
|
198
195
|
}
|
|
@@ -214,7 +211,6 @@ class LicenseManager {
|
|
|
214
211
|
}
|
|
215
212
|
|
|
216
213
|
checkLimit(type, currentCount) {
|
|
217
|
-
// Option B: no record count gate. Gating is by capabilities/mode.
|
|
218
214
|
if (type === 'records') return true;
|
|
219
215
|
|
|
220
216
|
if (type === 'mode') {
|
|
@@ -222,15 +218,15 @@ class LicenseManager {
|
|
|
222
218
|
const caps = this._capsForTier(this.tier);
|
|
223
219
|
if (!caps.modes.includes(requested)) {
|
|
224
220
|
if (this.key && !this.verified && this.validationError) {
|
|
225
|
-
throw new Error(`Mumpix license is invalid for mode
|
|
221
|
+
throw new Error(`Mumpix license is invalid for mode ${requested}: ${this.validationError}`);
|
|
226
222
|
}
|
|
227
223
|
if (requested === 'strict') {
|
|
228
|
-
throw new Error('MumpixDB
|
|
224
|
+
throw new Error('MumpixDB strict mode requires Developer tier or higher. Upgrade at mumpixdb.com.');
|
|
229
225
|
}
|
|
230
226
|
if (requested === 'verified') {
|
|
231
|
-
throw new Error('MumpixDB
|
|
227
|
+
throw new Error('MumpixDB verified mode requires Compliance tier. Upgrade at mumpixdb.com.');
|
|
232
228
|
}
|
|
233
|
-
throw new Error(`MumpixDB mode
|
|
229
|
+
throw new Error(`MumpixDB mode ${requested} is not available on your current tier.`);
|
|
234
230
|
}
|
|
235
231
|
return true;
|
|
236
232
|
}
|
|
@@ -242,7 +238,6 @@ class LicenseManager {
|
|
|
242
238
|
this.checkLimit('mode', mode);
|
|
243
239
|
const now = Date.now();
|
|
244
240
|
this._enforceClockMonotonic(now);
|
|
245
|
-
// Expired licenses degrade to eventual mode only. Strict/verified remain blocked until renewal.
|
|
246
241
|
if (this.expiry && now > Number(this.expiry) && mode !== 'eventual') {
|
|
247
242
|
throw new Error('Mumpix license expired. Renew license to continue using this mode.');
|
|
248
243
|
}
|
|
@@ -269,4 +264,4 @@ class LicenseManager {
|
|
|
269
264
|
}
|
|
270
265
|
}
|
|
271
266
|
|
|
272
|
-
module.exports = { LicenseManager };
|
|
267
|
+
module.exports = { LicenseManager };
|