@openclaw/fs-safe 0.18.1 → 0.19.0

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.
Files changed (94) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/advanced.d.ts +2 -1
  3. package/dist/advanced.js +1 -1
  4. package/dist/archive-durability.js +1 -1
  5. package/dist/archive-merge.js +1 -2
  6. package/dist/archive-staging.d.ts +1 -2
  7. package/dist/archive-staging.js +3 -3
  8. package/dist/archive-zip-preflight.d.ts +0 -2
  9. package/dist/archive-zip-preflight.js +0 -1
  10. package/dist/archive.d.ts +6 -3
  11. package/dist/archive.js +5 -3
  12. package/dist/directory-guard.d.ts +0 -1
  13. package/dist/directory-guard.js +0 -3
  14. package/dist/directory-mode-node.d.ts +20 -2
  15. package/dist/directory-mode-node.js +77 -1
  16. package/dist/file-lock-sync-acquisition.js +1 -1
  17. package/dist/file-lock-sync-root-held.d.ts +4 -11
  18. package/dist/file-lock-sync-root-io.d.ts +1 -4
  19. package/dist/file-lock-sync-root-options.d.ts +2 -10
  20. package/dist/file-lock-sync-root.d.ts +2 -4
  21. package/dist/file-store-boundary.d.ts +3 -7
  22. package/dist/file-store-boundary.js +7 -10
  23. package/dist/file-store-prune.js +3 -2
  24. package/dist/file-store.js +19 -21
  25. package/dist/guest-native-python.js +23 -31
  26. package/dist/guest.js +21 -12
  27. package/dist/json-durable-queue-ownership.d.ts +1 -5
  28. package/dist/json-durable-queue.d.ts +0 -1
  29. package/dist/json-durable-queue.js +2 -7
  30. package/dist/local-file-descriptor.d.ts +2 -5
  31. package/dist/local-roots.d.ts +2 -7
  32. package/dist/native-binding.d.ts +12 -13
  33. package/dist/permission-exec.d.ts +6 -0
  34. package/dist/permissions-public.d.ts +2 -1
  35. package/dist/permissions-windows.d.ts +3 -6
  36. package/dist/permissions.d.ts +3 -10
  37. package/dist/permissions.js +0 -1
  38. package/dist/pinned-mutation-shared-route.d.ts +2 -8
  39. package/dist/pinned-open.d.ts +1 -0
  40. package/dist/pinned-open.js +1 -1
  41. package/dist/replace-directory.js +5 -17
  42. package/dist/replace-file-copy-fallback.d.ts +1 -8
  43. package/dist/replace-file-descriptor.d.ts +3 -11
  44. package/dist/replace-file-descriptor.js +1 -1
  45. package/dist/retained-directory-replacement.d.ts +1 -0
  46. package/dist/retained-directory-replacement.js +1 -1
  47. package/dist/root-context.js +3 -2
  48. package/dist/root-file-final-admission.d.ts +1 -1
  49. package/dist/root-file-final-admission.js +1 -6
  50. package/dist/root-file.js +2 -5
  51. package/dist/root-move-noreplace.d.ts +2 -7
  52. package/dist/root-paths.d.ts +2 -6
  53. package/dist/root-remove-identity.d.ts +1 -3
  54. package/dist/root-walk.js +8 -3
  55. package/dist/root-write-admission.js +1 -4
  56. package/dist/root-write-complete-parent.d.ts +2 -0
  57. package/dist/root-write-complete-parent.js +1 -1
  58. package/dist/secret-file.d.ts +6 -2
  59. package/dist/secret-file.js +12 -16
  60. package/dist/secret-read-policy.js +1 -1
  61. package/dist/secure-file-windows.js +5 -14
  62. package/dist/secure-temp-dir.js +2 -7
  63. package/dist/sidecar-lock-acquire.js +1 -1
  64. package/dist/sidecar-lock-admission-parser.d.ts +1 -2
  65. package/dist/sidecar-lock-handle.d.ts +2 -8
  66. package/dist/sidecar-lock-policy.d.ts +2 -7
  67. package/dist/sidecar-lock-reclaim.js +5 -6
  68. package/dist/sidecar-lock-stale-admission.d.ts +1 -5
  69. package/dist/stat-observation.js +3 -10
  70. package/dist/strict-file-identity.d.ts +2 -0
  71. package/dist/strict-file-identity.js +6 -6
  72. package/dist/temp-target.js +3 -8
  73. package/dist/temp-workspace-admission.js +6 -12
  74. package/dist/temp-workspace-child-admission.js +2 -8
  75. package/dist/temp-workspace-descriptor.js +1 -2
  76. package/dist/test-hooks.d.ts +1 -1
  77. package/dist/windows-owner.d.ts +3 -6
  78. package/dist/windows-security-bridge.cs +0 -2
  79. package/dist/windows-security-command.js +1 -4
  80. package/dist/windows-security-facts.d.ts +1 -0
  81. package/dist/windows-security-facts.js +2 -2
  82. package/docs/archive.md +2 -0
  83. package/docs/copy.md +2 -0
  84. package/docs/file-store.md +5 -0
  85. package/docs/guest.md +7 -1
  86. package/docs/native-helper.md +6 -0
  87. package/docs/root.md +10 -1
  88. package/docs/secret-file.md +19 -0
  89. package/docs/sidecar-lock.md +2 -0
  90. package/docs/walk.md +12 -0
  91. package/docs/writing.md +11 -0
  92. package/package.json +8 -8
  93. package/dist/directory-mode-owner.d.ts +0 -20
  94. package/dist/directory-mode-owner.js +0 -78
@@ -3,7 +3,7 @@ import { normalizeMaxBytes } from "./byte-budget.js";
3
3
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
4
4
  import { FsSafeError } from "./errors.js";
5
5
  import { pruneExpiredStoreEntries } from "./file-store-prune.js";
6
- import { assertFileStoreMaxBytes, assertRelativePath, ensureParentInRoot, openPrivateStoreLockRoot, openWritableStoreRoot, readFileStoreCopySource, resolveStorePath, writeStreamToTempSource, } from "./file-store-boundary.js";
6
+ import { assertFileStoreMaxBytes, assertRelativePath, ensureParentInRoot, literalStoreRootPath, openPrivateStoreLockRoot, openWritableStoreRoot, readFileStoreCopySource, resolveStorePath, writeStreamToTempSource, } from "./file-store-boundary.js";
7
7
  import { writeFileSyncAtomic } from "./file-store-sync-write.js";
8
8
  import { createJsonStore } from "./json-document-store.js";
9
9
  import { stringifyJsonDocument } from "./json-stringify.js";
@@ -61,7 +61,7 @@ function handleSyncStoreReadOpenFailure(opened) {
61
61
  });
62
62
  }
63
63
  async function copyIntoRoot(params) {
64
- const relativePath = assertRelativePath(params.relativePath);
64
+ const relativePath = params.relativePath;
65
65
  const destination = resolveStorePath(params.rootDir, relativePath);
66
66
  assertNoWindowsPathAlias(params.sourcePath, "filesystem", "source path uses a Windows filesystem namespace alias");
67
67
  const sourceStat = syncFs.lstatSync(params.sourcePath);
@@ -75,7 +75,7 @@ async function copyIntoRoot(params) {
75
75
  maxBytes: params.maxBytes,
76
76
  });
77
77
  await ensureParentInRoot(scopedRoot, relativePath, params.dirMode);
78
- await scopedRoot.copyIn(relativePath, params.sourcePath, {
78
+ await scopedRoot.copyIn(literalStoreRootPath(relativePath), params.sourcePath, {
79
79
  durable: params.durable,
80
80
  maxBytes: params.maxBytes,
81
81
  mkdir: false,
@@ -97,8 +97,7 @@ export function fileStore(options) {
97
97
  return await root(rootDir, { hardlinks: "reject", maxBytes });
98
98
  }
99
99
  async function write(relativePath, data, writeOptions) {
100
- const safeRelativePath = assertRelativePath(relativePath);
101
- const destination = resolveStorePath(rootDir, safeRelativePath);
100
+ const destination = resolveStorePath(rootDir, relativePath);
102
101
  const content = Buffer.isBuffer(data) ? data : Buffer.from(data);
103
102
  const writeMaxBytes = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
104
103
  assertFileStoreMaxBytes(content.byteLength, writeMaxBytes);
@@ -121,8 +120,8 @@ export function fileStore(options) {
121
120
  dirMode: writeDirMode,
122
121
  maxBytes: writeMaxBytes,
123
122
  });
124
- await ensureParentInRoot(scopedRoot, safeRelativePath, writeDirMode);
125
- await scopedRoot.write(safeRelativePath, content, {
123
+ await ensureParentInRoot(scopedRoot, relativePath, writeDirMode);
124
+ await scopedRoot.write(literalStoreRootPath(relativePath), content, {
126
125
  mkdir: false,
127
126
  mode: writeMode,
128
127
  durable: writeDurable,
@@ -135,8 +134,7 @@ export function fileStore(options) {
135
134
  root: openRoot,
136
135
  write,
137
136
  writeStream: async (relativePath, stream, writeOptions) => {
138
- const safeRelativePath = assertRelativePath(relativePath);
139
- const destination = resolveStorePath(rootDir, safeRelativePath);
137
+ const destination = resolveStorePath(rootDir, relativePath);
140
138
  const configuredLimit = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
141
139
  const limit = configuredLimit ?? (privateMode ? DEFAULT_ROOT_MAX_BYTES : undefined);
142
140
  if (privateMode) {
@@ -172,7 +170,7 @@ export function fileStore(options) {
172
170
  try {
173
171
  await copyIntoRoot({
174
172
  rootDir,
175
- relativePath: safeRelativePath,
173
+ relativePath,
176
174
  sourcePath: staged.path,
177
175
  durable: writeDurable,
178
176
  maxBytes: limit,
@@ -214,17 +212,17 @@ export function fileStore(options) {
214
212
  tempPrefix: writeOptions?.tempPrefix,
215
213
  });
216
214
  },
217
- open: async (relativePath, readOptions) => await (await openRoot()).open(assertRelativePath(relativePath), readOptions),
218
- read: async (relativePath, readOptions) => await (await openRoot()).read(assertRelativePath(relativePath), readOptions),
219
- readBytes: async (relativePath, readOptions) => await (await openRoot()).readBytes(assertRelativePath(relativePath), readOptions),
215
+ open: async (relativePath, readOptions) => await (await openRoot()).open(literalStoreRootPath(assertRelativePath(relativePath)), readOptions),
216
+ read: async (relativePath, readOptions) => await (await openRoot()).read(literalStoreRootPath(assertRelativePath(relativePath)), readOptions),
217
+ readBytes: async (relativePath, readOptions) => await (await openRoot()).readBytes(literalStoreRootPath(assertRelativePath(relativePath)), readOptions),
220
218
  readText: async (relativePath, readOptions) => {
221
219
  const { encoding = "utf8", ...options } = readOptions ?? {};
222
- return (await (await openRoot()).read(assertRelativePath(relativePath), options)).buffer
220
+ return (await (await openRoot()).read(literalStoreRootPath(assertRelativePath(relativePath)), options)).buffer
223
221
  .toString(encoding);
224
222
  },
225
223
  readTextIfExists: async (relativePath, readOptions) => {
226
224
  try {
227
- return await (await openRoot()).readText(assertRelativePath(relativePath), readOptions);
225
+ return await (await openRoot()).readText(literalStoreRootPath(assertRelativePath(relativePath)), readOptions);
228
226
  }
229
227
  catch (error) {
230
228
  if (isNotFound(error)) {
@@ -235,12 +233,12 @@ export function fileStore(options) {
235
233
  },
236
234
  readJson: async (relativePath, readOptions) => {
237
235
  const { encoding = "utf8", ...options } = readOptions ?? {};
238
- return JSON.parse((await (await openRoot()).read(assertRelativePath(relativePath), options)).buffer
236
+ return JSON.parse((await (await openRoot()).read(literalStoreRootPath(assertRelativePath(relativePath)), options)).buffer
239
237
  .toString(encoding));
240
238
  },
241
239
  readJsonIfExists: async (relativePath, readOptions) => {
242
240
  try {
243
- return await (await openRoot()).readJson(assertRelativePath(relativePath), readOptions);
241
+ return await (await openRoot()).readJson(literalStoreRootPath(assertRelativePath(relativePath)), readOptions);
244
242
  }
245
243
  catch (error) {
246
244
  if (isNotFound(error)) {
@@ -250,9 +248,9 @@ export function fileStore(options) {
250
248
  }
251
249
  },
252
250
  remove: async (relativePath) => {
253
- await (await openRoot()).remove(assertRelativePath(relativePath));
251
+ await (await openRoot()).remove(literalStoreRootPath(assertRelativePath(relativePath)));
254
252
  },
255
- exists: async (relativePath) => await (await openRoot()).exists(assertRelativePath(relativePath)),
253
+ exists: async (relativePath) => await (await openRoot()).exists(literalStoreRootPath(assertRelativePath(relativePath))),
256
254
  writeText: async (relativePath, data, writeOptions) => await write(relativePath, data, writeOptions),
257
255
  writeJson: async (relativePath, data, writeOptions) => {
258
256
  const trailingNewline = writeOptions?.trailingNewline;
@@ -269,7 +267,7 @@ export function fileStore(options) {
269
267
  } : {}),
270
268
  readIfExists: async () => {
271
269
  try {
272
- return await (await openRoot()).readJson(assertRelativePath(relativePath));
270
+ return await (await openRoot()).readJson(literalStoreRootPath(relativePath));
273
271
  }
274
272
  catch (error) {
275
273
  if (isNotFound(error)) {
@@ -278,7 +276,7 @@ export function fileStore(options) {
278
276
  throw error;
279
277
  }
280
278
  },
281
- readRequired: async () => await (await openRoot()).readJson(assertRelativePath(relativePath)),
279
+ readRequired: async () => await (await openRoot()).readJson(literalStoreRootPath(relativePath)),
282
280
  write: async (value, options) => {
283
281
  const json = stringifyJsonDocument(value, null, 2);
284
282
  await write(relativePath, options?.trailingNewline === false ? json : `${json}\n`, { durable: options?.durable });
@@ -36,15 +36,6 @@ export const GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON = [
36
36
  " is_linux = sys.platform.startswith('linux')",
37
37
  " if is_linux:",
38
38
  " rename_fn = getattr(libc, 'renameat2', None)",
39
- " if rename_fn is None:",
40
- " os.link(",
41
- " src_basename,",
42
- " dst_basename,",
43
- " src_dir_fd=src_parent_fd,",
44
- " dst_dir_fd=dst_parent_fd,",
45
- " follow_symlinks=False,",
46
- " )",
47
- " return",
48
39
  " flags = 1 # RENAME_NOREPLACE",
49
40
  " elif sys.platform == 'darwin':",
50
41
  " rename_fn = getattr(libc, 'renameatx_np', None)",
@@ -52,32 +43,33 @@ export const GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON = [
52
43
  " else:",
53
44
  " rename_fn = None",
54
45
  " flags = 0",
55
- " if rename_fn is None:",
56
- " raise OSError(errno.ENOSYS, 'atomic no-replace rename is unavailable')",
57
- " rename_fn.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint]",
58
- " rename_fn.restype = ctypes.c_int",
59
- " result = rename_fn(",
60
- " src_parent_fd,",
61
- " os.fsencode(src_basename),",
62
- " dst_parent_fd,",
63
- " os.fsencode(dst_basename),",
64
- " flags,",
65
- " )",
66
- " if result != 0:",
46
+ " if rename_fn is not None:",
47
+ " rename_fn.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint]",
48
+ " rename_fn.restype = ctypes.c_int",
49
+ " result = rename_fn(",
50
+ " src_parent_fd,",
51
+ " os.fsencode(src_basename),",
52
+ " dst_parent_fd,",
53
+ " os.fsencode(dst_basename),",
54
+ " flags,",
55
+ " )",
56
+ " if result == 0:",
57
+ " return",
67
58
  " error_code = ctypes.get_errno()",
68
59
  " unsupported_codes = {errno.ENOSYS, errno.EINVAL, errno.ENOTSUP}",
69
60
  " if hasattr(errno, 'EOPNOTSUPP'):",
70
61
  " unsupported_codes.add(errno.EOPNOTSUPP)",
71
- " if is_linux and error_code in unsupported_codes:",
72
- " os.link(",
73
- " src_basename,",
74
- " dst_basename,",
75
- " src_dir_fd=src_parent_fd,",
76
- " dst_dir_fd=dst_parent_fd,",
77
- " follow_symlinks=False,",
78
- " )",
79
- " return",
80
- " raise OSError(error_code, os.strerror(error_code), dst_basename)",
62
+ " if not is_linux or error_code not in unsupported_codes:",
63
+ " raise OSError(error_code, os.strerror(error_code), dst_basename)",
64
+ " elif not is_linux:",
65
+ " raise OSError(errno.ENOSYS, 'atomic no-replace rename is unavailable')",
66
+ " os.link(",
67
+ " src_basename,",
68
+ " dst_basename,",
69
+ " src_dir_fd=src_parent_fd,",
70
+ " dst_dir_fd=dst_parent_fd,",
71
+ " follow_symlinks=False,",
72
+ " )",
81
73
  ].join("\n");
82
74
  export const GUEST_FILESYSTEM_CREATE_EXCLUSIVE_PYTHON = [
83
75
  "def create_exclusive(parent_fd, basename, stdin_buffer):",
package/dist/guest.js CHANGED
@@ -270,13 +270,15 @@ export const GUEST_FILESYSTEM_PYTHON = [
270
270
  "def copy_entry(src_parent_fd, src_basename, dst_parent_fd, dst_basename):",
271
271
  " src_stat = os.lstat(src_basename, dir_fd=src_parent_fd)",
272
272
  " if stat.S_ISDIR(src_stat.st_mode) and not stat.S_ISLNK(src_stat.st_mode):",
273
- " os.mkdir(dst_basename, stat.S_IMODE(src_stat.st_mode) or 0o755, dir_fd=dst_parent_fd)",
274
273
  " copied_children = []",
275
274
  " src_dir_fd = None",
276
275
  " dst_dir_fd = None",
276
+ " destination_created = False",
277
277
  " try:",
278
278
  " src_dir_fd = open_dir(src_basename, dir_fd=src_parent_fd)",
279
279
  " src_stat = os.fstat(src_dir_fd)",
280
+ " os.mkdir(dst_basename, stat.S_IMODE(src_stat.st_mode), dir_fd=dst_parent_fd)",
281
+ " destination_created = True",
280
282
  " dst_dir_fd = open_dir(dst_basename, dir_fd=dst_parent_fd)",
281
283
  " for child in os.listdir(src_dir_fd):",
282
284
  " copied_children.append((child, copy_entry(src_dir_fd, child, dst_dir_fd, child)))",
@@ -284,10 +286,11 @@ export const GUEST_FILESYSTEM_PYTHON = [
284
286
  " if dst_dir_fd is not None:",
285
287
  " os.close(dst_dir_fd)",
286
288
  " dst_dir_fd = None",
287
- " try:",
288
- " remove_tree(dst_parent_fd, dst_basename)",
289
- " except FileNotFoundError:",
290
- " pass",
289
+ " if destination_created:",
290
+ " try:",
291
+ " remove_tree(dst_parent_fd, dst_basename)",
292
+ " except FileNotFoundError:",
293
+ " pass",
291
294
  " raise",
292
295
  " finally:",
293
296
  " if src_dir_fd is not None:",
@@ -328,30 +331,36 @@ export const GUEST_FILESYSTEM_PYTHON = [
328
331
  " raise",
329
332
  " src_stat = os.lstat(src_basename, dir_fd=src_parent_fd)",
330
333
  " if stat.S_ISDIR(src_stat.st_mode) and not stat.S_ISLNK(src_stat.st_mode):",
331
- " temp_dir_name = create_temp_dir(dst_parent_fd, stat.S_IMODE(src_stat.st_mode) or 0o755)",
334
+ " temp_dir_name = None",
332
335
  " copied_children = []",
333
336
  " temp_dir_fd = None",
334
337
  " src_dir_fd = None",
335
338
  " try:",
336
- " temp_dir_fd = open_dir(temp_dir_name, dir_fd=dst_parent_fd)",
337
339
  " src_dir_fd = open_dir(src_basename, dir_fd=src_parent_fd)",
338
340
  " src_stat = os.fstat(src_dir_fd)",
341
+ " source_mode = stat.S_IMODE(src_stat.st_mode)",
342
+ " temp_dir_name = create_temp_dir(dst_parent_fd, source_mode or 0o700)",
343
+ " temp_dir_fd = open_dir(temp_dir_name, dir_fd=dst_parent_fd)",
339
344
  " for child in os.listdir(src_dir_fd):",
340
345
  " copied_children.append((child, copy_entry(src_dir_fd, child, temp_dir_fd, child)))",
341
346
  " os.close(src_dir_fd)",
342
347
  " src_dir_fd = None",
348
+ " os.rename(temp_dir_name, dst_basename, src_dir_fd=dst_parent_fd, dst_dir_fd=dst_parent_fd)",
349
+ " temp_dir_name = None",
350
+ " if source_mode == 0:",
351
+ " os.fchmod(temp_dir_fd, 0)",
343
352
  " os.close(temp_dir_fd)",
344
353
  " temp_dir_fd = None",
345
- " os.rename(temp_dir_name, dst_basename, src_dir_fd=dst_parent_fd, dst_dir_fd=dst_parent_fd)",
346
354
  " except Exception:",
347
355
  " if src_dir_fd is not None:",
348
356
  " os.close(src_dir_fd)",
349
357
  " if temp_dir_fd is not None:",
350
358
  " os.close(temp_dir_fd)",
351
- " try:",
352
- " remove_tree(dst_parent_fd, temp_dir_name)",
353
- " except FileNotFoundError:",
354
- " pass",
359
+ " if temp_dir_name is not None:",
360
+ " try:",
361
+ " remove_tree(dst_parent_fd, temp_dir_name)",
362
+ " except FileNotFoundError:",
363
+ " pass",
355
364
  " raise",
356
365
  " remove_copied_entry(src_parent_fd, src_basename, ('dir', entry_identity(src_stat), copied_children))",
357
366
  " os.fsync(dst_parent_fd)",
@@ -4,11 +4,7 @@ export type DurableQueueEntryPathsLike = {
4
4
  deliveredPath: string;
5
5
  processingPath?: string;
6
6
  };
7
- export type ValidatedDurableQueueEntryPaths = Readonly<{
8
- jsonPath: string;
9
- deliveredPath: string;
10
- processingPath: string;
11
- }>;
7
+ export type ValidatedDurableQueueEntryPaths = Readonly<Required<DurableQueueEntryPathsLike>>;
12
8
  export declare function validateDurableQueueEntryPaths(paths: DurableQueueEntryPathsLike): ValidatedDurableQueueEntryPaths;
13
9
  export declare function claimDurableQueueEntry(paths: DurableQueueEntryPathsLike, options?: {
14
10
  skipUnowned?: boolean;
@@ -1,4 +1,3 @@
1
- export { DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES } from "./json-durable-queue-read.js";
2
1
  export type JsonDurableQueueEntryPaths = {
3
2
  jsonPath: string;
4
3
  deliveredPath: string;
@@ -8,7 +8,6 @@ import { stringifyJsonDocument } from "./json-stringify.js";
8
8
  import { replaceFileAtomicWithDirectorySync } from "./replace-file.js";
9
9
  import { assertSafePathSegment } from "./safe-path-segment.js";
10
10
  import { admitStandalonePublicationPath, assertNoWindowsPathAlias, hasWindowsPathAlias, } from "./windows-path-alias.js";
11
- export { DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES } from "./json-durable-queue-read.js";
12
11
  function assertSafeQueueEntryId(id) {
13
12
  assertSafePathSegment(id, { label: "queue entry id" });
14
13
  }
@@ -146,8 +145,7 @@ export async function loadPendingJsonDurableQueueEntries(options) {
146
145
  await unlinkStaleTmpBestEffort(path.join(queueDir, file), now, options.cleanupTmpMaxAgeMs);
147
146
  }
148
147
  }
149
- const ids = [];
150
- const seenIds = new Set();
148
+ const ids = new Set();
151
149
  for (const file of files) {
152
150
  const suffix = file.endsWith(".processing")
153
151
  ? ".processing"
@@ -163,10 +161,7 @@ export async function loadPendingJsonDurableQueueEntries(options) {
163
161
  catch {
164
162
  continue;
165
163
  }
166
- if (seenIds.has(id))
167
- continue;
168
- seenIds.add(id);
169
- ids.push(id);
164
+ ids.add(id);
170
165
  }
171
166
  const entries = [];
172
167
  for (const id of ids) {
@@ -1,16 +1,13 @@
1
1
  import type { BigIntStats, Stats } from "node:fs";
2
2
  import type { FileHandle } from "node:fs/promises";
3
- import type { HardlinkPolicy } from "./root-options.js";
4
- import type { SymlinkPolicy } from "./root-symlink-policy.js";
3
+ import type { RootReadOptions } from "./root-options.js";
5
4
  type OwnedLocalFile = {
6
5
  handle: FileHandle;
7
6
  stat: Stats;
8
7
  identity: BigIntStats;
9
8
  preOpenStat: BigIntStats | undefined;
10
9
  };
11
- export declare function openLocalFileDescriptor(filePath: string, options?: {
12
- hardlinks?: HardlinkPolicy;
13
- symlinks?: SymlinkPolicy;
10
+ export declare function openLocalFileDescriptor(filePath: string, options?: Pick<RootReadOptions, "hardlinks" | "symlinks"> & {
14
11
  readWrite?: true;
15
12
  }): Promise<OwnedLocalFile>;
16
13
  export {};
@@ -1,4 +1,4 @@
1
- import { type HardlinkPolicy, type ReadResult, type SymlinkPolicy } from "./root.js";
1
+ import { type ReadResult, type RootReadOptions } from "./root.js";
2
2
  export type LocalRootsPathResult = {
3
3
  path: string;
4
4
  root: string;
@@ -15,11 +15,6 @@ export type ResolveLocalPathFromRootsSyncOptions = LocalRootsInputOptions & {
15
15
  allowMissing?: boolean;
16
16
  requireFile?: boolean;
17
17
  };
18
- export type ReadLocalFileFromRootsOptions = LocalRootsInputOptions & {
19
- hardlinks?: HardlinkPolicy;
20
- maxBytes?: number;
21
- nonBlockingRead?: boolean;
22
- symlinks?: SymlinkPolicy;
23
- };
18
+ export type ReadLocalFileFromRootsOptions = LocalRootsInputOptions & RootReadOptions;
24
19
  export declare function resolveLocalPathFromRootsSync(options: ResolveLocalPathFromRootsSyncOptions): LocalRootsPathResult | null;
25
20
  export declare function readLocalFileFromRoots(options: ReadLocalFileFromRootsOptions): Promise<LocalRootsReadResult | null>;
@@ -1,6 +1,7 @@
1
1
  import type { TarMeterLimits } from "./archive-limits.js";
2
2
  import type { ArchiveMemberKind } from "./archive-plan.js";
3
3
  import type { CopyCloneMode } from "./copy-policy.js";
4
+ import type { WindowsAceFlags } from "./owner-dacl.js";
4
5
  export interface NativeFileHash {
5
6
  bytes: number;
6
7
  digest: string;
@@ -58,16 +59,7 @@ export interface NativeWindowsAccessControlEntry {
58
59
  sid: string;
59
60
  mask: number;
60
61
  aceType: string;
61
- flags: {
62
- raw: number;
63
- objectInherit: boolean;
64
- containerInherit: boolean;
65
- noPropagateInherit: boolean;
66
- inheritOnly: boolean;
67
- inherited: boolean;
68
- successfulAccess: boolean;
69
- failedAccess: boolean;
70
- };
62
+ flags: WindowsAceFlags;
71
63
  }
72
64
  export interface NativeWindowsSecurityFacts {
73
65
  ownerSid: string;
@@ -96,6 +88,12 @@ export interface NativeWindowsDirectoryReceipt {
96
88
  export interface NativeDarwinAclFacts {
97
89
  state: "absent" | "empty" | "present";
98
90
  }
91
+ type NativeTwoPathArgs = [
92
+ sourceRootFd: number,
93
+ sourceRelPath: string,
94
+ targetRootFd: number,
95
+ targetRelPath: string
96
+ ];
99
97
  export interface NativeBinding {
100
98
  /** Internal: consumes only a descriptor returned by this binding. */
101
99
  closeOwnedFd(fd: number): void;
@@ -141,7 +139,7 @@ export interface NativeBinding {
141
139
  extractArchiveNative(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], limits: TarMeterLimits, signal: AbortSignal): Promise<void>;
142
140
  fstatIdentity(fd: number): NativeFileIdentity;
143
141
  inspectArchiveNative(path: string, kind: string, limits: TarMeterLimits, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
144
- linkBeneath(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
142
+ linkBeneath(...args: NativeTwoPathArgs): void;
145
143
  /** Direct-child mkdir; true is receipt provenance only, never cleanup ownership. */
146
144
  mkdirChildBeneath?(parentFd: number, basename: string, mode: number): boolean;
147
145
  mkdirBeneath(rootFd: number, relPath: string, mode: number): void;
@@ -153,10 +151,11 @@ export interface NativeBinding {
153
151
  ownedTreeRemovalAvailable?(parentFd: number): boolean;
154
152
  removeOwnedTree?(parentFd: number, basename: string, directoryFd: number): Promise<NativeOwnedTreeRemovalResult>;
155
153
  removeOwnedTreeSync?(parentFd: number, basename: string, directoryFd: number): NativeOwnedTreeRemovalResult;
156
- renameNoReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
154
+ renameNoReplace(...args: NativeTwoPathArgs): void;
157
155
  /** Identity-fenced retained-directory rename capability. */
158
156
  renameNoReplaceWithIdentity?(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string, expectedSourceDev: bigint, expectedSourceIno: bigint): void;
159
- renameReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
157
+ renameReplace(...args: NativeTwoPathArgs): void;
160
158
  sha256File(fd: number, maxBytes?: number, signal?: AbortSignal): Promise<NativeFileHash>;
161
159
  }
162
160
  export declare function captureNativeFdClose(binding: NativeBinding): (fd: number) => void;
161
+ export {};
@@ -7,6 +7,12 @@ export type PermissionCommandFailure = {
7
7
  signal: string | null;
8
8
  stderr: string;
9
9
  };
10
+ export type PermissionFailureFields = {
11
+ error?: string;
12
+ errorDetail?: PermissionCommandFailure;
13
+ /** Original inspection failure, retained separately from serializable diagnostics. */
14
+ errorCause?: unknown;
15
+ };
10
16
  export declare function formatPermissionErrorDetail(value: string): string;
11
17
  /** Formats only caught permission-query failures without invoking user code. */
12
18
  export declare function formatCaughtPermissionFailure(error: unknown): string;
@@ -1,3 +1,4 @@
1
- export { formatOctal, formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, safeStat, type PermissionCheck, type PermissionCheckOptions, type PermissionCommandFailure, type SafeStatResult, } from "./permissions.js";
1
+ export { formatOctal, formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, safeStat, type PermissionCheck, type PermissionCheckOptions, type SafeStatResult, } from "./permissions.js";
2
+ export type { PermissionCommandFailure } from "./permission-exec.js";
2
3
  export { createPrivateDirectory, type CreatePrivateDirectoryOptions, } from "./private-directory.js";
3
4
  export { readOwnerAndDacl, type OwnerAndDaclResult, type WindowsAccessControlEntry, type WindowsAceFlags, } from "./owner-dacl.js";
@@ -1,4 +1,4 @@
1
- import { type PermissionCommandFailure } from "./permission-exec.js";
1
+ import { type PermissionFailureFields } from "./permission-exec.js";
2
2
  import { type PermissionCheck, type PermissionCheckOptions } from "./permissions.js";
3
3
  export type PermissionExec = (command: string, args: string[]) => Promise<{
4
4
  stdout: string;
@@ -13,16 +13,13 @@ export type WindowsAclEntry = {
13
13
  canRead: boolean;
14
14
  canWrite: boolean;
15
15
  };
16
- export type WindowsAclSummary = {
16
+ export type WindowsAclSummary = Omit<PermissionFailureFields & {
17
17
  ok: boolean;
18
18
  entries: WindowsAclEntry[];
19
19
  untrustedWorld: WindowsAclEntry[];
20
20
  untrustedGroup: WindowsAclEntry[];
21
21
  trusted: WindowsAclEntry[];
22
- error?: string;
23
- errorDetail?: PermissionCommandFailure;
24
- errorCause?: unknown;
25
- };
22
+ }, never>;
26
23
  export type WindowsUserInfoProvider = () => {
27
24
  username?: string | null;
28
25
  };
@@ -1,9 +1,6 @@
1
- import type { PermissionCommandFailure } from "./permission-exec.js";
1
+ import type { PermissionFailureFields } from "./permission-exec.js";
2
2
  import { type PermissionExec } from "./permissions-windows.js";
3
- export type { PermissionCommandFailure } from "./permission-exec.js";
4
- export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
5
- export type { IcaclsResetCommandOptions, PermissionExec, WindowsAclEntry, WindowsAclSummary, WindowsUserInfoProvider, } from "./permissions-windows.js";
6
- export type PermissionCheck = {
3
+ export type PermissionCheck = Omit<PermissionFailureFields & {
7
4
  ok: boolean;
8
5
  isSymlink: boolean;
9
6
  isDir: boolean;
@@ -21,11 +18,7 @@ export type PermissionCheck = {
21
18
  /** Owner-query failure detail when Windows ownership could not be verified. */
22
19
  ownerError?: string;
23
20
  aclSummary?: string;
24
- error?: string;
25
- errorDetail?: PermissionCommandFailure;
26
- /** Original inspection failure, retained separately from serializable diagnostics. */
27
- errorCause?: unknown;
28
- };
21
+ }, never>;
29
22
  export type PermissionCheckOptions = {
30
23
  platform?: NodeJS.Platform;
31
24
  env?: NodeJS.ProcessEnv;
@@ -1,7 +1,6 @@
1
1
  import fsSync from "node:fs";
2
2
  import { formatIcaclsResetCommand, inspectWindowsPermissions, } from "./permissions-windows.js";
3
3
  import { hasWindowsPathAlias, pathForWindowsFilesystem, } from "./windows-path-alias.js";
4
- export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
5
4
  function failedSafeStat(error) {
6
5
  return {
7
6
  ok: false,
@@ -1,14 +1,9 @@
1
- import type { DenyMutationPolicy } from "./deny-mutations.js";
1
+ import type { PinnedMutationPolicySnapshot } from "./pinned-mutation-admission.js";
2
2
  import type { RootBoundaryIdentity } from "./root-boundary.js";
3
- import type { MutationSymlinkPolicy } from "./root-symlink-policy.js";
4
3
  export type ExactRootIdentity = Readonly<{
5
4
  dev: bigint;
6
5
  ino: bigint;
7
6
  }>;
8
- type SharedMutationPolicy = Readonly<{
9
- denyMutations?: DenyMutationPolicy;
10
- mutationSymlinks?: MutationSymlinkPolicy;
11
- }>;
12
7
  export declare function ordinaryWindowsSegments(relativePath: string): boolean;
13
8
  export declare function ordinarySharedAbsoluteInsideRoot(rootReal: string, candidatePath: string, rootIdentity: ExactRootIdentity): boolean;
14
9
  export declare function simpleSharedRoute(params: {
@@ -16,9 +11,8 @@ export declare function simpleSharedRoute(params: {
16
11
  rootIdentity?: RootBoundaryIdentity;
17
12
  originalPath?: string;
18
13
  selectedTarget: string;
19
- policy: SharedMutationPolicy;
14
+ policy: PinnedMutationPolicySnapshot;
20
15
  }): {
21
16
  route: string;
22
17
  rootIdentity: ExactRootIdentity;
23
18
  } | undefined;
24
- export {};
@@ -18,6 +18,7 @@ export type PinnedOpenSyncFinalAdmission = (params: {
18
18
  descriptorIdentity: fs.BigIntStats;
19
19
  }) => string;
20
20
  export type PinnedOpenSyncFs = Pick<typeof fs, "constants" | "lstatSync" | "realpathSync" | "openSync" | "fstatSync" | "closeSync">;
21
+ export declare function isExpectedPathError(error: unknown): boolean;
21
22
  export declare function openPinnedFileSync(params: {
22
23
  filePath: string;
23
24
  resolvedPath?: string;
@@ -5,7 +5,7 @@ import { inspectFileIdentitySync } from "./strict-file-identity.js";
5
5
  import { resolveReadOpenFlags } from "./read-open-flags.js";
6
6
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
7
7
  import { realpathSync } from "./realpath.js";
8
- function isExpectedPathError(error) {
8
+ export function isExpectedPathError(error) {
9
9
  const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
10
10
  return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
11
11
  }
@@ -6,7 +6,7 @@ import { assertSyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryId
6
6
  import { FsSafeError } from "./errors.js";
7
7
  import { classifyNativeRenameFailure, NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH, } from "./native-rename-outcome.js";
8
8
  import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
9
- import { RetainedDirectoryReplacement } from "./retained-directory-replacement.js";
9
+ import { copyOperationalCode, RetainedDirectoryReplacement } from "./retained-directory-replacement.js";
10
10
  import { assertSafePathPrefix } from "./safe-path-segment.js";
11
11
  import { admitStandalonePublicationPath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
12
12
  import { serializePathWrite } from "./write-queue.js";
@@ -17,30 +17,18 @@ function assertGuardsCurrent(guards) {
17
17
  for (const guard of guards)
18
18
  assertSyncDirectoryGuard(guard);
19
19
  }
20
- function copyOperationalCode(target, source) {
21
- const code = source?.code;
22
- if (typeof code === "string") {
23
- Object.defineProperty(target, "code", { configurable: true, value: code });
24
- }
25
- }
26
- function replacementFailure(error, message, details) {
20
+ function replacementFailure(error, message, details, cause = error) {
27
21
  if (error instanceof FsSafeError) {
28
- return new FsSafeError(error.code, message, { cause: error, details });
22
+ return new FsSafeError(error.code, message, { cause, details });
29
23
  }
30
- const failure = new Error(message, { cause: error });
24
+ const failure = new Error(message, { cause });
31
25
  copyOperationalCode(failure, error);
32
26
  Object.defineProperty(failure, "details", { configurable: true, value: details });
33
27
  return failure;
34
28
  }
35
29
  function replacementAggregate(error, recoveryError, message, details) {
36
30
  const combined = new AggregateError([error, recoveryError], "directory replacement operation and recovery both failed");
37
- if (error instanceof FsSafeError) {
38
- return new FsSafeError(error.code, message, { cause: combined, details });
39
- }
40
- const failure = new Error(message, { cause: combined });
41
- copyOperationalCode(failure, error);
42
- Object.defineProperty(failure, "details", { configurable: true, value: details });
43
- return failure;
31
+ return replacementFailure(error, message, details, combined);
44
32
  }
45
33
  function isMissingDirectoryEntry(error) {
46
34
  return error?.code === "ENOENT";
@@ -24,15 +24,8 @@ export declare function copyFallbackReplace(params: {
24
24
  expectedSourceIdentity?: BigIntStats;
25
25
  sync: boolean;
26
26
  }): Promise<void>;
27
- export declare function copyFallbackReplaceSync(params: {
27
+ export declare function copyFallbackReplaceSync(params: Omit<Parameters<typeof copyFallbackReplace>[0], "fsModule"> & {
28
28
  fsModule: SyncFallbackFs;
29
- src: string;
30
- dest: string;
31
- destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
32
- restore: ReplaceFileCopyFallbackRestorePolicy;
33
- maxRestoreBytes?: number;
34
- expectedSourceIdentity?: BigIntStats;
35
29
  fchmodSync?: (fd: number, mode: number) => void;
36
- sync: boolean;
37
30
  }): void;
38
31
  export {};