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.
@@ -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 _d, _e, _f, _g, _h;
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
- const owner = (_d = (_b = (_a = this.deps).createOwnerToken) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _d !== void 0 ? _d : (0, node_crypto_1.randomUUID)();
260
- const openLog = (_e = this.deps.openLog) !== null && _e !== void 0 ? _e : log_rotate_1.openRotatedLog;
261
- const closeFd = (_f = this.deps.closeFd) !== null && _f !== void 0 ? _f : fs.closeSync;
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 = ((_g = this.deps.spawn) !== null && _g !== void 0 ? _g : node_child_process_1.spawn)("uv", ["run", "python", "server.py"], {
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 (_j) { }
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 = (_h = (_c = startup.error) === null || _c === void 0 ? void 0 : _c.message) !== null && _h !== void 0 ? _h : (this.shouldStop() ? "MLX startup cancelled" : "MLX startup timed out");
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
- fs.mkdirSync(path.dirname(lmdbPath), { recursive: true });
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
- class DiskPressureError extends Error {
138
- constructor(message = "Disk critically low — writes suspended") {
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
- try {
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 (now - this.lastDiskCheckMs < VectorDB.DISK_CHECK_INTERVAL_MS) {
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.ensureTable();
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
- * Checks disk bloat ratio and retries optimize when bloat persists.
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
- let didWork = this.lastOptimizeDidWork;
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
- // Only retry if disk is still ok after optimize (don't spiral)
1447
- if (bloatRatio > 2.0 && this.checkDiskPressure() === "ok") {
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
- * Explicit `gmax watch --daemon` is never gated: typing it is the user asking
50
- * for a daemon in that moment.
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
- try {
60
- return fs.existsSync(config_1.PATHS.autostartDisabledFile) ? "file" : null;
61
- }
62
- catch (_a) {
63
- return null;
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.