grepmax 0.26.63 → 0.26.65
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 +12 -2
- package/dist/bin.js +1 -1
- package/dist/commands/recover.js +117 -0
- package/dist/index.js +2 -0
- package/dist/lib/daemon/watcher-manager.js +69 -27
- package/dist/lib/index/watcher-errors.js +16 -0
- package/dist/lib/index/watcher.js +43 -3
- package/dist/lib/store/lance-cleanup.js +425 -0
- package/dist/lib/store/prune-state.js +113 -0
- package/dist/lib/store/recovery-admission.js +92 -0
- package/dist/lib/store/recovery.js +163 -0
- package/dist/lib/store/store-lease.js +119 -4
- package/dist/lib/utils/host-resource.js +3 -3
- package/dist/lib/utils/resource-budget.js +6 -6
- package/lance-maintenance/prune.py +200 -0
- package/lance-maintenance/pyproject.toml +9 -0
- package/lance-maintenance/uv.lock +531 -0
- package/package.json +5 -2
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,425 @@
|
|
|
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
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.runCleanupProcess = runCleanupProcess;
|
|
46
|
+
exports.prepareCleanupRuntime = prepareCleanupRuntime;
|
|
47
|
+
exports.pruneVersions = pruneVersions;
|
|
48
|
+
const node_child_process_1 = require("node:child_process");
|
|
49
|
+
const node_crypto_1 = require("node:crypto");
|
|
50
|
+
const fs = __importStar(require("node:fs"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
const config_1 = require("../../config");
|
|
53
|
+
const prune_state_1 = require("./prune-state");
|
|
54
|
+
const recovery_admission_1 = require("./recovery-admission");
|
|
55
|
+
const store_lease_1 = require("./store-lease");
|
|
56
|
+
const project = path.resolve(__dirname, "../../../lance-maintenance");
|
|
57
|
+
const MAX_OUTPUT_BYTES = 128 * 1024;
|
|
58
|
+
const MAX_HELPER_MS = 120000;
|
|
59
|
+
/** No shell, inherited service credentials, Python path hooks or source builds.
|
|
60
|
+
* Thread settings apply before Arrow/Lance imports in the child. */
|
|
61
|
+
function helperEnvironment() {
|
|
62
|
+
const env = {};
|
|
63
|
+
for (const name of [
|
|
64
|
+
"PATH",
|
|
65
|
+
"HOME",
|
|
66
|
+
"TMPDIR",
|
|
67
|
+
"TEMP",
|
|
68
|
+
"SystemRoot",
|
|
69
|
+
"LOCALAPPDATA",
|
|
70
|
+
"SSL_CERT_FILE",
|
|
71
|
+
"SSL_CERT_DIR",
|
|
72
|
+
]) {
|
|
73
|
+
if (process.env[name] !== undefined)
|
|
74
|
+
env[name] = process.env[name];
|
|
75
|
+
}
|
|
76
|
+
return Object.assign(Object.assign({}, env), { RAYON_NUM_THREADS: "1", TOKIO_WORKER_THREADS: "1", OMP_NUM_THREADS: "1", OPENBLAS_NUM_THREADS: "1", MKL_NUM_THREADS: "1", NUMEXPR_NUM_THREADS: "1", UV_CONCURRENT_DOWNLOADS: "1", UV_CONCURRENT_INSTALLS: "1", UV_CONCURRENT_BUILDS: "1", UV_PYTHON_DOWNLOADS: "never" });
|
|
77
|
+
}
|
|
78
|
+
/** Settle only after close, including abort/timeout. Releasing an exclusive lease
|
|
79
|
+
* on AbortError before the child exits would let writers race native deletion. */
|
|
80
|
+
function runCleanupProcess(executable, args, options = {}) {
|
|
81
|
+
var _a;
|
|
82
|
+
if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted)
|
|
83
|
+
return Promise.reject(new Error("Cleanup cancelled before launch"));
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
var _a, _b, _c;
|
|
86
|
+
var _d;
|
|
87
|
+
const child = (0, node_child_process_1.spawn)(executable, args, {
|
|
88
|
+
env: Object.assign(Object.assign({}, helperEnvironment()), options.env),
|
|
89
|
+
stdio: [options.admission ? "pipe" : "ignore", "pipe", "pipe"],
|
|
90
|
+
windowsHide: true,
|
|
91
|
+
});
|
|
92
|
+
let output = "";
|
|
93
|
+
let diagnostics = "";
|
|
94
|
+
let bytes = 0;
|
|
95
|
+
let failure;
|
|
96
|
+
let approved = false;
|
|
97
|
+
let launched = false;
|
|
98
|
+
let readyBuffer = "";
|
|
99
|
+
let monitor;
|
|
100
|
+
let forceKill;
|
|
101
|
+
const stop = (reason) => {
|
|
102
|
+
failure !== null && failure !== void 0 ? failure : (failure = new Error(`${reason}; cleanup completion is uncertain`));
|
|
103
|
+
child.kill("SIGTERM");
|
|
104
|
+
forceKill !== null && forceKill !== void 0 ? forceKill : (forceKill = setTimeout(() => child.kill("SIGKILL"), 1000));
|
|
105
|
+
};
|
|
106
|
+
const abort = () => stop("Cleanup cancelled");
|
|
107
|
+
const timeout = setTimeout(() => stop("Cleanup timed out"), Math.min((_d = options.timeoutMs) !== null && _d !== void 0 ? _d : MAX_HELPER_MS, MAX_HELPER_MS));
|
|
108
|
+
(_a = options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener("abort", abort, { once: true });
|
|
109
|
+
// stderr is deliberately not returned: child diagnostics may contain paths,
|
|
110
|
+
// tokens or registry URLs. Retain a concrete bounded exit/signal/error code.
|
|
111
|
+
for (const stream of [child.stdout, child.stderr]) {
|
|
112
|
+
stream.on("data", (chunk) => {
|
|
113
|
+
bytes += chunk.length;
|
|
114
|
+
if (bytes > MAX_OUTPUT_BYTES)
|
|
115
|
+
stop("Cleanup output exceeded limit");
|
|
116
|
+
else if (stream === child.stdout) {
|
|
117
|
+
if (options.admission && !approved) {
|
|
118
|
+
readyBuffer += chunk.toString("utf8");
|
|
119
|
+
let end = readyBuffer.indexOf("\n");
|
|
120
|
+
while (end >= 0 && !approved && !failure) {
|
|
121
|
+
try {
|
|
122
|
+
const phase = JSON.parse(readyBuffer.slice(0, end)).admission;
|
|
123
|
+
readyBuffer = readyBuffer.slice(end + 1);
|
|
124
|
+
if (phase === "launch" && !launched) {
|
|
125
|
+
if (!child.stdin)
|
|
126
|
+
throw Error();
|
|
127
|
+
child.stdin.write(`${options.admission.nonce}\n`);
|
|
128
|
+
launched = true;
|
|
129
|
+
}
|
|
130
|
+
else if (phase === "ready" && launched) {
|
|
131
|
+
options.admission.approve();
|
|
132
|
+
child.stdin.end(`${options.admission.nonce}\n`);
|
|
133
|
+
approved = true;
|
|
134
|
+
output += readyBuffer;
|
|
135
|
+
readyBuffer = "";
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
throw Error();
|
|
139
|
+
}
|
|
140
|
+
catch (_a) {
|
|
141
|
+
stop("Prune deletion admission refused");
|
|
142
|
+
}
|
|
143
|
+
end = readyBuffer.indexOf("\n");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else
|
|
147
|
+
output += chunk.toString("utf8");
|
|
148
|
+
}
|
|
149
|
+
else
|
|
150
|
+
diagnostics += chunk.toString("utf8");
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
child.on("error", (error) => {
|
|
154
|
+
var _a;
|
|
155
|
+
failure !== null && failure !== void 0 ? failure : (failure = new Error(`Cleanup process could not start (${(_a = error.code) !== null && _a !== void 0 ? _a : "unknown"})`));
|
|
156
|
+
});
|
|
157
|
+
child.once("close", (code, signal) => {
|
|
158
|
+
var _a, _b;
|
|
159
|
+
var _c;
|
|
160
|
+
clearTimeout(timeout);
|
|
161
|
+
if (forceKill)
|
|
162
|
+
clearTimeout(forceKill);
|
|
163
|
+
if (monitor)
|
|
164
|
+
clearInterval(monitor);
|
|
165
|
+
try {
|
|
166
|
+
(_a = options.admission) === null || _a === void 0 ? void 0 : _a.close();
|
|
167
|
+
}
|
|
168
|
+
catch (_d) {
|
|
169
|
+
failure !== null && failure !== void 0 ? failure : (failure = new Error("Prune ownership/resource close failed; completion is uncertain"));
|
|
170
|
+
}
|
|
171
|
+
if (options.admission && !approved)
|
|
172
|
+
failure !== null && failure !== void 0 ? failure : (failure = new Error("Prune deletion was not admitted; completion is uncertain"));
|
|
173
|
+
(_b = options.signal) === null || _b === void 0 ? void 0 : _b.removeEventListener("abort", abort);
|
|
174
|
+
if (failure)
|
|
175
|
+
reject(failure);
|
|
176
|
+
else if (code !== 0) {
|
|
177
|
+
// Whitelist categories, never repeat registry credentials, file paths,
|
|
178
|
+
// full traceback text or arbitrary diagnostics from a subprocess.
|
|
179
|
+
const detail = /lockfile.*(?:needs|updated|out.of.date)|locked.*(?:changed|update)/i.test(diagnostics)
|
|
180
|
+
? "locked project/runtime mismatch"
|
|
181
|
+
: /no space left|ENOSPC/i.test(diagnostics)
|
|
182
|
+
? "disk full"
|
|
183
|
+
: /no (?:python|interpreter)|python.*(?:not found|unavailable)/i.test(diagnostics)
|
|
184
|
+
? "compatible Python unavailable"
|
|
185
|
+
: /failed to (?:download|fetch)|connection refused|network|cache missing/i.test(diagnostics)
|
|
186
|
+
? "download, network or cache unavailable"
|
|
187
|
+
: /exclusive (?:lease|ownership)|shared store owners/i.test(diagnostics)
|
|
188
|
+
? "exclusive ownership verification failed"
|
|
189
|
+
: /protected state|table changed|state.*changed/i.test(diagnostics)
|
|
190
|
+
? "current/protected state verification failed"
|
|
191
|
+
: "unclassified child failure";
|
|
192
|
+
reject(new Error(`Cleanup process exited (${(_c = signal !== null && signal !== void 0 ? signal : code) !== null && _c !== void 0 ? _c : "unknown"}): ${detail}; cleanup completion is uncertain`));
|
|
193
|
+
}
|
|
194
|
+
else
|
|
195
|
+
resolve(output);
|
|
196
|
+
});
|
|
197
|
+
(_b = child.stdin) === null || _b === void 0 ? void 0 : _b.on("error", () => stop("Prune admission pipe failed"));
|
|
198
|
+
if (options.admission) {
|
|
199
|
+
try {
|
|
200
|
+
if (!child.pid)
|
|
201
|
+
throw Error();
|
|
202
|
+
options.admission.start(child.pid);
|
|
203
|
+
monitor = setInterval(() => {
|
|
204
|
+
try {
|
|
205
|
+
options.admission.check();
|
|
206
|
+
}
|
|
207
|
+
catch (_a) {
|
|
208
|
+
stop("Prune host/resource admission changed");
|
|
209
|
+
}
|
|
210
|
+
}, 5000);
|
|
211
|
+
}
|
|
212
|
+
catch (_e) {
|
|
213
|
+
stop("Prune helper launch admission refused");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if ((_c = options.signal) === null || _c === void 0 ? void 0 : _c.aborted)
|
|
217
|
+
abort();
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
let preparation;
|
|
221
|
+
/** Explicit recovery only. Containment has no automatic caller of this setup. */
|
|
222
|
+
function prepareCleanupRuntime(signal) {
|
|
223
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
224
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
225
|
+
throw new Error("Cleanup setup cancelled");
|
|
226
|
+
if (process.platform !== "darwin" && process.platform !== "linux")
|
|
227
|
+
throw new Error("Prune-only runtime currently supports macOS and Linux");
|
|
228
|
+
if (!preparation) {
|
|
229
|
+
preparation = (() => __awaiter(this, void 0, void 0, function* () {
|
|
230
|
+
const stdout = yield runCleanupProcess("uv", ["--version"], {
|
|
231
|
+
signal,
|
|
232
|
+
timeoutMs: 10000,
|
|
233
|
+
});
|
|
234
|
+
const match = /^uv (\d+)\.(\d+)\.(\d+)/.exec(stdout);
|
|
235
|
+
if (!match ||
|
|
236
|
+
(Number(match[1]) === 0 &&
|
|
237
|
+
(Number(match[2]) < 12 ||
|
|
238
|
+
(Number(match[2]) === 12 && Number(match[3]) < 18)))) {
|
|
239
|
+
throw new Error("Prune-only runtime requires uv >=0.12.18");
|
|
240
|
+
}
|
|
241
|
+
const hash = (0, node_crypto_1.createHash)("sha256")
|
|
242
|
+
.update(fs.readFileSync(path.join(project, "pyproject.toml")))
|
|
243
|
+
.update(fs.readFileSync(path.join(project, "uv.lock")))
|
|
244
|
+
.digest("hex")
|
|
245
|
+
.slice(0, 16);
|
|
246
|
+
const root = path.join(config_1.PATHS.sharedRoot, "runtimes");
|
|
247
|
+
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
248
|
+
if (fs.lstatSync(root).isSymbolicLink())
|
|
249
|
+
throw new Error("Unverified cleanup runtime directory");
|
|
250
|
+
fs.chmodSync(root, 0o700);
|
|
251
|
+
const environment = path.join(root, `lance-cleanup-${hash}`);
|
|
252
|
+
const python = path.join(environment, process.platform === "win32" ? "Scripts/python.exe" : "bin/python");
|
|
253
|
+
if (fs.existsSync(environment)) {
|
|
254
|
+
if (!fs.lstatSync(environment).isDirectory() ||
|
|
255
|
+
fs.lstatSync(environment).isSymbolicLink())
|
|
256
|
+
throw new Error("Unverified cleanup environment");
|
|
257
|
+
yield runCleanupProcess("uv", [
|
|
258
|
+
"sync",
|
|
259
|
+
"--check",
|
|
260
|
+
"--locked",
|
|
261
|
+
"--offline",
|
|
262
|
+
"--project",
|
|
263
|
+
project,
|
|
264
|
+
"--no-dev",
|
|
265
|
+
"--no-python-downloads",
|
|
266
|
+
"--no-build",
|
|
267
|
+
], {
|
|
268
|
+
signal,
|
|
269
|
+
timeoutMs: 10000,
|
|
270
|
+
env: { UV_PROJECT_ENVIRONMENT: environment },
|
|
271
|
+
});
|
|
272
|
+
return { python, script: path.join(project, "prune.py") };
|
|
273
|
+
}
|
|
274
|
+
const available = fs.statfsSync(root);
|
|
275
|
+
const freeBytes = available.bavail * available.bsize;
|
|
276
|
+
if (!Number.isFinite(freeBytes) || freeBytes < 512 * Math.pow(1024, 2)) {
|
|
277
|
+
throw new Error("Cleanup runtime setup refused: less than 512MiB free");
|
|
278
|
+
}
|
|
279
|
+
yield runCleanupProcess("uv", [
|
|
280
|
+
"sync",
|
|
281
|
+
"--locked",
|
|
282
|
+
"--project",
|
|
283
|
+
project,
|
|
284
|
+
"--no-dev",
|
|
285
|
+
"--no-python-downloads",
|
|
286
|
+
"--no-build",
|
|
287
|
+
], {
|
|
288
|
+
signal,
|
|
289
|
+
env: { UV_PROJECT_ENVIRONMENT: environment },
|
|
290
|
+
});
|
|
291
|
+
fs.chmodSync(environment, 0o700);
|
|
292
|
+
return {
|
|
293
|
+
python,
|
|
294
|
+
script: path.join(project, "prune.py"),
|
|
295
|
+
};
|
|
296
|
+
}))().catch((error) => {
|
|
297
|
+
preparation = undefined;
|
|
298
|
+
throw error;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return preparation;
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/** Explicit recovery only. Caller must have drained local work/readers before
|
|
305
|
+
* acquiring the exclusive lease. The lease cannot be released during this call. */
|
|
306
|
+
function pruneVersions(runtime, store, version, cutoff, options) {
|
|
307
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
308
|
+
var _a;
|
|
309
|
+
if (!((options === null || options === void 0 ? void 0 : options.lease) instanceof store_lease_1.StoreLease))
|
|
310
|
+
throw new Error("Pruning requires a verified StoreLease token");
|
|
311
|
+
if (!Number.isSafeInteger(version) ||
|
|
312
|
+
version < 1 ||
|
|
313
|
+
!Number.isSafeInteger(cutoff.getTime()) ||
|
|
314
|
+
cutoff.getTime() > Date.now()) {
|
|
315
|
+
throw new Error("Invalid prune version or future cutoff");
|
|
316
|
+
}
|
|
317
|
+
const table = fs.realpathSync(store);
|
|
318
|
+
if (!table.endsWith(".lance") || !fs.statSync(table).isDirectory())
|
|
319
|
+
throw new Error("Pruning requires a local Lance table");
|
|
320
|
+
const storeDir = path.dirname(table);
|
|
321
|
+
if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted)
|
|
322
|
+
throw new Error("Cleanup cancelled before launch");
|
|
323
|
+
let attempt;
|
|
324
|
+
let admission;
|
|
325
|
+
try {
|
|
326
|
+
const result = yield options.lease.withExclusiveUse(storeDir, () => __awaiter(this, void 0, void 0, function* () {
|
|
327
|
+
var _a;
|
|
328
|
+
const previous = (0, prune_state_1.readPruneState)(storeDir);
|
|
329
|
+
if ((previous === null || previous === void 0 ? void 0 : previous.outcome) === "uncertain" &&
|
|
330
|
+
options.acknowledgeUncertain !== previous.attemptId)
|
|
331
|
+
throw new Error(`Previous prune completion is uncertain; explicit acknowledgement required for attempt ${previous.attemptId}`);
|
|
332
|
+
admission = (_a = options.admission) !== null && _a !== void 0 ? _a : (0, recovery_admission_1.admitPrune)(storeDir);
|
|
333
|
+
attempt = {
|
|
334
|
+
schemaVersion: 1,
|
|
335
|
+
storeIdentity: fs.realpathSync(storeDir),
|
|
336
|
+
attemptId: (0, node_crypto_1.randomUUID)(),
|
|
337
|
+
version,
|
|
338
|
+
cutoffMs: cutoff.getTime(),
|
|
339
|
+
startedAt: Date.now(),
|
|
340
|
+
outcome: "running",
|
|
341
|
+
previousUncertainAttemptId: (previous === null || previous === void 0 ? void 0 : previous.outcome) === "uncertain"
|
|
342
|
+
? previous.attemptId
|
|
343
|
+
: previous === null || previous === void 0 ? void 0 : previous.previousUncertainAttemptId,
|
|
344
|
+
};
|
|
345
|
+
(0, prune_state_1.writePruneState)(attempt);
|
|
346
|
+
let unpin;
|
|
347
|
+
const stdout = yield runCleanupProcess(runtime.python, [
|
|
348
|
+
"-I",
|
|
349
|
+
runtime.script,
|
|
350
|
+
"--store",
|
|
351
|
+
table,
|
|
352
|
+
"--version",
|
|
353
|
+
String(version),
|
|
354
|
+
"--cutoff-ms",
|
|
355
|
+
String(cutoff.getTime()),
|
|
356
|
+
"--lease-owner",
|
|
357
|
+
(0, store_lease_1.storeLeasePaths)(storeDir).intentOwnerFile,
|
|
358
|
+
"--lease-nonce",
|
|
359
|
+
options.lease.owner.nonce,
|
|
360
|
+
"--require-admission",
|
|
361
|
+
], {
|
|
362
|
+
signal: options.signal,
|
|
363
|
+
admission: {
|
|
364
|
+
nonce: options.lease.owner.nonce,
|
|
365
|
+
start: (pid) => {
|
|
366
|
+
unpin = options.lease.pinExclusiveHelper(pid);
|
|
367
|
+
admission.start(pid);
|
|
368
|
+
},
|
|
369
|
+
approve: () => admission.approve(),
|
|
370
|
+
check: () => admission.check(),
|
|
371
|
+
close: () => {
|
|
372
|
+
try {
|
|
373
|
+
unpin === null || unpin === void 0 ? void 0 : unpin();
|
|
374
|
+
}
|
|
375
|
+
finally {
|
|
376
|
+
admission.close();
|
|
377
|
+
admission = undefined;
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
const result = JSON.parse(stdout);
|
|
383
|
+
if (result.engine !== "12.0.0" ||
|
|
384
|
+
result.version !== version ||
|
|
385
|
+
result.rewritten !== false ||
|
|
386
|
+
![
|
|
387
|
+
"fragments",
|
|
388
|
+
"bytesRemoved",
|
|
389
|
+
"versionsRemoved",
|
|
390
|
+
"fileBytesBefore",
|
|
391
|
+
"fileBytesAfter",
|
|
392
|
+
"allocatedBytesBefore",
|
|
393
|
+
"allocatedBytesAfter",
|
|
394
|
+
"freeBytesBefore",
|
|
395
|
+
"freeBytesAfter",
|
|
396
|
+
].every((key) => {
|
|
397
|
+
const value = result[key];
|
|
398
|
+
return (typeof value === "number" &&
|
|
399
|
+
Number.isSafeInteger(value) &&
|
|
400
|
+
value >= 0);
|
|
401
|
+
}))
|
|
402
|
+
throw new Error("Invalid prune-only cleanup result; cleanup completion is uncertain");
|
|
403
|
+
return result;
|
|
404
|
+
}));
|
|
405
|
+
// withExclusiveUse verifies ownership again before this synchronous write.
|
|
406
|
+
// The caller still holds the lease; it cannot release it during the helper.
|
|
407
|
+
(0, prune_state_1.writePruneState)(Object.assign(Object.assign({}, attempt), { outcome: "completed", finishedAt: Date.now(), result }));
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
if (attempt) {
|
|
412
|
+
try {
|
|
413
|
+
(0, prune_state_1.writePruneState)(Object.assign(Object.assign({}, attempt), { outcome: "uncertain", finishedAt: Date.now() }));
|
|
414
|
+
}
|
|
415
|
+
catch (_b) {
|
|
416
|
+
throw new Error("Prune completion is uncertain; durable outcome could not be recorded");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
admission === null || admission === void 0 ? void 0 : admission.close();
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
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.pruneStatePath = pruneStatePath;
|
|
37
|
+
exports.readPruneState = readPruneState;
|
|
38
|
+
exports.writePruneState = writePruneState;
|
|
39
|
+
const node_crypto_1 = require("node:crypto");
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
function pruneStatePath(storeDir) {
|
|
43
|
+
return path.join(storeDir, ".gmax-prune-state.json");
|
|
44
|
+
}
|
|
45
|
+
/** A running receipt survives parent death. Readers must treat it as uncertain,
|
|
46
|
+
* never as successful cleanup or permission to launch automatic maintenance. */
|
|
47
|
+
function readPruneState(storeDir) {
|
|
48
|
+
var _a, _b, _c;
|
|
49
|
+
const identity = fs.realpathSync(storeDir);
|
|
50
|
+
const target = pruneStatePath(identity);
|
|
51
|
+
let stat;
|
|
52
|
+
try {
|
|
53
|
+
stat = fs.lstatSync(target);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error.code === "ENOENT")
|
|
57
|
+
return null;
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
if (!stat.isFile() || stat.size > 16 * 1024)
|
|
61
|
+
throw new Error("Prune state is unverified or oversized");
|
|
62
|
+
const state = JSON.parse(fs.readFileSync(target, "utf8"));
|
|
63
|
+
if (state.schemaVersion !== 1 ||
|
|
64
|
+
state.storeIdentity !== identity ||
|
|
65
|
+
typeof state.attemptId !== "string" ||
|
|
66
|
+
!state.attemptId ||
|
|
67
|
+
!Number.isSafeInteger(state.version) ||
|
|
68
|
+
state.version < 1 ||
|
|
69
|
+
!Number.isSafeInteger(state.cutoffMs) ||
|
|
70
|
+
!Number.isSafeInteger(state.startedAt) ||
|
|
71
|
+
!["running", "uncertain", "completed"].includes(state.outcome) ||
|
|
72
|
+
(state.outcome === "completed" &&
|
|
73
|
+
(!Number.isSafeInteger(state.finishedAt) ||
|
|
74
|
+
((_a = state.result) === null || _a === void 0 ? void 0 : _a.version) !== state.version ||
|
|
75
|
+
((_b = state.result) === null || _b === void 0 ? void 0 : _b.engine) !== "12.0.0" ||
|
|
76
|
+
((_c = state.result) === null || _c === void 0 ? void 0 : _c.rewritten) !== false)))
|
|
77
|
+
throw new Error("Prune state is invalid; cleanup remains pending");
|
|
78
|
+
return state.outcome === "running"
|
|
79
|
+
? Object.assign(Object.assign({}, state), { outcome: "uncertain" }) : state;
|
|
80
|
+
}
|
|
81
|
+
/** Persist and sync before native deletion. Failure refuses the operation;
|
|
82
|
+
* failed final writes leave a running/uncertain receipt for the next reader. */
|
|
83
|
+
function writePruneState(state) {
|
|
84
|
+
const target = pruneStatePath(state.storeIdentity);
|
|
85
|
+
if (fs.existsSync(target)) {
|
|
86
|
+
const stat = fs.lstatSync(target);
|
|
87
|
+
if (!stat.isFile() || stat.size > 16 * 1024)
|
|
88
|
+
throw new Error("Prune state is unverified or oversized");
|
|
89
|
+
}
|
|
90
|
+
const temporary = path.join(state.storeIdentity, `.gmax-prune-${(0, node_crypto_1.randomUUID)()}.tmp`);
|
|
91
|
+
try {
|
|
92
|
+
const descriptor = fs.openSync(temporary, "wx", 0o600);
|
|
93
|
+
try {
|
|
94
|
+
fs.writeFileSync(descriptor, `${JSON.stringify(state)}\n`);
|
|
95
|
+
fs.fsyncSync(descriptor);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
fs.closeSync(descriptor);
|
|
99
|
+
}
|
|
100
|
+
fs.renameSync(temporary, target);
|
|
101
|
+
const directory = fs.openSync(state.storeIdentity, "r");
|
|
102
|
+
try {
|
|
103
|
+
fs.fsyncSync(directory);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
fs.closeSync(directory);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
if (fs.existsSync(temporary))
|
|
111
|
+
fs.unlinkSync(temporary);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
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.PRUNE_METADATA_HEADROOM_BYTES = void 0;
|
|
37
|
+
exports.createRecoveryBudget = createRecoveryBudget;
|
|
38
|
+
exports.assertRecoveryAdmission = assertRecoveryAdmission;
|
|
39
|
+
exports.admitPrune = admitPrune;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const resource_budget_1 = require("../utils/resource-budget");
|
|
43
|
+
const safety_latch_1 = require("../utils/safety-latch");
|
|
44
|
+
const maintenance_policy_1 = require("./maintenance-policy");
|
|
45
|
+
exports.PRUNE_METADATA_HEADROOM_BYTES = 1024 * 1024;
|
|
46
|
+
/** Explicit recovery has aggregate admission without changing normal service
|
|
47
|
+
* policy. Offline autostart containment is allowed; a host safety stop is not. */
|
|
48
|
+
function createRecoveryBudget() {
|
|
49
|
+
return new resource_budget_1.ResourceBudget({
|
|
50
|
+
policy: () => "strict",
|
|
51
|
+
quarantine: safety_latch_1.safetyStopReason,
|
|
52
|
+
requireClientRegistration: false,
|
|
53
|
+
samplerOverrides: { sampleTimeoutMs: 8000, kernelProbeTimeoutMs: 5000 },
|
|
54
|
+
sampleMaxAgeMs: 10000,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function assertRecoveryAdmission(storeDir, budget) {
|
|
58
|
+
const marker = path.join(path.dirname(fs.realpathSync(storeDir)), "autostart-disabled");
|
|
59
|
+
const stat = fs.lstatSync(marker, { throwIfNoEntry: false });
|
|
60
|
+
if (!(stat === null || stat === void 0 ? void 0 : stat.isFile()) || stat.isSymbolicLink())
|
|
61
|
+
throw new Error("Recovery requires a persistent autostart-disabled file in the store home; close store owners separately");
|
|
62
|
+
const disk = (0, maintenance_policy_1.availableStoreDiskBytes)(storeDir);
|
|
63
|
+
if (!Number.isFinite(disk) || disk < exports.PRUNE_METADATA_HEADROOM_BYTES)
|
|
64
|
+
throw new Error("Prune metadata disk headroom is low or unknown");
|
|
65
|
+
const snapshot = budget.check();
|
|
66
|
+
if ((snapshot === null || snapshot === void 0 ? void 0 : snapshot.platform) !== "darwin")
|
|
67
|
+
throw new Error("Production recovery admission currently requires macOS host measurements");
|
|
68
|
+
if (snapshot.physicalFreeMb === null ||
|
|
69
|
+
!Number.isFinite(snapshot.physicalFreeMb) ||
|
|
70
|
+
snapshot.physicalFreeMb < resource_budget_1.HELPER_RESOURCE_RESERVE_MB)
|
|
71
|
+
throw new Error("Insufficient measured physical headroom for prune helper");
|
|
72
|
+
return snapshot;
|
|
73
|
+
}
|
|
74
|
+
function admitPrune(storeDir, budget = createRecoveryBudget()) {
|
|
75
|
+
assertRecoveryAdmission(storeDir, budget);
|
|
76
|
+
const reserve = budget.reserve(resource_budget_1.HELPER_RESOURCE_RESERVE_MB, "prune-helper");
|
|
77
|
+
let snapshot;
|
|
78
|
+
try {
|
|
79
|
+
snapshot = assertRecoveryAdmission(storeDir, budget);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
reserve.release();
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
snapshot,
|
|
87
|
+
start: (pid) => reserve.attach(pid),
|
|
88
|
+
approve: () => assertRecoveryAdmission(storeDir, budget),
|
|
89
|
+
check: () => assertRecoveryAdmission(storeDir, budget),
|
|
90
|
+
close: () => reserve.release(),
|
|
91
|
+
};
|
|
92
|
+
}
|