grepmax 0.26.54 → 0.26.56
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +9 -7
- package/dist/commands/add.js +2 -0
- package/dist/commands/doctor.js +9 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/remove.js +2 -0
- package/dist/commands/repair.js +2 -0
- package/dist/commands/watch.js +12 -0
- package/dist/lib/daemon/daemon.js +201 -82
- package/dist/lib/daemon/mlx-server-manager.js +24 -8
- package/dist/lib/store/maintenance-policy.js +172 -0
- package/dist/lib/store/meta-cache.js +34 -1
- package/dist/lib/store/vector-db.js +76 -29
- package/dist/lib/utils/autostart.js +34 -7
- package/dist/lib/utils/daemon-launcher.js +6 -0
- package/dist/lib/utils/kernel-zone.js +140 -21
- package/dist/lib/utils/safety-latch.js +121 -0
- package/dist/lib/workers/pool.js +97 -10
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/watch-lease.js +13 -5
|
@@ -203,8 +203,8 @@ class MlxServerManager {
|
|
|
203
203
|
}
|
|
204
204
|
ensureInternal(model) {
|
|
205
205
|
return __awaiter(this, void 0, void 0, function* () {
|
|
206
|
-
var _a, _b, _c;
|
|
207
|
-
var
|
|
206
|
+
var _a, _b, _c, _d, _e;
|
|
207
|
+
var _f, _g, _h, _j, _k;
|
|
208
208
|
this.phase = "probing";
|
|
209
209
|
const health = yield this.probeHealth(this.port);
|
|
210
210
|
if (this.shouldStop())
|
|
@@ -256,15 +256,31 @@ class MlxServerManager {
|
|
|
256
256
|
console.warn("[daemon] MLX embed server not found — falling back to CPU embeddings");
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
// A health/termination await may outlive the daemon's outer admission.
|
|
260
|
+
// Refusal must not leak a log descriptor or look like a successful startup.
|
|
261
|
+
// Disable automatic retries until an explicit admitted ensure occurs;
|
|
262
|
+
// fire-and-forget health callers must catch the propagated rejection.
|
|
263
|
+
try {
|
|
264
|
+
(_b = (_a = this.deps).assertExpansionAdmission) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
this.enabled = false;
|
|
268
|
+
this.phase = "failed";
|
|
269
|
+
this.lastError = `MLX expansion admission refused: ${error instanceof Error ? error.message : "host admission unavailable"}`;
|
|
270
|
+
console.error(`[daemon] ${this.lastError}`);
|
|
271
|
+
throw new Error(this.lastError);
|
|
272
|
+
}
|
|
273
|
+
if (this.shouldStop())
|
|
274
|
+
return;
|
|
275
|
+
const owner = (_f = (_d = (_c = this.deps).createOwnerToken) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _f !== void 0 ? _f : (0, node_crypto_1.randomUUID)();
|
|
276
|
+
const openLog = (_g = this.deps.openLog) !== null && _g !== void 0 ? _g : log_rotate_1.openRotatedLog;
|
|
277
|
+
const closeFd = (_h = this.deps.closeFd) !== null && _h !== void 0 ? _h : fs.closeSync;
|
|
262
278
|
const logFd = openLog(path.join(config_1.PATHS.logsDir, "mlx-embed-server.log"));
|
|
263
279
|
const env = Object.assign(Object.assign({}, process.env), { MLX_EMBED_MODEL: model, GMAX_EMBED_OWNER_TOKEN: owner, HF_HOME: (0, mlx_hf_cache_1.resolveMlxHfHome)(model) });
|
|
264
280
|
this.phase = "starting";
|
|
265
281
|
let child;
|
|
266
282
|
try {
|
|
267
|
-
child = ((
|
|
283
|
+
child = ((_j = this.deps.spawn) !== null && _j !== void 0 ? _j : node_child_process_1.spawn)("uv", ["run", "python", "server.py"], {
|
|
268
284
|
cwd: serverDir,
|
|
269
285
|
detached: true,
|
|
270
286
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -281,7 +297,7 @@ class MlxServerManager {
|
|
|
281
297
|
try {
|
|
282
298
|
closeFd(logFd);
|
|
283
299
|
}
|
|
284
|
-
catch (
|
|
300
|
+
catch (_l) { }
|
|
285
301
|
}
|
|
286
302
|
if (!child.pid) {
|
|
287
303
|
this.lastError = "spawned MLX process has no PID";
|
|
@@ -340,7 +356,7 @@ class MlxServerManager {
|
|
|
340
356
|
child.off("error", onError);
|
|
341
357
|
child.off("exit", onExit);
|
|
342
358
|
yield this.terminateOwned(candidate);
|
|
343
|
-
this.lastError = (
|
|
359
|
+
this.lastError = (_k = (_e = startup.error) === null || _e === void 0 ? void 0 : _e.message) !== null && _k !== void 0 ? _k : (this.shouldStop() ? "MLX startup cancelled" : "MLX startup timed out");
|
|
344
360
|
if (!this.shouldStop()) {
|
|
345
361
|
this.phase = "failed";
|
|
346
362
|
console.error(`[daemon] ${this.lastError} — falling back to CPU embeddings`);
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DiskPressureError = exports.FULL_TABLE_MAINTENANCE_DISABLED_REASON = void 0;
|
|
37
|
+
exports.fullTableMaintenanceDisabled = fullTableMaintenanceDisabled;
|
|
38
|
+
exports.availableStoreDiskBytes = availableStoreDiskBytes;
|
|
39
|
+
exports.assertFreshDiskMutationAllowed = assertFreshDiskMutationAllowed;
|
|
40
|
+
exports.storeMutationDeniedReason = storeMutationDeniedReason;
|
|
41
|
+
exports.assertStoreMutationAllowed = assertStoreMutationAllowed;
|
|
42
|
+
exports.maintenancePolicyPath = maintenancePolicyPath;
|
|
43
|
+
exports.recordMaintenanceContainment = recordMaintenanceContainment;
|
|
44
|
+
const node_crypto_1 = require("node:crypto");
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
47
|
+
const config_1 = require("../../config");
|
|
48
|
+
const autostart_1 = require("../utils/autostart");
|
|
49
|
+
const safety_latch_1 = require("../utils/safety-latch");
|
|
50
|
+
exports.FULL_TABLE_MAINTENANCE_DISABLED_REASON = "full-table maintenance disabled by host-safety containment; disk recovery is pending";
|
|
51
|
+
/** This release has no production override: force, configuration and persisted
|
|
52
|
+
* state cannot enable rewriting. Tests may mock this module to cover the old
|
|
53
|
+
* native algorithm without turning it into a supported production entry point. */
|
|
54
|
+
function fullTableMaintenanceDisabled() {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
class DiskPressureError extends Error {
|
|
58
|
+
constructor(message = "Disk critically low or unknown — writes suspended") {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = "DiskPressureError";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.DiskPressureError = DiskPressureError;
|
|
64
|
+
/** Fresh, bounded filesystem sample. ENOENT alone permits trying an ancestor;
|
|
65
|
+
* permission errors, invalid values and all other uncertainty refuse writes. */
|
|
66
|
+
function availableStoreDiskBytes(storeDir) {
|
|
67
|
+
let existing = path.resolve(storeDir);
|
|
68
|
+
for (;;) {
|
|
69
|
+
try {
|
|
70
|
+
const stats = fs.statfsSync(existing);
|
|
71
|
+
const available = stats.bavail * stats.bsize;
|
|
72
|
+
return Number.isFinite(available) && available >= 0
|
|
73
|
+
? available
|
|
74
|
+
: Number.NaN;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const parent = path.dirname(existing);
|
|
78
|
+
if (error.code !== "ENOENT" ||
|
|
79
|
+
parent === existing)
|
|
80
|
+
return Number.NaN;
|
|
81
|
+
existing = parent;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Reusable admission for LMDB and other store mutations. Never cached. */
|
|
86
|
+
function assertFreshDiskMutationAllowed(storeDir) {
|
|
87
|
+
const available = availableStoreDiskBytes(storeDir);
|
|
88
|
+
if (!Number.isFinite(available) || available < config_1.DISK_CRITICAL_BYTES)
|
|
89
|
+
throw new DiskPressureError();
|
|
90
|
+
return available;
|
|
91
|
+
}
|
|
92
|
+
function storeMutationDeniedReason() {
|
|
93
|
+
const safety = (0, safety_latch_1.safetyStopReason)();
|
|
94
|
+
if (safety !== null)
|
|
95
|
+
return `host safety stop: ${safety}`;
|
|
96
|
+
return (0, autostart_1.autostartDisabledReason)() !== null
|
|
97
|
+
? "store mutations are quarantined while daemon autostart is disabled"
|
|
98
|
+
: null;
|
|
99
|
+
}
|
|
100
|
+
function assertStoreMutationAllowed() {
|
|
101
|
+
const reason = storeMutationDeniedReason();
|
|
102
|
+
if (reason !== null)
|
|
103
|
+
throw new Error(`gmax store mutation blocked: ${reason}`);
|
|
104
|
+
}
|
|
105
|
+
function maintenancePolicyPath(storeDir) {
|
|
106
|
+
return path.join(storeDir, ".gmax-maintenance-policy.json");
|
|
107
|
+
}
|
|
108
|
+
/** Persist a zero-rewrite receipt once. Unknown/corrupt/unwritable state never
|
|
109
|
+
* weakens the immutable default. No timestamps or budgets authorize native work. */
|
|
110
|
+
function recordMaintenanceContainment(storeDir) {
|
|
111
|
+
let temporary;
|
|
112
|
+
try {
|
|
113
|
+
fs.mkdirSync(storeDir, { recursive: true, mode: 0o700 });
|
|
114
|
+
const storeIdentity = fs.realpathSync(storeDir);
|
|
115
|
+
const target = maintenancePolicyPath(storeIdentity);
|
|
116
|
+
if (fs.existsSync(target)) {
|
|
117
|
+
const stat = fs.lstatSync(target);
|
|
118
|
+
if (!stat.isFile() || stat.size > 16 * 1024)
|
|
119
|
+
throw new Error("unsafe maintenance policy file");
|
|
120
|
+
const previous = JSON.parse(fs.readFileSync(target, "utf8"));
|
|
121
|
+
if (previous.schemaVersion === 1 &&
|
|
122
|
+
previous.storeIdentity === storeIdentity &&
|
|
123
|
+
previous.mode === "disabled" &&
|
|
124
|
+
previous.rewriteBudgetBytes === 0 &&
|
|
125
|
+
previous.cleanupPending === true) {
|
|
126
|
+
fs.chmodSync(target, 0o600);
|
|
127
|
+
return exports.FULL_TABLE_MAINTENANCE_DISABLED_REASON;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const policy = {
|
|
131
|
+
schemaVersion: 1,
|
|
132
|
+
storeIdentity,
|
|
133
|
+
mode: "disabled",
|
|
134
|
+
reason: exports.FULL_TABLE_MAINTENANCE_DISABLED_REASON,
|
|
135
|
+
recordedAt: Date.now(),
|
|
136
|
+
rewriteBudgetBytes: 0,
|
|
137
|
+
cleanupPending: true,
|
|
138
|
+
};
|
|
139
|
+
temporary = path.join(storeIdentity, `.gmax-maintenance-${(0, node_crypto_1.randomUUID)()}.tmp`);
|
|
140
|
+
const descriptor = fs.openSync(temporary, "wx", 0o600);
|
|
141
|
+
try {
|
|
142
|
+
fs.writeFileSync(descriptor, `${JSON.stringify(policy)}\n`);
|
|
143
|
+
fs.fsyncSync(descriptor);
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
fs.closeSync(descriptor);
|
|
147
|
+
}
|
|
148
|
+
fs.renameSync(temporary, target);
|
|
149
|
+
temporary = undefined;
|
|
150
|
+
const directory = fs.openSync(storeIdentity, "r");
|
|
151
|
+
try {
|
|
152
|
+
fs.fsyncSync(directory);
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
fs.closeSync(directory);
|
|
156
|
+
}
|
|
157
|
+
return exports.FULL_TABLE_MAINTENANCE_DISABLED_REASON;
|
|
158
|
+
}
|
|
159
|
+
catch (_a) {
|
|
160
|
+
return `${exports.FULL_TABLE_MAINTENANCE_DISABLED_REASON}; persistent policy unavailable or invalid (still disabled)`;
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
if (temporary) {
|
|
164
|
+
try {
|
|
165
|
+
fs.unlinkSync(temporary);
|
|
166
|
+
}
|
|
167
|
+
catch (_b) {
|
|
168
|
+
// Failed receipt cleanup cannot authorize maintenance.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -67,14 +67,39 @@ const fs = __importStar(require("node:fs"));
|
|
|
67
67
|
const path = __importStar(require("node:path"));
|
|
68
68
|
const lmdb_1 = require("lmdb");
|
|
69
69
|
const cleanup_1 = require("../utils/cleanup");
|
|
70
|
+
const maintenance_policy_1 = require("./maintenance-policy");
|
|
70
71
|
class MetaCache {
|
|
71
72
|
constructor(lmdbPath) {
|
|
73
|
+
this.lmdbPath = lmdbPath;
|
|
72
74
|
this.closed = false;
|
|
73
|
-
|
|
75
|
+
let diskAllowsWrites = false;
|
|
76
|
+
try {
|
|
77
|
+
(0, maintenance_policy_1.assertFreshDiskMutationAllowed)(lmdbPath);
|
|
78
|
+
diskAllowsWrites = true;
|
|
79
|
+
}
|
|
80
|
+
catch (_a) {
|
|
81
|
+
/* Unknown/critical disk allows existing-cache reads only. */
|
|
82
|
+
}
|
|
83
|
+
this.readOnly = (0, maintenance_policy_1.storeMutationDeniedReason)() !== null || !diskAllowsWrites;
|
|
84
|
+
if (this.readOnly) {
|
|
85
|
+
// Quarantine permits reads of an existing cache, never creating one.
|
|
86
|
+
try {
|
|
87
|
+
fs.lstatSync(lmdbPath);
|
|
88
|
+
}
|
|
89
|
+
catch (_b) {
|
|
90
|
+
throw new Error("gmax metadata cache is quarantined or disk-constrained and unavailable");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
95
|
+
(0, maintenance_policy_1.assertFreshDiskMutationAllowed)(lmdbPath);
|
|
96
|
+
fs.mkdirSync(path.dirname(lmdbPath), { recursive: true });
|
|
97
|
+
}
|
|
74
98
|
this.db = (0, lmdb_1.open)({
|
|
75
99
|
path: lmdbPath,
|
|
76
100
|
compression: true,
|
|
77
101
|
cache: true,
|
|
102
|
+
readOnly: this.readOnly,
|
|
78
103
|
});
|
|
79
104
|
this.unregisterCleanup = (0, cleanup_1.registerCleanup)(() => this.close());
|
|
80
105
|
}
|
|
@@ -129,11 +154,19 @@ class MetaCache {
|
|
|
129
154
|
});
|
|
130
155
|
}
|
|
131
156
|
put(filePath, entry) {
|
|
157
|
+
this.assertWritable();
|
|
132
158
|
this.db.put(filePath, entry);
|
|
133
159
|
}
|
|
134
160
|
delete(filePath) {
|
|
161
|
+
this.assertWritable();
|
|
135
162
|
this.db.remove(filePath);
|
|
136
163
|
}
|
|
164
|
+
assertWritable() {
|
|
165
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
166
|
+
(0, maintenance_policy_1.assertFreshDiskMutationAllowed)(this.lmdbPath);
|
|
167
|
+
if (this.readOnly)
|
|
168
|
+
throw new Error("gmax metadata cache was opened read-only; reopen after reviewing containment");
|
|
169
|
+
}
|
|
137
170
|
entries() {
|
|
138
171
|
return __asyncGenerator(this, arguments, function* entries_1() {
|
|
139
172
|
var _a, e_3, _b, _c;
|
|
@@ -66,6 +66,7 @@ const query_timeout_1 = require("../utils/query-timeout");
|
|
|
66
66
|
const ann_config_1 = require("./ann-config");
|
|
67
67
|
const compaction_result_1 = require("./compaction-result");
|
|
68
68
|
const lancedb = __importStar(require("./lance-sdk"));
|
|
69
|
+
const maintenance_policy_1 = require("./maintenance-policy");
|
|
69
70
|
const store_lease_1 = require("./store-lease");
|
|
70
71
|
/**
|
|
71
72
|
* Find abandoned Lance scratch files under `<lancedbDir>/ *.lance/data`.
|
|
@@ -134,13 +135,8 @@ function sweepStaleLanceTempFiles(lancedbDir, minAgeMs = config_1.STALE_TEMP_FIL
|
|
|
134
135
|
}
|
|
135
136
|
return { filesRemoved, bytesFreed };
|
|
136
137
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
super(message);
|
|
140
|
-
this.name = "DiskPressureError";
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
exports.DiskPressureError = DiskPressureError;
|
|
138
|
+
var maintenance_policy_2 = require("./maintenance-policy");
|
|
139
|
+
Object.defineProperty(exports, "DiskPressureError", { enumerable: true, get: function () { return maintenance_policy_2.DiskPressureError; } });
|
|
144
140
|
/**
|
|
145
141
|
* Detects "Not found: <hash>.lance" errors from LanceDB — these indicate the
|
|
146
142
|
* manifest references a fragment file that doesn't exist on disk, typically
|
|
@@ -279,6 +275,10 @@ class VectorDB {
|
|
|
279
275
|
* Call once from the daemon — replaces per-processor maintenance intervals.
|
|
280
276
|
*/
|
|
281
277
|
startMaintenanceLoop(runOperation) {
|
|
278
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
279
|
+
this.recordSkippedCompaction((0, maintenance_policy_1.recordMaintenanceContainment)(this.lancedbDir));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
282
|
if (runOperation)
|
|
283
283
|
this.maintenanceRunner = runOperation;
|
|
284
284
|
if (this.maintenanceTimer)
|
|
@@ -370,11 +370,20 @@ class VectorDB {
|
|
|
370
370
|
if (this.closed) {
|
|
371
371
|
throw new Error("VectorDB connection is closed");
|
|
372
372
|
}
|
|
373
|
+
const creatingStore = !fs.existsSync(this.lancedbDir);
|
|
374
|
+
if (creatingStore) {
|
|
375
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
376
|
+
(0, maintenance_policy_1.assertFreshDiskMutationAllowed)(this.lancedbDir);
|
|
377
|
+
}
|
|
373
378
|
yield this.getLease();
|
|
374
379
|
if (this.closed) {
|
|
375
380
|
throw new Error("VectorDB connection is closed");
|
|
376
381
|
}
|
|
377
382
|
if (!this.db) {
|
|
383
|
+
if (creatingStore || !fs.existsSync(this.lancedbDir)) {
|
|
384
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
385
|
+
(0, maintenance_policy_1.assertFreshDiskMutationAllowed)(this.lancedbDir);
|
|
386
|
+
}
|
|
378
387
|
fs.mkdirSync(this.lancedbDir, { recursive: true });
|
|
379
388
|
this.session = createLanceSession();
|
|
380
389
|
// 0.38 accepts the legacy third argument in its types but drops it in
|
|
@@ -469,23 +478,18 @@ class VectorDB {
|
|
|
469
478
|
});
|
|
470
479
|
}
|
|
471
480
|
getAvailableBytes() {
|
|
472
|
-
|
|
473
|
-
const stats = fs.statfsSync(this.lancedbDir);
|
|
474
|
-
return stats.bavail * stats.bsize;
|
|
475
|
-
}
|
|
476
|
-
catch (_a) {
|
|
477
|
-
return Number.MAX_SAFE_INTEGER; // fail-open
|
|
478
|
-
}
|
|
481
|
+
return (0, maintenance_policy_1.availableStoreDiskBytes)(this.lancedbDir);
|
|
479
482
|
}
|
|
480
|
-
checkDiskPressure() {
|
|
483
|
+
checkDiskPressure(fresh = false) {
|
|
481
484
|
const now = Date.now();
|
|
482
|
-
if (
|
|
485
|
+
if (!fresh &&
|
|
486
|
+
now - this.lastDiskCheckMs < VectorDB.DISK_CHECK_INTERVAL_MS) {
|
|
483
487
|
return this.diskPressure;
|
|
484
488
|
}
|
|
485
489
|
this.lastDiskCheckMs = now;
|
|
486
490
|
const avail = this.getAvailableBytes();
|
|
487
491
|
let level;
|
|
488
|
-
if (avail < config_1.DISK_CRITICAL_BYTES) {
|
|
492
|
+
if (!Number.isFinite(avail) || avail < 0 || avail < config_1.DISK_CRITICAL_BYTES) {
|
|
489
493
|
level = "critical";
|
|
490
494
|
}
|
|
491
495
|
else if (avail < config_1.DISK_LOW_BYTES) {
|
|
@@ -497,7 +501,7 @@ class VectorDB {
|
|
|
497
501
|
if (level !== this.lastLoggedPressure) {
|
|
498
502
|
const freeStr = `${(avail / 1024 / 1024 / 1024).toFixed(1)}GB`;
|
|
499
503
|
if (level === "critical") {
|
|
500
|
-
(0, logger_1.log)("vectordb", `CRITICAL: disk space critically low (${freeStr} free) — writes suspended`);
|
|
504
|
+
(0, logger_1.log)("vectordb", `CRITICAL: ${Number.isFinite(avail) ? `disk space critically low (${freeStr} free)` : "disk space unknown"} — writes suspended`);
|
|
501
505
|
}
|
|
502
506
|
else if (level === "low") {
|
|
503
507
|
(0, logger_1.log)("vectordb", `WARNING: disk space low (${freeStr} free) — compaction limited`);
|
|
@@ -511,8 +515,8 @@ class VectorDB {
|
|
|
511
515
|
return level;
|
|
512
516
|
}
|
|
513
517
|
ensureDiskOk() {
|
|
514
|
-
if (this.checkDiskPressure() === "critical") {
|
|
515
|
-
throw new DiskPressureError();
|
|
518
|
+
if (this.checkDiskPressure(true) === "critical") {
|
|
519
|
+
throw new maintenance_policy_1.DiskPressureError();
|
|
516
520
|
}
|
|
517
521
|
}
|
|
518
522
|
/**
|
|
@@ -522,6 +526,8 @@ class VectorDB {
|
|
|
522
526
|
*/
|
|
523
527
|
withWriteGate(fn) {
|
|
524
528
|
return __awaiter(this, void 0, void 0, function* () {
|
|
529
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
530
|
+
this.ensureDiskOk();
|
|
525
531
|
while (this.exclusiveMutationPromise || this.compactingPromise) {
|
|
526
532
|
if (this.closed)
|
|
527
533
|
throw new Error("VectorDB connection is closed");
|
|
@@ -529,6 +535,8 @@ class VectorDB {
|
|
|
529
535
|
}
|
|
530
536
|
if (this.closed)
|
|
531
537
|
throw new Error("VectorDB connection is closed");
|
|
538
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
539
|
+
this.ensureDiskOk();
|
|
532
540
|
this.activeWrites++;
|
|
533
541
|
try {
|
|
534
542
|
return yield fn();
|
|
@@ -567,6 +575,8 @@ class VectorDB {
|
|
|
567
575
|
}
|
|
568
576
|
withExclusiveTableMutation(mutation) {
|
|
569
577
|
return __awaiter(this, void 0, void 0, function* () {
|
|
578
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
579
|
+
this.ensureDiskOk();
|
|
570
580
|
if (this.closed)
|
|
571
581
|
throw new Error("VectorDB connection is closed");
|
|
572
582
|
if (this.exclusiveMutationPromise) {
|
|
@@ -592,6 +602,8 @@ class VectorDB {
|
|
|
592
602
|
});
|
|
593
603
|
}
|
|
594
604
|
yield Promise.all([this.drainWrites(), this.drainCompactions()]);
|
|
605
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
606
|
+
this.ensureDiskOk();
|
|
595
607
|
const db = yield this.getDb();
|
|
596
608
|
const result = yield mutation(db);
|
|
597
609
|
this.markWriteCommitted();
|
|
@@ -761,6 +773,14 @@ class VectorDB {
|
|
|
761
773
|
}
|
|
762
774
|
ensureTable() {
|
|
763
775
|
return __awaiter(this, void 0, void 0, function* () {
|
|
776
|
+
if ((0, maintenance_policy_1.storeMutationDeniedReason)() !== null ||
|
|
777
|
+
this.checkDiskPressure(true) === "critical") {
|
|
778
|
+
const existing = yield this.openExistingTableUnsafe();
|
|
779
|
+
if (!existing)
|
|
780
|
+
throw new Error("gmax store is quarantined and has no existing table");
|
|
781
|
+
yield this.validateSchema(existing);
|
|
782
|
+
return existing;
|
|
783
|
+
}
|
|
764
784
|
return this.withWriteGate(() => this.ensureTableUnsafe());
|
|
765
785
|
});
|
|
766
786
|
}
|
|
@@ -774,6 +794,7 @@ class VectorDB {
|
|
|
774
794
|
catch (err) {
|
|
775
795
|
if (!isMissingTableError(err))
|
|
776
796
|
throw err;
|
|
797
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
777
798
|
(0, logger_1.log)("db", `Creating table (${this.vectorDim}d)`);
|
|
778
799
|
const schema = this.buildSchema();
|
|
779
800
|
table = yield db.createTable(TABLE_NAME, [this.seedRow()], {
|
|
@@ -784,6 +805,7 @@ class VectorDB {
|
|
|
784
805
|
return table;
|
|
785
806
|
}
|
|
786
807
|
yield this.validateSchema(table);
|
|
808
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
787
809
|
yield this.evolveSchema(table);
|
|
788
810
|
return table;
|
|
789
811
|
});
|
|
@@ -903,6 +925,12 @@ class VectorDB {
|
|
|
903
925
|
}
|
|
904
926
|
createFTSIndex() {
|
|
905
927
|
return __awaiter(this, arguments, void 0, function* (rebuild = false, retries = 5) {
|
|
928
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
929
|
+
if (rebuild)
|
|
930
|
+
throw new Error(maintenance_policy_1.FULL_TABLE_MAINTENANCE_DISABLED_REASON);
|
|
931
|
+
// Existing indexes remain searchable; absence does not authorize a build.
|
|
932
|
+
return this.adoptFTSIndex();
|
|
933
|
+
}
|
|
906
934
|
return this.withWriteGate(() => this.createFTSIndexUnsafe(rebuild, retries));
|
|
907
935
|
});
|
|
908
936
|
}
|
|
@@ -939,7 +967,9 @@ class VectorDB {
|
|
|
939
967
|
return __awaiter(this, void 0, void 0, function* () {
|
|
940
968
|
if (this.ftsIndexEnsured)
|
|
941
969
|
return;
|
|
942
|
-
const table = yield this.
|
|
970
|
+
const table = yield this.openExistingTableUnsafe();
|
|
971
|
+
if (!table)
|
|
972
|
+
throw new Error("[vector-db] No existing table or FTS index; index maintenance is disabled");
|
|
943
973
|
const indices = yield table.listIndices();
|
|
944
974
|
const existing = indices.find((index) => index.name === "content_idx" || index.columns.includes("content"));
|
|
945
975
|
if (!existing) {
|
|
@@ -950,6 +980,10 @@ class VectorDB {
|
|
|
950
980
|
}
|
|
951
981
|
createVectorIndex() {
|
|
952
982
|
return __awaiter(this, arguments, void 0, function* (rebuild = false, retries = 5, checkOnly = false) {
|
|
983
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
984
|
+
(0, logger_1.debug)("vectordb", "ANN/index maintenance disabled by host-safety containment");
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
953
987
|
if (this.checkDiskPressure() !== "ok") {
|
|
954
988
|
(0, logger_1.debug)("vectordb", "ANN index skipped under disk pressure");
|
|
955
989
|
return false;
|
|
@@ -1182,6 +1216,10 @@ class VectorDB {
|
|
|
1182
1216
|
return __awaiter(this, arguments, void 0, function* (retries = COMPACTION_MAX_ATTEMPTS, retentionMs = 0, bypassExclusiveMutation = false) {
|
|
1183
1217
|
var _a;
|
|
1184
1218
|
var _b;
|
|
1219
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
1220
|
+
this.lastOptimizeDidWork = false;
|
|
1221
|
+
return this.recordSkippedCompaction((0, maintenance_policy_1.recordMaintenanceContainment)(this.lancedbDir));
|
|
1222
|
+
}
|
|
1185
1223
|
if (!bypassExclusiveMutation) {
|
|
1186
1224
|
while (this.exclusiveMutationPromise)
|
|
1187
1225
|
yield this.exclusiveMutationPromise;
|
|
@@ -1372,10 +1410,14 @@ class VectorDB {
|
|
|
1372
1410
|
/**
|
|
1373
1411
|
* Run FTS rebuild + optimize as a single serialized operation.
|
|
1374
1412
|
* Safe to call from multiple project processors — only one runs at a time.
|
|
1375
|
-
*
|
|
1413
|
+
* Reports bloat without another whole-table rewrite.
|
|
1376
1414
|
*/
|
|
1377
1415
|
runMaintenance() {
|
|
1378
1416
|
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
1417
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
1418
|
+
this.lastOptimizeDidWork = false;
|
|
1419
|
+
return this.recordSkippedCompaction((0, maintenance_policy_1.recordMaintenanceContainment)(this.lancedbDir));
|
|
1420
|
+
}
|
|
1379
1421
|
if (this.maintenanceRunning) {
|
|
1380
1422
|
(0, logger_1.debug)("vectordb", "Maintenance already running, skipping");
|
|
1381
1423
|
return (0, compaction_result_1.skippedCompaction)("maintenance already running");
|
|
@@ -1426,7 +1468,7 @@ class VectorDB {
|
|
|
1426
1468
|
// Track across both passes: `lastOptimizeDidWork` is reset per optimize()
|
|
1427
1469
|
// call, so a productive first pass followed by a barren bloat retry would
|
|
1428
1470
|
// otherwise read as unproductive and trigger a spurious backoff.
|
|
1429
|
-
|
|
1471
|
+
const didWork = this.lastOptimizeDidWork;
|
|
1430
1472
|
if (result.status === "completed" && this.lastOptimizeDidWork) {
|
|
1431
1473
|
const table = yield this.openExistingTableUnsafe();
|
|
1432
1474
|
if (table) {
|
|
@@ -1443,17 +1485,17 @@ class VectorDB {
|
|
|
1443
1485
|
const diskSize = this.getDirectorySize(this.lancedbDir);
|
|
1444
1486
|
const logicalSize = stats.totalBytes;
|
|
1445
1487
|
const bloatRatio = logicalSize > 0 ? diskSize / logicalSize : 0;
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
(0, logger_1.log)("vectordb", `Bloat detected after optimize: ${(diskSize / 1024 / 1024).toFixed(0)}MB disk vs ${(logicalSize / 1024 / 1024).toFixed(0)}MB logical (${bloatRatio.toFixed(1)}x) — retrying`);
|
|
1449
|
-
yield new Promise((r) => setTimeout(r, 2000));
|
|
1450
|
-
result = yield this.optimize(5, 0, true);
|
|
1451
|
-
didWork = didWork || this.lastOptimizeDidWork;
|
|
1488
|
+
if (bloatRatio > 2.0) {
|
|
1489
|
+
(0, logger_1.log)("vectordb", `Bloat remains after optimize: ${bloatRatio.toFixed(1)}x; additional rewrite disabled`);
|
|
1452
1490
|
}
|
|
1453
1491
|
}
|
|
1454
1492
|
}
|
|
1455
1493
|
this.noteCompaction(didWork);
|
|
1456
1494
|
}
|
|
1495
|
+
if (result && result.status !== "completed")
|
|
1496
|
+
return result;
|
|
1497
|
+
if (result === null || result === void 0 ? void 0 : result.cleanupReason)
|
|
1498
|
+
return result;
|
|
1457
1499
|
const maintainedTable = yield this.openExistingTableUnsafe();
|
|
1458
1500
|
this.maintainedTableVersion =
|
|
1459
1501
|
yield this.readTableVersion(maintainedTable);
|
|
@@ -1509,6 +1551,11 @@ class VectorDB {
|
|
|
1509
1551
|
}
|
|
1510
1552
|
compactIfNeeded() {
|
|
1511
1553
|
return __awaiter(this, arguments, void 0, function* (threshold = config_1.FRAGMENT_COMPACT_THRESHOLD) {
|
|
1554
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
1555
|
+
this.lastOptimizeDidWork = false;
|
|
1556
|
+
this.recordSkippedCompaction((0, maintenance_policy_1.recordMaintenanceContainment)(this.lancedbDir));
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1512
1559
|
if (this.maintenanceRunning)
|
|
1513
1560
|
return false;
|
|
1514
1561
|
this.maintenanceRunning = true;
|
|
@@ -35,10 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.autostartDisabledReason = autostartDisabledReason;
|
|
37
37
|
exports.isAutostartDisabled = isAutostartDisabled;
|
|
38
|
+
exports.daemonStartDeniedReason = daemonStartDeniedReason;
|
|
38
39
|
exports.autostartDisabledUndo = autostartDisabledUndo;
|
|
39
40
|
exports.autostartDisabledNotice = autostartDisabledNotice;
|
|
40
41
|
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
41
43
|
const config_1 = require("../../config");
|
|
44
|
+
const safety_latch_1 = require("./safety-latch");
|
|
42
45
|
/**
|
|
43
46
|
* Kill switch for *implicit* daemon auto-start. Create
|
|
44
47
|
* ~/.gmax/autostart-disabled (or export GMAX_NO_AUTOSTART=1) to keep session
|
|
@@ -46,8 +49,8 @@ const config_1 = require("../../config");
|
|
|
46
49
|
* reviving the daemon — needed when the host is quarantined and the daemon's
|
|
47
50
|
* write volume is the thing under investigation.
|
|
48
51
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
52
|
+
* Safety containment also gates explicit daemon launches. A launch is not
|
|
53
|
+
* authorization to remove an existing host quarantine.
|
|
51
54
|
*
|
|
52
55
|
* plugins/grepmax/hooks/watch-lease.js carries a plain-JS copy of these semantics
|
|
53
56
|
* (env var checked first, then the file) because a SessionStart hook cannot
|
|
@@ -56,16 +59,35 @@ const config_1 = require("../../config");
|
|
|
56
59
|
function autostartDisabledReason() {
|
|
57
60
|
if (process.env.GMAX_NO_AUTOSTART === "1")
|
|
58
61
|
return "env";
|
|
59
|
-
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
if ((0, safety_latch_1.safetyStopReason)() !== null)
|
|
63
|
+
return "safety";
|
|
64
|
+
const markers = new Set([
|
|
65
|
+
config_1.PATHS.autostartDisabledFile,
|
|
66
|
+
...(config_1.PATHS.sharedRoot
|
|
67
|
+
? [path.join(config_1.PATHS.sharedRoot, "autostart-disabled")]
|
|
68
|
+
: []),
|
|
69
|
+
]);
|
|
70
|
+
for (const marker of markers) {
|
|
71
|
+
try {
|
|
72
|
+
fs.lstatSync(marker);
|
|
73
|
+
return "file";
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error.code !== "ENOENT")
|
|
77
|
+
return "file";
|
|
78
|
+
}
|
|
64
79
|
}
|
|
80
|
+
return null;
|
|
65
81
|
}
|
|
66
82
|
function isAutostartDisabled() {
|
|
67
83
|
return autostartDisabledReason() !== null;
|
|
68
84
|
}
|
|
85
|
+
function daemonStartDeniedReason() {
|
|
86
|
+
const safety = (0, safety_latch_1.safetyStopReason)();
|
|
87
|
+
if (safety !== null)
|
|
88
|
+
return `host safety stop: ${safety}`;
|
|
89
|
+
return isAutostartDisabled() ? "daemon startup is quarantined" : null;
|
|
90
|
+
}
|
|
69
91
|
/**
|
|
70
92
|
* One-line notice for commands that fell back to in-process work because the
|
|
71
93
|
* kill switch is on, or null when it isn't. Names the specific undo step so
|
|
@@ -76,11 +98,16 @@ function autostartDisabledUndo() {
|
|
|
76
98
|
const reason = autostartDisabledReason();
|
|
77
99
|
if (!reason)
|
|
78
100
|
return null;
|
|
101
|
+
if (reason === "safety")
|
|
102
|
+
return "review the persistent host safety stop before resuming";
|
|
79
103
|
return reason === "env"
|
|
80
104
|
? "unset GMAX_NO_AUTOSTART"
|
|
81
105
|
: `rm ${config_1.PATHS.autostartDisabledFile}`;
|
|
82
106
|
}
|
|
83
107
|
function autostartDisabledNotice() {
|
|
108
|
+
const safety = (0, safety_latch_1.safetyStopReason)();
|
|
109
|
+
if (safety !== null)
|
|
110
|
+
return `Gmax is paused by a persistent host safety stop: ${safety}. Mutations remain blocked until containment is reviewed.`;
|
|
84
111
|
// A secondary store (src/bin.ts) runs in-process by design, not because the
|
|
85
112
|
// kill switch is on, so say that instead of naming an undo step. Callers use
|
|
86
113
|
// a non-null notice as the gate, so this must stay non-null.
|