@the-open-engine/zeroshot 6.24.0 → 6.25.1
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/cli/index.js +135 -72
- package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.js +37 -11
- package/lib/agent-cli-provider/adapters/omp.js.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-driver.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-driver.js +27 -2
- package/lib/agent-cli-provider/omp-rpc-driver.js.map +1 -1
- package/lib/agent-cli-provider/omp-rpc-session.js +3 -3
- package/lib/agent-cli-provider/omp-rpc-session.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +7 -1
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +2 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/package.json +1 -1
- package/src/agent/agent-lifecycle.js +66 -2
- package/src/agent/agent-task-executor.js +72 -3
- package/src/agent/provider-session.js +125 -2
- package/src/agent-cli-provider/adapters/omp.ts +41 -11
- package/src/agent-cli-provider/omp-rpc-driver.ts +31 -3
- package/src/agent-cli-provider/omp-rpc-session.ts +3 -3
- package/src/agent-cli-provider/provider-registry.ts +7 -1
- package/src/agent-cli-provider/types.ts +4 -0
- package/src/omp-blob-root.js +110 -0
- package/src/omp-config-overlay.js +9 -1
- package/src/omp-execution-fingerprint.js +62 -0
- package/src/omp-session-limits.js +41 -0
- package/src/omp-session-partition.js +424 -0
- package/src/omp-session-verifier.js +740 -0
- package/task-lib/commands/clean.js +92 -35
- package/task-lib/commands/kill.js +21 -0
- package/task-lib/commands/resume.js +62 -0
- package/task-lib/commands/run.js +80 -0
- package/task-lib/omp-session-cleanup.js +197 -0
- package/task-lib/omp-session-ownership-schema.js +268 -0
- package/task-lib/omp-session-ownership.js +367 -0
- package/task-lib/omp-storage-root.js +35 -0
- package/task-lib/rpc-watcher.js +368 -2
- package/task-lib/runner.js +243 -5
- package/task-lib/store.js +106 -76
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { createHash, randomUUID } = require('crypto');
|
|
4
|
+
const { isInsideOmpBlobsDir } = require('./omp-blob-root');
|
|
5
|
+
|
|
6
|
+
// Every OMP session partition lives under <storageRoot>/omp-sessions/<uuid>/. storageRoot is the
|
|
7
|
+
// owning cluster's storageDir for cluster-agent tasks or the standalone TASKS_DIR otherwise (see
|
|
8
|
+
// task-lib/omp-storage-root.js) — never derived from prompt text or cwd. The shared OMP CAS blob
|
|
9
|
+
// store is *not* under here at all (it is machine-wide, at pi-utils::getBlobsDir(); see
|
|
10
|
+
// src/omp-blob-root.js), which is what makes per-task partition deletion safe.
|
|
11
|
+
const OMP_SESSIONS_SUBDIR = 'omp-sessions';
|
|
12
|
+
const PARTITION_ID_PATTERN =
|
|
13
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
14
|
+
const DELETING_PREFIX = '.zeroshot-deleting-';
|
|
15
|
+
|
|
16
|
+
const O_NOFOLLOW = fs.constants.O_NOFOLLOW ?? 0;
|
|
17
|
+
const O_DIRECTORY = fs.constants.O_DIRECTORY ?? 0;
|
|
18
|
+
// See src/omp-session-verifier.js: O_NONBLOCK stops a FIFO planted at one of these paths from
|
|
19
|
+
// blocking the open forever; the fstat that follows still rejects the wrong type.
|
|
20
|
+
const O_NONBLOCK = fs.constants.O_NONBLOCK ?? 0;
|
|
21
|
+
|
|
22
|
+
function ompSessionsRoot(storageRoot) {
|
|
23
|
+
return path.join(path.resolve(storageRoot), OMP_SESSIONS_SUBDIR);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function partitionPathFor(storageRoot, partitionId) {
|
|
27
|
+
if (typeof partitionId !== 'string' || !PARTITION_ID_PATTERN.test(partitionId)) {
|
|
28
|
+
throw new Error(`Invalid OMP session partition id: ${partitionId}`);
|
|
29
|
+
}
|
|
30
|
+
return path.join(ompSessionsRoot(storageRoot), partitionId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function generateOmpPartitionId() {
|
|
34
|
+
return randomUUID();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Owner-only (0700) directory creation. Idempotent: safe to call once the row referencing this
|
|
38
|
+
* partitionId is already durable (row-before-directory — see task-lib/runner.js). */
|
|
39
|
+
function createOmpSessionPartitionDirectory(partitionPath) {
|
|
40
|
+
fs.mkdirSync(partitionPath, { recursive: true, mode: 0o700 });
|
|
41
|
+
if (process.platform !== 'win32') {
|
|
42
|
+
fs.chmodSync(partitionPath, 0o700);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Allocate a fresh, random, secret-free UUID partition directory under storageRoot in one step.
|
|
48
|
+
* Callers that must durably record the partitionId before the directory exists on disk (fresh
|
|
49
|
+
* task spawn — see task-lib/runner.js) should use generateOmpPartitionId/partitionPathFor and
|
|
50
|
+
* createOmpSessionPartitionDirectory separately instead, in that order.
|
|
51
|
+
*/
|
|
52
|
+
function allocateOmpSessionPartition(storageRoot) {
|
|
53
|
+
const partitionId = generateOmpPartitionId();
|
|
54
|
+
const partitionPath = partitionPathFor(storageRoot, partitionId);
|
|
55
|
+
createOmpSessionPartitionDirectory(partitionPath);
|
|
56
|
+
return { partitionId, path: partitionPath };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function identityOf(stat) {
|
|
60
|
+
return { device: String(stat.dev), inode: String(stat.ino) };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function sameIdentity(a, b) {
|
|
64
|
+
return Boolean(a) && Boolean(b) && a.device === b.device && a.inode === b.inode;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Descriptor-pinned directory identity: never follows a final symlink, and the returned identity
|
|
68
|
+
* describes the descriptor itself rather than a second pathname lookup. */
|
|
69
|
+
function pinDirectoryIdentity(dirPath) {
|
|
70
|
+
const fd = fs.openSync(dirPath, fs.constants.O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_DIRECTORY);
|
|
71
|
+
try {
|
|
72
|
+
const stat = fs.fstatSync(fd);
|
|
73
|
+
if (!stat.isDirectory()) {
|
|
74
|
+
throw Object.assign(new Error(`${dirPath} is not a directory`), { code: 'ENOTDIR' });
|
|
75
|
+
}
|
|
76
|
+
return { identity: identityOf(stat), uid: stat.uid };
|
|
77
|
+
} finally {
|
|
78
|
+
fs.closeSync(fd);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function currentUid() {
|
|
83
|
+
return typeof process.getuid === 'function' ? String(process.getuid()) : '0';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function stagingPathForOwnership(root, ownership) {
|
|
87
|
+
const owner = ownership.owner ?? {};
|
|
88
|
+
const persistedOwnerIdentity = JSON.stringify([
|
|
89
|
+
ownership.partitionId,
|
|
90
|
+
ownership.ownerUid ?? null,
|
|
91
|
+
ownership.storageRootIdentity?.device ?? null,
|
|
92
|
+
ownership.storageRootIdentity?.inode ?? null,
|
|
93
|
+
ownership.partitionIdentity?.device ?? null,
|
|
94
|
+
ownership.partitionIdentity?.inode ?? null,
|
|
95
|
+
owner.kind ?? null,
|
|
96
|
+
owner.clusterId ?? null,
|
|
97
|
+
owner.agentId ?? null,
|
|
98
|
+
owner.taskId ?? null,
|
|
99
|
+
]);
|
|
100
|
+
const digest = createHash('sha256').update(persistedOwnerIdentity).digest('hex');
|
|
101
|
+
return path.join(root, `${DELETING_PREFIX}${ownership.partitionId}-${digest}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Phase 1 of deletion: validate the owner record against what is actually on disk and move the
|
|
106
|
+
* partition out of its canonical name.
|
|
107
|
+
*
|
|
108
|
+
* The check/use race (CodeQL js/file-system-race) is closed by *moving before deleting*: the
|
|
109
|
+
* partition is renamed, within its own parent, to a deterministic staging name bound to its
|
|
110
|
+
* partition id and exact persisted owner identity, and only then re-pinned. A retry can therefore
|
|
111
|
+
* recover the staged directory after a crash or failed recursive removal. `rename(2)` is atomic,
|
|
112
|
+
* and the post-rename identity comparison proves it is still the same directory that passed
|
|
113
|
+
* validation. A canonical/staged conflict or identity mismatch leaves both names untouched.
|
|
114
|
+
*
|
|
115
|
+
* Splitting the rename from the recursive removal is what lets a caller hold a *task-store* write
|
|
116
|
+
* fence across "no other row claims this partition" -> "the partition no longer answers to its
|
|
117
|
+
* canonical name" without also holding it across an arbitrarily long `rm -r`. See
|
|
118
|
+
* task-lib/omp-session-cleanup.js.
|
|
119
|
+
*
|
|
120
|
+
* Never throws.
|
|
121
|
+
* `{staged:true, stagingPath}` the directory is parked and the caller must remove it
|
|
122
|
+
* `{staged:false, deleted:true, ...}` there was nothing to delete
|
|
123
|
+
* `{staged:false, deleted:false, ...}` refused; preserve the owner record and warn
|
|
124
|
+
*
|
|
125
|
+
* @param {object} ownership canonical, already-validated task.ompSessionOwnership record
|
|
126
|
+
*/
|
|
127
|
+
function stageOmpSessionPartitionForDeletion(ownership) {
|
|
128
|
+
if (!ownership || typeof ownership !== 'object') {
|
|
129
|
+
return { staged: false, deleted: false, reason: 'no ownership record' };
|
|
130
|
+
}
|
|
131
|
+
const { partitionId, storageRoot, partitionPath, ownerUid, storageRootIdentity } = ownership;
|
|
132
|
+
|
|
133
|
+
if (ownerUid !== currentUid()) {
|
|
134
|
+
return {
|
|
135
|
+
staged: false,
|
|
136
|
+
deleted: false,
|
|
137
|
+
reason: `recorded owner uid ${ownerUid} is not the current uid ${currentUid()}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let expectedPartitionPath;
|
|
142
|
+
try {
|
|
143
|
+
expectedPartitionPath = partitionPathFor(storageRoot, partitionId);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return { staged: false, deleted: false, reason: error.message };
|
|
146
|
+
}
|
|
147
|
+
if (expectedPartitionPath !== partitionPath) {
|
|
148
|
+
return {
|
|
149
|
+
staged: false,
|
|
150
|
+
deleted: false,
|
|
151
|
+
reason: `${partitionPath} is not the canonical partition path for ${partitionId}`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const root = ompSessionsRoot(storageRoot);
|
|
155
|
+
if (path.dirname(expectedPartitionPath) !== root) {
|
|
156
|
+
return {
|
|
157
|
+
staged: false,
|
|
158
|
+
deleted: false,
|
|
159
|
+
reason: `${expectedPartitionPath} does not resolve directly under ${root}`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Defence in depth: OMP's shared, cross-session CAS root must never be reachable from a
|
|
163
|
+
// partition path, whatever a tampered or migrated storageRoot claims.
|
|
164
|
+
if (isInsideOmpBlobsDir(expectedPartitionPath) || isInsideOmpBlobsDir(root)) {
|
|
165
|
+
return {
|
|
166
|
+
staged: false,
|
|
167
|
+
deleted: false,
|
|
168
|
+
reason: `${expectedPartitionPath} resolves inside the shared OMP blob store; refusing to delete`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// omp-sessions/ is the directory the staging rename happens inside, so it has to be a real,
|
|
173
|
+
// owner-held directory reached without following a symlink before anything is moved into it.
|
|
174
|
+
let rootPin;
|
|
175
|
+
try {
|
|
176
|
+
rootPin = pinDirectoryIdentity(root);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (error.code === 'ENOENT') return { staged: false, deleted: true, reason: 'already absent' };
|
|
179
|
+
return { staged: false, deleted: false, reason: `${root}: ${error.message}` };
|
|
180
|
+
}
|
|
181
|
+
if (String(rootPin.uid) !== currentUid()) {
|
|
182
|
+
return { staged: false, deleted: false, reason: `${root} is not owned by the current user` };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let storagePin;
|
|
186
|
+
try {
|
|
187
|
+
storagePin = pinDirectoryIdentity(path.resolve(storageRoot));
|
|
188
|
+
} catch (error) {
|
|
189
|
+
return { staged: false, deleted: false, reason: `${storageRoot}: ${error.message}` };
|
|
190
|
+
}
|
|
191
|
+
if (!sameIdentity(storagePin.identity, storageRootIdentity)) {
|
|
192
|
+
return {
|
|
193
|
+
staged: false,
|
|
194
|
+
deleted: false,
|
|
195
|
+
reason: `${storageRoot} identity ${storagePin.identity.device}:${storagePin.identity.inode} does not match the recorded ${storageRootIdentity?.device}:${storageRootIdentity?.inode}`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (String(storagePin.uid) !== currentUid()) {
|
|
199
|
+
return {
|
|
200
|
+
staged: false,
|
|
201
|
+
deleted: false,
|
|
202
|
+
reason: `${storageRoot} is not owned by the current user`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let stagingPath;
|
|
207
|
+
try {
|
|
208
|
+
stagingPath = stagingPathForOwnership(root, ownership);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
return {
|
|
211
|
+
staged: false,
|
|
212
|
+
deleted: false,
|
|
213
|
+
reason: `could not derive a staging name from the persisted owner identity: ${error.message}`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let before;
|
|
218
|
+
try {
|
|
219
|
+
before = pinDirectoryIdentity(expectedPartitionPath);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error.code !== 'ENOENT') {
|
|
222
|
+
if (error.code === 'ELOOP' || error.code === 'EMLINK') {
|
|
223
|
+
return {
|
|
224
|
+
staged: false,
|
|
225
|
+
deleted: false,
|
|
226
|
+
reason: `${expectedPartitionPath} is a symlink; refusing to delete`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (error.code === 'ENOTDIR') {
|
|
230
|
+
return {
|
|
231
|
+
staged: false,
|
|
232
|
+
deleted: false,
|
|
233
|
+
reason: `${expectedPartitionPath} is not a real directory; refusing to delete`,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return { staged: false, deleted: false, reason: error.message };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let recovered;
|
|
240
|
+
try {
|
|
241
|
+
recovered = pinDirectoryIdentity(stagingPath);
|
|
242
|
+
} catch (stagedError) {
|
|
243
|
+
if (stagedError.code === 'ENOENT') {
|
|
244
|
+
return { staged: false, deleted: true, reason: 'already absent' };
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
staged: false,
|
|
248
|
+
deleted: false,
|
|
249
|
+
reason: `canonical partition is absent, but staged ${stagingPath} is unsafe: ${stagedError.message}`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
if (String(recovered.uid) !== currentUid()) {
|
|
253
|
+
return {
|
|
254
|
+
staged: false,
|
|
255
|
+
deleted: false,
|
|
256
|
+
reason: `staged ${stagingPath} is not owned by the current user`,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (
|
|
260
|
+
ownership.partitionIdentity &&
|
|
261
|
+
!sameIdentity(recovered.identity, ownership.partitionIdentity)
|
|
262
|
+
) {
|
|
263
|
+
return {
|
|
264
|
+
staged: false,
|
|
265
|
+
deleted: false,
|
|
266
|
+
reason: `staged ${stagingPath} identity ${recovered.identity.device}:${recovered.identity.inode} does not match the recorded ${ownership.partitionIdentity.device}:${ownership.partitionIdentity.inode}`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return { staged: true, stagingPath };
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
pinDirectoryIdentity(stagingPath);
|
|
273
|
+
return {
|
|
274
|
+
staged: false,
|
|
275
|
+
deleted: false,
|
|
276
|
+
reason: `both canonical ${expectedPartitionPath} and staged ${stagingPath} exist; refusing to choose one`,
|
|
277
|
+
};
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (error.code !== 'ENOENT') {
|
|
280
|
+
return {
|
|
281
|
+
staged: false,
|
|
282
|
+
deleted: false,
|
|
283
|
+
reason: `staging conflict at ${stagingPath}: ${error.message}`,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (String(before.uid) !== currentUid()) {
|
|
288
|
+
return {
|
|
289
|
+
staged: false,
|
|
290
|
+
deleted: false,
|
|
291
|
+
reason: `${expectedPartitionPath} is not owned by the current user`,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if (ownership.partitionIdentity && !sameIdentity(before.identity, ownership.partitionIdentity)) {
|
|
295
|
+
return {
|
|
296
|
+
staged: false,
|
|
297
|
+
deleted: false,
|
|
298
|
+
reason: `${expectedPartitionPath} identity ${before.identity.device}:${before.identity.inode} does not match the recorded ${ownership.partitionIdentity.device}:${ownership.partitionIdentity.inode}`,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
fs.renameSync(expectedPartitionPath, stagingPath);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
staged: false,
|
|
307
|
+
deleted: false,
|
|
308
|
+
reason: `could not stage ${expectedPartitionPath}: ${error.message}`,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let after;
|
|
313
|
+
try {
|
|
314
|
+
after = pinDirectoryIdentity(stagingPath);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
return {
|
|
317
|
+
staged: false,
|
|
318
|
+
deleted: false,
|
|
319
|
+
reason: `staged ${stagingPath} could not be pinned (${error.message}); left in place for retry or inspection`,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (!sameIdentity(after.identity, before.identity)) {
|
|
323
|
+
return {
|
|
324
|
+
staged: false,
|
|
325
|
+
deleted: false,
|
|
326
|
+
reason: `${expectedPartitionPath} was substituted before deletion; the staged directory ${stagingPath} was left in place for inspection`,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { staged: true, stagingPath };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Phase 2: remove a directory that {@link stageOmpSessionPartitionForDeletion} parked under its
|
|
335
|
+
* deterministic, owner-bound staging name. Safe to run outside the task-store lock after
|
|
336
|
+
* revalidating that exact staged name and its persisted partition identity.
|
|
337
|
+
*/
|
|
338
|
+
function removeStagedOmpSessionPartition(stagingPath, ownership) {
|
|
339
|
+
if (!ownership || typeof ownership !== 'object') {
|
|
340
|
+
return { deleted: false, reason: 'no ownership record for staged partition removal' };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let expectedPartitionPath;
|
|
344
|
+
try {
|
|
345
|
+
expectedPartitionPath = partitionPathFor(ownership.storageRoot, ownership.partitionId);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
return { deleted: false, reason: error.message };
|
|
348
|
+
}
|
|
349
|
+
if (expectedPartitionPath !== ownership.partitionPath) {
|
|
350
|
+
return {
|
|
351
|
+
deleted: false,
|
|
352
|
+
reason: `${ownership.partitionPath} is not the canonical partition path for ${ownership.partitionId}`,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const root = ompSessionsRoot(ownership.storageRoot);
|
|
356
|
+
let expectedStagingPath;
|
|
357
|
+
try {
|
|
358
|
+
expectedStagingPath = stagingPathForOwnership(root, ownership);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
return {
|
|
361
|
+
deleted: false,
|
|
362
|
+
reason: `could not derive a staging name from the persisted owner identity: ${error.message}`,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
if (stagingPath !== expectedStagingPath) {
|
|
366
|
+
return {
|
|
367
|
+
deleted: false,
|
|
368
|
+
reason: `${stagingPath} is not the staged path bound to partition ${ownership.partitionId} and its persisted owner identity`,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let staged;
|
|
373
|
+
try {
|
|
374
|
+
staged = pinDirectoryIdentity(stagingPath);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
if (error.code === 'ENOENT') return { deleted: true, reason: 'already absent' };
|
|
377
|
+
return { deleted: false, reason: `${stagingPath}: ${error.message}` };
|
|
378
|
+
}
|
|
379
|
+
if (String(staged.uid) !== currentUid()) {
|
|
380
|
+
return { deleted: false, reason: `${stagingPath} is not owned by the current user` };
|
|
381
|
+
}
|
|
382
|
+
if (ownership.partitionIdentity && !sameIdentity(staged.identity, ownership.partitionIdentity)) {
|
|
383
|
+
return {
|
|
384
|
+
deleted: false,
|
|
385
|
+
reason: `${stagingPath} identity ${staged.identity.device}:${staged.identity.inode} does not match the recorded ${ownership.partitionIdentity.device}:${ownership.partitionIdentity.inode}`,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
fs.rmSync(stagingPath, { recursive: true, force: true });
|
|
391
|
+
} catch (error) {
|
|
392
|
+
return { deleted: false, reason: `${stagingPath}: ${error.message}` };
|
|
393
|
+
}
|
|
394
|
+
return { deleted: true };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Validate, stage, and remove one owner-validated partition directory. Never the shared OMP CAS
|
|
399
|
+
* blob root, never anything outside `<storageRoot>/omp-sessions/`, and never a directory that is
|
|
400
|
+
* not the one the persisted ownership record describes.
|
|
401
|
+
*
|
|
402
|
+
* Never throws. `{deleted:false, reason}` means the caller must preserve the owner record and warn.
|
|
403
|
+
*
|
|
404
|
+
* @param {object} ownership canonical, already-validated task.ompSessionOwnership record
|
|
405
|
+
*/
|
|
406
|
+
function deleteOmpSessionPartition(ownership) {
|
|
407
|
+
const staged = stageOmpSessionPartitionForDeletion(ownership);
|
|
408
|
+
if (!staged.staged) return { deleted: staged.deleted === true, reason: staged.reason };
|
|
409
|
+
return removeStagedOmpSessionPartition(staged.stagingPath, ownership);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
module.exports = {
|
|
413
|
+
DELETING_PREFIX,
|
|
414
|
+
OMP_SESSIONS_SUBDIR,
|
|
415
|
+
PARTITION_ID_PATTERN,
|
|
416
|
+
ompSessionsRoot,
|
|
417
|
+
partitionPathFor,
|
|
418
|
+
generateOmpPartitionId,
|
|
419
|
+
createOmpSessionPartitionDirectory,
|
|
420
|
+
allocateOmpSessionPartition,
|
|
421
|
+
stageOmpSessionPartitionForDeletion,
|
|
422
|
+
removeStagedOmpSessionPartition,
|
|
423
|
+
deleteOmpSessionPartition,
|
|
424
|
+
};
|