grepmax 0.26.53 → 0.26.55
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 +90 -34
- package/dist/lib/output/mcp-results.js +3 -0
- 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 +166 -33
- package/dist/lib/utils/autostart.js +34 -7
- package/dist/lib/utils/daemon-launcher.js +6 -0
- package/dist/lib/utils/doctor-optimize.js +6 -0
- package/dist/lib/utils/kernel-zone.js +10 -5
- package/dist/lib/utils/safety-latch.js +121 -0
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/watch-lease.js +13 -5
|
@@ -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
|
|
@@ -370,11 +366,20 @@ class VectorDB {
|
|
|
370
366
|
if (this.closed) {
|
|
371
367
|
throw new Error("VectorDB connection is closed");
|
|
372
368
|
}
|
|
369
|
+
const creatingStore = !fs.existsSync(this.lancedbDir);
|
|
370
|
+
if (creatingStore) {
|
|
371
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
372
|
+
(0, maintenance_policy_1.assertFreshDiskMutationAllowed)(this.lancedbDir);
|
|
373
|
+
}
|
|
373
374
|
yield this.getLease();
|
|
374
375
|
if (this.closed) {
|
|
375
376
|
throw new Error("VectorDB connection is closed");
|
|
376
377
|
}
|
|
377
378
|
if (!this.db) {
|
|
379
|
+
if (creatingStore || !fs.existsSync(this.lancedbDir)) {
|
|
380
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
381
|
+
(0, maintenance_policy_1.assertFreshDiskMutationAllowed)(this.lancedbDir);
|
|
382
|
+
}
|
|
378
383
|
fs.mkdirSync(this.lancedbDir, { recursive: true });
|
|
379
384
|
this.session = createLanceSession();
|
|
380
385
|
// 0.38 accepts the legacy third argument in its types but drops it in
|
|
@@ -469,23 +474,18 @@ class VectorDB {
|
|
|
469
474
|
});
|
|
470
475
|
}
|
|
471
476
|
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
|
-
}
|
|
477
|
+
return (0, maintenance_policy_1.availableStoreDiskBytes)(this.lancedbDir);
|
|
479
478
|
}
|
|
480
|
-
checkDiskPressure() {
|
|
479
|
+
checkDiskPressure(fresh = false) {
|
|
481
480
|
const now = Date.now();
|
|
482
|
-
if (
|
|
481
|
+
if (!fresh &&
|
|
482
|
+
now - this.lastDiskCheckMs < VectorDB.DISK_CHECK_INTERVAL_MS) {
|
|
483
483
|
return this.diskPressure;
|
|
484
484
|
}
|
|
485
485
|
this.lastDiskCheckMs = now;
|
|
486
486
|
const avail = this.getAvailableBytes();
|
|
487
487
|
let level;
|
|
488
|
-
if (avail < config_1.DISK_CRITICAL_BYTES) {
|
|
488
|
+
if (!Number.isFinite(avail) || avail < 0 || avail < config_1.DISK_CRITICAL_BYTES) {
|
|
489
489
|
level = "critical";
|
|
490
490
|
}
|
|
491
491
|
else if (avail < config_1.DISK_LOW_BYTES) {
|
|
@@ -497,7 +497,7 @@ class VectorDB {
|
|
|
497
497
|
if (level !== this.lastLoggedPressure) {
|
|
498
498
|
const freeStr = `${(avail / 1024 / 1024 / 1024).toFixed(1)}GB`;
|
|
499
499
|
if (level === "critical") {
|
|
500
|
-
(0, logger_1.log)("vectordb", `CRITICAL: disk space critically low (${freeStr} free) — writes suspended`);
|
|
500
|
+
(0, logger_1.log)("vectordb", `CRITICAL: ${Number.isFinite(avail) ? `disk space critically low (${freeStr} free)` : "disk space unknown"} — writes suspended`);
|
|
501
501
|
}
|
|
502
502
|
else if (level === "low") {
|
|
503
503
|
(0, logger_1.log)("vectordb", `WARNING: disk space low (${freeStr} free) — compaction limited`);
|
|
@@ -511,8 +511,8 @@ class VectorDB {
|
|
|
511
511
|
return level;
|
|
512
512
|
}
|
|
513
513
|
ensureDiskOk() {
|
|
514
|
-
if (this.checkDiskPressure() === "critical") {
|
|
515
|
-
throw new DiskPressureError();
|
|
514
|
+
if (this.checkDiskPressure(true) === "critical") {
|
|
515
|
+
throw new maintenance_policy_1.DiskPressureError();
|
|
516
516
|
}
|
|
517
517
|
}
|
|
518
518
|
/**
|
|
@@ -522,6 +522,8 @@ class VectorDB {
|
|
|
522
522
|
*/
|
|
523
523
|
withWriteGate(fn) {
|
|
524
524
|
return __awaiter(this, void 0, void 0, function* () {
|
|
525
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
526
|
+
this.ensureDiskOk();
|
|
525
527
|
while (this.exclusiveMutationPromise || this.compactingPromise) {
|
|
526
528
|
if (this.closed)
|
|
527
529
|
throw new Error("VectorDB connection is closed");
|
|
@@ -529,6 +531,8 @@ class VectorDB {
|
|
|
529
531
|
}
|
|
530
532
|
if (this.closed)
|
|
531
533
|
throw new Error("VectorDB connection is closed");
|
|
534
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
535
|
+
this.ensureDiskOk();
|
|
532
536
|
this.activeWrites++;
|
|
533
537
|
try {
|
|
534
538
|
return yield fn();
|
|
@@ -567,6 +571,8 @@ class VectorDB {
|
|
|
567
571
|
}
|
|
568
572
|
withExclusiveTableMutation(mutation) {
|
|
569
573
|
return __awaiter(this, void 0, void 0, function* () {
|
|
574
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
575
|
+
this.ensureDiskOk();
|
|
570
576
|
if (this.closed)
|
|
571
577
|
throw new Error("VectorDB connection is closed");
|
|
572
578
|
if (this.exclusiveMutationPromise) {
|
|
@@ -592,6 +598,8 @@ class VectorDB {
|
|
|
592
598
|
});
|
|
593
599
|
}
|
|
594
600
|
yield Promise.all([this.drainWrites(), this.drainCompactions()]);
|
|
601
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
602
|
+
this.ensureDiskOk();
|
|
595
603
|
const db = yield this.getDb();
|
|
596
604
|
const result = yield mutation(db);
|
|
597
605
|
this.markWriteCommitted();
|
|
@@ -761,6 +769,14 @@ class VectorDB {
|
|
|
761
769
|
}
|
|
762
770
|
ensureTable() {
|
|
763
771
|
return __awaiter(this, void 0, void 0, function* () {
|
|
772
|
+
if ((0, maintenance_policy_1.storeMutationDeniedReason)() !== null ||
|
|
773
|
+
this.checkDiskPressure(true) === "critical") {
|
|
774
|
+
const existing = yield this.openExistingTableUnsafe();
|
|
775
|
+
if (!existing)
|
|
776
|
+
throw new Error("gmax store is quarantined and has no existing table");
|
|
777
|
+
yield this.validateSchema(existing);
|
|
778
|
+
return existing;
|
|
779
|
+
}
|
|
764
780
|
return this.withWriteGate(() => this.ensureTableUnsafe());
|
|
765
781
|
});
|
|
766
782
|
}
|
|
@@ -774,6 +790,7 @@ class VectorDB {
|
|
|
774
790
|
catch (err) {
|
|
775
791
|
if (!isMissingTableError(err))
|
|
776
792
|
throw err;
|
|
793
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
777
794
|
(0, logger_1.log)("db", `Creating table (${this.vectorDim}d)`);
|
|
778
795
|
const schema = this.buildSchema();
|
|
779
796
|
table = yield db.createTable(TABLE_NAME, [this.seedRow()], {
|
|
@@ -784,6 +801,7 @@ class VectorDB {
|
|
|
784
801
|
return table;
|
|
785
802
|
}
|
|
786
803
|
yield this.validateSchema(table);
|
|
804
|
+
(0, maintenance_policy_1.assertStoreMutationAllowed)();
|
|
787
805
|
yield this.evolveSchema(table);
|
|
788
806
|
return table;
|
|
789
807
|
});
|
|
@@ -903,6 +921,12 @@ class VectorDB {
|
|
|
903
921
|
}
|
|
904
922
|
createFTSIndex() {
|
|
905
923
|
return __awaiter(this, arguments, void 0, function* (rebuild = false, retries = 5) {
|
|
924
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
925
|
+
if (rebuild)
|
|
926
|
+
throw new Error(maintenance_policy_1.FULL_TABLE_MAINTENANCE_DISABLED_REASON);
|
|
927
|
+
// Existing indexes remain searchable; absence does not authorize a build.
|
|
928
|
+
return this.adoptFTSIndex();
|
|
929
|
+
}
|
|
906
930
|
return this.withWriteGate(() => this.createFTSIndexUnsafe(rebuild, retries));
|
|
907
931
|
});
|
|
908
932
|
}
|
|
@@ -939,7 +963,9 @@ class VectorDB {
|
|
|
939
963
|
return __awaiter(this, void 0, void 0, function* () {
|
|
940
964
|
if (this.ftsIndexEnsured)
|
|
941
965
|
return;
|
|
942
|
-
const table = yield this.
|
|
966
|
+
const table = yield this.openExistingTableUnsafe();
|
|
967
|
+
if (!table)
|
|
968
|
+
throw new Error("[vector-db] No existing table or FTS index; index maintenance is disabled");
|
|
943
969
|
const indices = yield table.listIndices();
|
|
944
970
|
const existing = indices.find((index) => index.name === "content_idx" || index.columns.includes("content"));
|
|
945
971
|
if (!existing) {
|
|
@@ -950,6 +976,10 @@ class VectorDB {
|
|
|
950
976
|
}
|
|
951
977
|
createVectorIndex() {
|
|
952
978
|
return __awaiter(this, arguments, void 0, function* (rebuild = false, retries = 5, checkOnly = false) {
|
|
979
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
980
|
+
(0, logger_1.debug)("vectordb", "ANN/index maintenance disabled by host-safety containment");
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
953
983
|
if (this.checkDiskPressure() !== "ok") {
|
|
954
984
|
(0, logger_1.debug)("vectordb", "ANN index skipped under disk pressure");
|
|
955
985
|
return false;
|
|
@@ -1108,9 +1138,84 @@ class VectorDB {
|
|
|
1108
1138
|
(0, logger_1.log)("vectordb", `Compaction result: ${JSON.stringify(result)}`);
|
|
1109
1139
|
return Object.assign({}, result);
|
|
1110
1140
|
}
|
|
1141
|
+
/** Prune the reserve-ID snapshot without another data rewrite. Caller holds
|
|
1142
|
+
* compactingPromise and has drained writes; never use this for retention > 0.
|
|
1143
|
+
* Lance 12 treats one deletion-free fragment as a compaction no-op. */
|
|
1144
|
+
cleanupCompactionReservation(table) {
|
|
1145
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1146
|
+
var _a;
|
|
1147
|
+
let passes = 0;
|
|
1148
|
+
const deferred = (reason) => ({
|
|
1149
|
+
passes: 0,
|
|
1150
|
+
bytesRemoved: 0,
|
|
1151
|
+
reason,
|
|
1152
|
+
});
|
|
1153
|
+
try {
|
|
1154
|
+
const stats = yield table.stats();
|
|
1155
|
+
if (((_a = stats.fragmentStats) === null || _a === void 0 ? void 0 : _a.numFragments) !== 1) {
|
|
1156
|
+
return deferred("cleanup deferred: table is not a single fragment");
|
|
1157
|
+
}
|
|
1158
|
+
const version = yield table.version();
|
|
1159
|
+
const fresh = yield this.openExistingTableUnsafe();
|
|
1160
|
+
if (!fresh || (yield fresh.version()) !== version) {
|
|
1161
|
+
return deferred("cleanup deferred: table changed after compaction");
|
|
1162
|
+
}
|
|
1163
|
+
const latest = (yield fresh.listVersions()).find((v) => v.version === version);
|
|
1164
|
+
if ((latest === null || latest === void 0 ? void 0 : latest.metadata.total_fragments) !== "1" ||
|
|
1165
|
+
latest.metadata.total_deletion_files !== "0" ||
|
|
1166
|
+
latest.metadata.total_deletion_file_rows !== "0") {
|
|
1167
|
+
return deferred("cleanup deferred: deletion-free fragment not verified");
|
|
1168
|
+
}
|
|
1169
|
+
// Manifest timestamps have sub-ms precision; wait for a real later tick,
|
|
1170
|
+
// never invent a future cutoff. No gmax writes can enter while gated.
|
|
1171
|
+
yield new Promise((resolve) => setTimeout(resolve, 1));
|
|
1172
|
+
const cutoff = new Date();
|
|
1173
|
+
if (!Number.isFinite(latest.timestamp.getTime()) ||
|
|
1174
|
+
cutoff.getTime() <= latest.timestamp.getTime()) {
|
|
1175
|
+
return deferred("cleanup deferred: clock has not passed the commit");
|
|
1176
|
+
}
|
|
1177
|
+
const freeBytes = this.getAvailableBytes();
|
|
1178
|
+
const requiredBytes = stats.totalBytes * 2 + config_1.DISK_CRITICAL_BYTES;
|
|
1179
|
+
if (!Number.isFinite(stats.totalBytes) ||
|
|
1180
|
+
stats.totalBytes < 0 ||
|
|
1181
|
+
freeBytes < requiredBytes) {
|
|
1182
|
+
return deferred("cleanup deferred: insufficient rewrite headroom");
|
|
1183
|
+
}
|
|
1184
|
+
(0, logger_1.log)("vectordb", `Compaction cleanup attempt: ${JSON.stringify({ version, cutoff: cutoff.getTime(), freeBytes, requiredBytes })}`);
|
|
1185
|
+
passes = 1;
|
|
1186
|
+
const result = yield fresh.optimize({
|
|
1187
|
+
cleanupOlderThan: cutoff,
|
|
1188
|
+
deleteUnverified: true,
|
|
1189
|
+
});
|
|
1190
|
+
if (result.compaction.fragmentsRemoved !== 0 ||
|
|
1191
|
+
result.compaction.fragmentsAdded !== 0) {
|
|
1192
|
+
return {
|
|
1193
|
+
passes,
|
|
1194
|
+
bytesRemoved: result.prune.bytesRemoved,
|
|
1195
|
+
failed: true,
|
|
1196
|
+
reason: "cleanup unexpectedly rewrote data; no retry",
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
return { passes, bytesRemoved: result.prune.bytesRemoved };
|
|
1200
|
+
}
|
|
1201
|
+
catch (error) {
|
|
1202
|
+
return {
|
|
1203
|
+
passes,
|
|
1204
|
+
bytesRemoved: 0,
|
|
1205
|
+
failed: true,
|
|
1206
|
+
reason: `cleanup failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1111
1211
|
optimize() {
|
|
1112
1212
|
return __awaiter(this, arguments, void 0, function* (retries = COMPACTION_MAX_ATTEMPTS, retentionMs = 0, bypassExclusiveMutation = false) {
|
|
1113
1213
|
var _a;
|
|
1214
|
+
var _b;
|
|
1215
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
1216
|
+
this.lastOptimizeDidWork = false;
|
|
1217
|
+
return this.recordSkippedCompaction((0, maintenance_policy_1.recordMaintenanceContainment)(this.lancedbDir));
|
|
1218
|
+
}
|
|
1114
1219
|
if (!bypassExclusiveMutation) {
|
|
1115
1220
|
while (this.exclusiveMutationPromise)
|
|
1116
1221
|
yield this.exclusiveMutationPromise;
|
|
@@ -1120,7 +1225,7 @@ class VectorDB {
|
|
|
1120
1225
|
if (this.compactingPromise) {
|
|
1121
1226
|
(0, logger_1.debug)("vectordb", "Optimize already in progress, skipping");
|
|
1122
1227
|
yield this.compactingPromise;
|
|
1123
|
-
return ((
|
|
1228
|
+
return ((_b = this.compactionStatus()) !== null && _b !== void 0 ? _b : (0, compaction_result_1.skippedCompaction)("compaction unavailable"));
|
|
1124
1229
|
}
|
|
1125
1230
|
this.lastOptimizeDidWork = false;
|
|
1126
1231
|
let resolveCompacting;
|
|
@@ -1133,7 +1238,10 @@ class VectorDB {
|
|
|
1133
1238
|
let logicalBytes;
|
|
1134
1239
|
let diskBytesBefore;
|
|
1135
1240
|
let freeBytesBefore;
|
|
1241
|
+
let cleanupPasses = 0;
|
|
1242
|
+
let cleanupReason;
|
|
1136
1243
|
const finish = (status, reason, bytesReclaimed) => {
|
|
1244
|
+
const diskBytesAfter = this.getDirectorySize(this.lancedbDir);
|
|
1137
1245
|
const result = {
|
|
1138
1246
|
status,
|
|
1139
1247
|
at: Date.now(),
|
|
@@ -1143,9 +1251,14 @@ class VectorDB {
|
|
|
1143
1251
|
logicalBytes,
|
|
1144
1252
|
diskBytesBefore,
|
|
1145
1253
|
freeBytesBefore,
|
|
1146
|
-
diskBytesAfter
|
|
1254
|
+
diskBytesAfter,
|
|
1147
1255
|
freeBytesAfter: this.getAvailableBytes(),
|
|
1148
1256
|
bytesReclaimed,
|
|
1257
|
+
netBytesReclaimed: diskBytesBefore === undefined
|
|
1258
|
+
? undefined
|
|
1259
|
+
: diskBytesBefore - diskBytesAfter,
|
|
1260
|
+
cleanupPasses,
|
|
1261
|
+
cleanupReason,
|
|
1149
1262
|
};
|
|
1150
1263
|
this.lastCompactionResult = result;
|
|
1151
1264
|
(0, logger_1.log)("vectordb", `Compaction result: ${JSON.stringify(result)}`);
|
|
@@ -1210,7 +1323,18 @@ class VectorDB {
|
|
|
1210
1323
|
else {
|
|
1211
1324
|
(0, logger_1.debug)("vectordb", "Optimize: nothing to compact or prune");
|
|
1212
1325
|
}
|
|
1213
|
-
|
|
1326
|
+
let bytesReclaimed = prune.bytesRemoved;
|
|
1327
|
+
if (retentionMs === 0 && compaction.fragmentsRemoved > 0) {
|
|
1328
|
+
const cleanup = yield this.cleanupCompactionReservation(table);
|
|
1329
|
+
cleanupPasses = cleanup.passes;
|
|
1330
|
+
cleanupReason = (_a = cleanup.reason) === null || _a === void 0 ? void 0 : _a.replace(/[\r\n\t]/g, " ").slice(0, 512);
|
|
1331
|
+
bytesReclaimed += cleanup.bytesRemoved;
|
|
1332
|
+
this.lastOptimizeDidWork || (this.lastOptimizeDidWork = cleanup.bytesRemoved > 0);
|
|
1333
|
+
(0, logger_1.log)("vectordb", `Compaction cleanup result: ${JSON.stringify(Object.assign(Object.assign({}, cleanup), { reason: cleanupReason }))}`);
|
|
1334
|
+
if (cleanup.failed)
|
|
1335
|
+
return finish("failed", cleanupReason, bytesReclaimed);
|
|
1336
|
+
}
|
|
1337
|
+
return finish("completed", undefined, bytesReclaimed);
|
|
1214
1338
|
}
|
|
1215
1339
|
catch (e) {
|
|
1216
1340
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -1282,10 +1406,14 @@ class VectorDB {
|
|
|
1282
1406
|
/**
|
|
1283
1407
|
* Run FTS rebuild + optimize as a single serialized operation.
|
|
1284
1408
|
* Safe to call from multiple project processors — only one runs at a time.
|
|
1285
|
-
*
|
|
1409
|
+
* Reports bloat without another whole-table rewrite.
|
|
1286
1410
|
*/
|
|
1287
1411
|
runMaintenance() {
|
|
1288
1412
|
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
1413
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
1414
|
+
this.lastOptimizeDidWork = false;
|
|
1415
|
+
return this.recordSkippedCompaction((0, maintenance_policy_1.recordMaintenanceContainment)(this.lancedbDir));
|
|
1416
|
+
}
|
|
1289
1417
|
if (this.maintenanceRunning) {
|
|
1290
1418
|
(0, logger_1.debug)("vectordb", "Maintenance already running, skipping");
|
|
1291
1419
|
return (0, compaction_result_1.skippedCompaction)("maintenance already running");
|
|
@@ -1336,8 +1464,8 @@ class VectorDB {
|
|
|
1336
1464
|
// Track across both passes: `lastOptimizeDidWork` is reset per optimize()
|
|
1337
1465
|
// call, so a productive first pass followed by a barren bloat retry would
|
|
1338
1466
|
// otherwise read as unproductive and trigger a spurious backoff.
|
|
1339
|
-
|
|
1340
|
-
if (this.lastOptimizeDidWork) {
|
|
1467
|
+
const didWork = this.lastOptimizeDidWork;
|
|
1468
|
+
if (result.status === "completed" && this.lastOptimizeDidWork) {
|
|
1341
1469
|
const table = yield this.openExistingTableUnsafe();
|
|
1342
1470
|
if (table) {
|
|
1343
1471
|
// Collect abandoned scratch files before measuring. They are pure
|
|
@@ -1353,17 +1481,17 @@ class VectorDB {
|
|
|
1353
1481
|
const diskSize = this.getDirectorySize(this.lancedbDir);
|
|
1354
1482
|
const logicalSize = stats.totalBytes;
|
|
1355
1483
|
const bloatRatio = logicalSize > 0 ? diskSize / logicalSize : 0;
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
(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`);
|
|
1359
|
-
yield new Promise((r) => setTimeout(r, 2000));
|
|
1360
|
-
result = yield this.optimize(5, 0, true);
|
|
1361
|
-
didWork = didWork || this.lastOptimizeDidWork;
|
|
1484
|
+
if (bloatRatio > 2.0) {
|
|
1485
|
+
(0, logger_1.log)("vectordb", `Bloat remains after optimize: ${bloatRatio.toFixed(1)}x; additional rewrite disabled`);
|
|
1362
1486
|
}
|
|
1363
1487
|
}
|
|
1364
1488
|
}
|
|
1365
1489
|
this.noteCompaction(didWork);
|
|
1366
1490
|
}
|
|
1491
|
+
if (result && result.status !== "completed")
|
|
1492
|
+
return result;
|
|
1493
|
+
if (result === null || result === void 0 ? void 0 : result.cleanupReason)
|
|
1494
|
+
return result;
|
|
1367
1495
|
const maintainedTable = yield this.openExistingTableUnsafe();
|
|
1368
1496
|
this.maintainedTableVersion =
|
|
1369
1497
|
yield this.readTableVersion(maintainedTable);
|
|
@@ -1419,6 +1547,11 @@ class VectorDB {
|
|
|
1419
1547
|
}
|
|
1420
1548
|
compactIfNeeded() {
|
|
1421
1549
|
return __awaiter(this, arguments, void 0, function* (threshold = config_1.FRAGMENT_COMPACT_THRESHOLD) {
|
|
1550
|
+
if ((0, maintenance_policy_1.fullTableMaintenanceDisabled)()) {
|
|
1551
|
+
this.lastOptimizeDidWork = false;
|
|
1552
|
+
this.recordSkippedCompaction((0, maintenance_policy_1.recordMaintenanceContainment)(this.lancedbDir));
|
|
1553
|
+
return false;
|
|
1554
|
+
}
|
|
1422
1555
|
if (this.maintenanceRunning)
|
|
1423
1556
|
return false;
|
|
1424
1557
|
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.
|
|
@@ -48,6 +48,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
48
48
|
const fs = __importStar(require("node:fs"));
|
|
49
49
|
const path = __importStar(require("node:path"));
|
|
50
50
|
const config_1 = require("../../config");
|
|
51
|
+
const autostart_1 = require("./autostart");
|
|
51
52
|
const log_rotate_1 = require("./log-rotate");
|
|
52
53
|
/**
|
|
53
54
|
* Spawn the daemon in background mode.
|
|
@@ -56,6 +57,8 @@ const log_rotate_1 = require("./log-rotate");
|
|
|
56
57
|
function spawnDaemon() {
|
|
57
58
|
return __awaiter(this, void 0, void 0, function* () {
|
|
58
59
|
var _a;
|
|
60
|
+
if ((0, autostart_1.daemonStartDeniedReason)() !== null)
|
|
61
|
+
return null;
|
|
59
62
|
let out = null;
|
|
60
63
|
try {
|
|
61
64
|
const logFile = path.join(config_1.PATHS.logsDir, "daemon.log");
|
|
@@ -90,6 +93,9 @@ function spawnDaemon() {
|
|
|
90
93
|
*/
|
|
91
94
|
function spawnDaemonProcess() {
|
|
92
95
|
return __awaiter(this, void 0, void 0, function* () {
|
|
96
|
+
const denied = (0, autostart_1.daemonStartDeniedReason)();
|
|
97
|
+
if (denied !== null)
|
|
98
|
+
throw new Error(`gmax: ${denied}`);
|
|
93
99
|
const logFile = path.join(config_1.PATHS.logsDir, "daemon.log");
|
|
94
100
|
const out = (0, log_rotate_1.openRotatedLog)(logFile);
|
|
95
101
|
try {
|
|
@@ -72,6 +72,12 @@ function formatDoctorOptimize(report, agent) {
|
|
|
72
72
|
...(typeof (r === null || r === void 0 ? void 0 : r.bytesReclaimed) === "number"
|
|
73
73
|
? [`bytes_reclaimed=${r.bytesReclaimed}`]
|
|
74
74
|
: []),
|
|
75
|
+
...(typeof (r === null || r === void 0 ? void 0 : r.netBytesReclaimed) === "number"
|
|
76
|
+
? [`net_bytes_reclaimed=${r.netBytesReclaimed}`]
|
|
77
|
+
: []),
|
|
78
|
+
...(typeof (r === null || r === void 0 ? void 0 : r.cleanupPasses) === "number"
|
|
79
|
+
? [`cleanup_passes=${r.cleanupPasses}`]
|
|
80
|
+
: []),
|
|
75
81
|
...(reason ? [`reason=${reason}`] : []),
|
|
76
82
|
].join("\t");
|
|
77
83
|
}
|
|
@@ -21,11 +21,15 @@ const node_child_process_1 = require("node:child_process");
|
|
|
21
21
|
* docs/2026-08-04-macos-kernel-zone-panic-incident.md.
|
|
22
22
|
*
|
|
23
23
|
* gmax cannot fix the kernel defect, but it can decline to be the thing that
|
|
24
|
-
* detonates it. Sampling the zone
|
|
25
|
-
*
|
|
24
|
+
* detonates it. Sampling the zone has taken 1.584 s on the incident host, so the
|
|
25
|
+
* probe uses a bounded five-second budget. The daemon stands down while there
|
|
26
|
+
* are still hours of headroom.
|
|
26
27
|
*/
|
|
27
28
|
/** The zone that leaks. Named explicitly so `zprint` returns a single line. */
|
|
28
29
|
const ZONE_NAME = "data.kalloc.1024";
|
|
30
|
+
// Two 500 ms probes timed out on the incident host; a subsequent probe completed
|
|
31
|
+
// in 1,584 ms. Keep the previous five-second ceiling to allow scheduling margin.
|
|
32
|
+
const PROBE_TIMEOUT_MS = 5000;
|
|
29
33
|
/**
|
|
30
34
|
* Warn threshold.
|
|
31
35
|
*
|
|
@@ -84,8 +88,8 @@ function parseZprintOutput(output, zoneName = ZONE_NAME) {
|
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
86
90
|
* Sample the zone. Returns null off macOS, or whenever the sample cannot be
|
|
87
|
-
* trusted
|
|
88
|
-
*
|
|
91
|
+
* trusted. An unparseable or timed-out report reads as "unknown", never as
|
|
92
|
+
* "healthy". macOS startup and heavy-operation admission refuse unknown samples.
|
|
89
93
|
*/
|
|
90
94
|
function readKernelZoneUsage(zoneName = ZONE_NAME) {
|
|
91
95
|
if (process.platform !== "darwin")
|
|
@@ -93,7 +97,8 @@ function readKernelZoneUsage(zoneName = ZONE_NAME) {
|
|
|
93
97
|
try {
|
|
94
98
|
const output = (0, node_child_process_1.execFileSync)("zprint", [zoneName], {
|
|
95
99
|
encoding: "utf-8",
|
|
96
|
-
timeout:
|
|
100
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
101
|
+
maxBuffer: 64 * 1024,
|
|
97
102
|
stdio: ["ignore", "pipe", "ignore"],
|
|
98
103
|
});
|
|
99
104
|
const parsed = parseZprintOutput(output, zoneName);
|