@openclaw/fs-safe 0.18.0 → 0.18.2

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 (183) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/advanced.d.ts +4 -2
  3. package/dist/advanced.js +1 -1
  4. package/dist/archive-durability.d.ts +1 -1
  5. package/dist/archive-merge.js +0 -1
  6. package/dist/archive-parser.wasm +0 -0
  7. package/dist/archive-plan.d.ts +64 -3
  8. package/dist/archive-plan.js +55 -1
  9. package/dist/archive-staging.d.ts +1 -2
  10. package/dist/archive-staging.js +0 -2
  11. package/dist/archive-tar-wasm.d.ts +1 -1
  12. package/dist/archive-zip-directory.d.ts +1 -1
  13. package/dist/archive-zip-entry.d.ts +1 -1
  14. package/dist/archive-zip-entry.js +1 -1
  15. package/dist/archive-zip-preflight.d.ts +0 -2
  16. package/dist/archive-zip-preflight.js +0 -1
  17. package/dist/archive.d.ts +14 -8
  18. package/dist/archive.js +127 -12
  19. package/dist/atomic.d.ts +3 -1
  20. package/dist/copy-publication.d.ts +1 -1
  21. package/dist/create.d.ts +16 -2
  22. package/dist/create.js +388 -2
  23. package/dist/creation-boundary.d.ts +60 -0
  24. package/dist/creation-boundary.js +371 -0
  25. package/dist/directory-guard.d.ts +0 -1
  26. package/dist/directory-guard.js +0 -3
  27. package/dist/directory-mode-node.d.ts +20 -2
  28. package/dist/directory-mode-node.js +89 -3
  29. package/dist/durability.d.ts +2 -1
  30. package/dist/file-lock-sync-acquisition.js +3 -3
  31. package/dist/file-lock-sync-root-acquire.js +2 -6
  32. package/dist/file-lock-sync-root-options.d.ts +2 -10
  33. package/dist/file-lock-sync-root.d.ts +2 -1
  34. package/dist/file-lock-sync-stale-admission.js +3 -10
  35. package/dist/file-lock-sync.d.ts +4 -21
  36. package/dist/file-store-boundary.d.ts +7 -0
  37. package/dist/file-store-boundary.js +58 -1
  38. package/dist/file-store-sync-write.js +2 -3
  39. package/dist/file-store.js +1 -6
  40. package/dist/guarded-mkdir.d.ts +1 -1
  41. package/dist/index.d.ts +9 -6
  42. package/dist/json-durable-queue-ownership.d.ts +1 -5
  43. package/dist/json-durable-queue-read.js +7 -3
  44. package/dist/json-durable-queue.d.ts +0 -1
  45. package/dist/json-durable-queue.js +1 -3
  46. package/dist/json-store.js +1 -2
  47. package/dist/json.js +1 -2
  48. package/dist/move-path.js +1 -2
  49. package/dist/native-operations.js +0 -1
  50. package/dist/native-pinned-write-windows.d.ts +1 -1
  51. package/dist/native-pinned-write-windows.js +1 -1
  52. package/dist/native-pinned-write.d.ts +1 -1
  53. package/dist/native-pinned-write.js +3 -6
  54. package/dist/native-policy-parent-windows.d.ts +1 -1
  55. package/dist/native-staged-file.d.ts +2 -2
  56. package/dist/native-staged-file.js +1 -2
  57. package/dist/permission-exec.d.ts +6 -0
  58. package/dist/permissions-public.d.ts +2 -1
  59. package/dist/permissions-windows.d.ts +3 -6
  60. package/dist/permissions.d.ts +3 -10
  61. package/dist/permissions.js +0 -1
  62. package/dist/pinned-mutation-admission.d.ts +1 -1
  63. package/dist/pinned-open.d.ts +1 -0
  64. package/dist/pinned-open.js +7 -2
  65. package/dist/pinned-operation.d.ts +1 -1
  66. package/dist/pinned-operation.js +3 -16
  67. package/dist/pinned-write-mode.js +1 -1
  68. package/dist/pinned-write-staged.js +1 -2
  69. package/dist/pinned-write.d.ts +0 -1
  70. package/dist/pinned-write.js +3 -5
  71. package/dist/private-producer-handoff-sync.d.ts +2 -12
  72. package/dist/private-producer-handoff-sync.js +1 -1
  73. package/dist/private-producer-handoff.d.ts +3 -3
  74. package/dist/private-temp-workspace.js +1 -2
  75. package/dist/publish-copy-stage.d.ts +1 -1
  76. package/dist/publish-file.d.ts +0 -1
  77. package/dist/publish-file.js +1 -2
  78. package/dist/replace-directory.js +6 -19
  79. package/dist/replace-file-copy-fallback.d.ts +1 -8
  80. package/dist/replace-file-descriptor.d.ts +3 -11
  81. package/dist/replace-file-descriptor.js +1 -1
  82. package/dist/replace-file-rename-policy.d.ts +1 -2
  83. package/dist/replace-file.d.ts +3 -3
  84. package/dist/replace-file.js +1 -1
  85. package/dist/retained-directory-replacement.d.ts +1 -0
  86. package/dist/retained-directory-replacement.js +1 -1
  87. package/dist/root-context.js +3 -7
  88. package/dist/root-create-input.d.ts +1 -1
  89. package/dist/root-directory-creation.d.ts +1 -1
  90. package/dist/root-directory-creation.js +1 -1
  91. package/dist/root-directory-list.js +3 -7
  92. package/dist/root-entries.js +2 -2
  93. package/dist/root-errors.d.ts +4 -0
  94. package/dist/root-errors.js +5 -3
  95. package/dist/root-file-final-admission.d.ts +1 -1
  96. package/dist/root-file-final-admission.js +1 -6
  97. package/dist/root-file.js +2 -5
  98. package/dist/root-impl.d.ts +0 -5
  99. package/dist/root-impl.js +22 -21
  100. package/dist/root-move-noreplace.js +4 -10
  101. package/dist/root-observed-path.js +2 -4
  102. package/dist/root-options.d.ts +1 -1
  103. package/dist/root-path-existing.js +2 -3
  104. package/dist/root-path-stat.js +2 -4
  105. package/dist/root-public-types.d.ts +11 -0
  106. package/dist/root-remove-identity.js +2 -4
  107. package/dist/root-remove.js +2 -4
  108. package/dist/root-write-admission.d.ts +1 -1
  109. package/dist/root-write-admission.js +3 -7
  110. package/dist/root-write-complete-parent.js +2 -3
  111. package/dist/root-write-verification.d.ts +1 -1
  112. package/dist/root.d.ts +2 -6
  113. package/dist/secret-file.js +11 -17
  114. package/dist/secret-read-policy.js +1 -1
  115. package/dist/secure-file-windows.js +4 -9
  116. package/dist/secure-temp-dir.js +2 -7
  117. package/dist/secure-temp-repair.js +5 -1
  118. package/dist/sidecar-lock-acquire.js +8 -11
  119. package/dist/sidecar-lock-policy.d.ts +1 -0
  120. package/dist/sidecar-lock-policy.js +7 -0
  121. package/dist/sidecar-lock-reclaim.js +5 -6
  122. package/dist/sidecar-lock-stale-admission.js +2 -6
  123. package/dist/sidecar-lock-types.d.ts +22 -16
  124. package/dist/stat-observation.js +3 -10
  125. package/dist/strict-file-identity.d.ts +2 -0
  126. package/dist/strict-file-identity.js +6 -6
  127. package/dist/temp-target.js +3 -8
  128. package/dist/temp-workspace-admission.js +7 -15
  129. package/dist/temp-workspace-child-admission.d.ts +16 -2
  130. package/dist/temp-workspace-child-admission.js +65 -3
  131. package/dist/temp-workspace-descriptor.d.ts +1 -1
  132. package/dist/temp-workspace-descriptor.js +2 -4
  133. package/dist/text-atomic.js +1 -1
  134. package/dist/windows-owner.d.ts +3 -6
  135. package/dist/windows-owner.js +1 -1
  136. package/dist/windows-path-alias.d.ts +1 -0
  137. package/dist/windows-path-alias.js +5 -0
  138. package/dist/windows-security-bridge.cs +0 -2
  139. package/docs/advanced.md +3 -1
  140. package/docs/secret-file.md +13 -0
  141. package/docs/sidecar-lock.md +6 -0
  142. package/docs/store.md +6 -0
  143. package/docs/temp.md +8 -1
  144. package/docs/writing.md +5 -0
  145. package/package.json +8 -8
  146. package/dist/archive-native.d.ts +0 -10
  147. package/dist/archive-native.js +0 -73
  148. package/dist/archive-options.d.ts +0 -30
  149. package/dist/archive-policy.d.ts +0 -21
  150. package/dist/archive-policy.js +0 -38
  151. package/dist/archive-tar-inspect.d.ts +0 -6
  152. package/dist/archive-tar-inspect.js +0 -62
  153. package/dist/archive-tar.d.ts +0 -18
  154. package/dist/archive-tar.js +0 -21
  155. package/dist/create-directory.d.ts +0 -18
  156. package/dist/create-directory.js +0 -111
  157. package/dist/create-file-async.d.ts +0 -6
  158. package/dist/create-file-async.js +0 -121
  159. package/dist/create-file.d.ts +0 -7
  160. package/dist/create-file.js +0 -179
  161. package/dist/creation-darwin.d.ts +0 -5
  162. package/dist/creation-darwin.js +0 -70
  163. package/dist/creation-file-state.d.ts +0 -18
  164. package/dist/creation-file-state.js +0 -118
  165. package/dist/creation-path.d.ts +0 -22
  166. package/dist/creation-path.js +0 -83
  167. package/dist/creation-permissions.d.ts +0 -18
  168. package/dist/creation-permissions.js +0 -115
  169. package/dist/directory-mode-owner.d.ts +0 -20
  170. package/dist/directory-mode-owner.js +0 -78
  171. package/dist/file-store-copy-source.d.ts +0 -4
  172. package/dist/file-store-copy-source.js +0 -31
  173. package/dist/file-store-limit.d.ts +0 -1
  174. package/dist/file-store-limit.js +0 -7
  175. package/dist/file-store-path.d.ts +0 -2
  176. package/dist/file-store-path.js +0 -30
  177. package/dist/standalone-publication-path.d.ts +0 -1
  178. package/dist/standalone-publication-path.js +0 -6
  179. package/dist/temp-workspace-identity.d.ts +0 -14
  180. package/dist/temp-workspace-identity.js +0 -41
  181. package/dist/temp-workspace-permissions.d.ts +0 -3
  182. package/dist/temp-workspace-permissions.js +0 -32
  183. /package/dist/{archive-options.js → root-public-types.js} +0 -0
@@ -1,28 +1,11 @@
1
- import type { Root } from "./root-impl.js";
2
1
  import { type SidecarLockStaleSnapshot } from "./sidecar-lock-reclaim.js";
3
- import type { SidecarLockCompromisedInfo, SidecarLockRetryOptions, SidecarLockStaleRecovery } from "./sidecar-lock-types.js";
4
- export type FileLockSyncAcquireOptions<TPayload extends Record<string, unknown>> = {
5
- lockPath?: string;
2
+ import type { SidecarLockCommonAcquireOptions, SidecarLockOptionFields, SidecarLockReclaimParams } from "./sidecar-lock-types.js";
3
+ export type FileLockSyncAcquireOptions<TPayload extends Record<string, unknown>> = SidecarLockOptionFields<SidecarLockCommonAcquireOptions & {
6
4
  staleMs?: number;
7
- timeoutMs?: number;
8
- retry?: SidecarLockRetryOptions;
9
- staleRecovery?: SidecarLockStaleRecovery;
10
- reentrantOwner?: string;
11
5
  payload: () => TPayload;
12
- shouldReclaim?: (params: {
13
- lockPath: string;
14
- normalizedTargetPath: string;
15
- payload: unknown;
16
- staleMs: number;
17
- nowMs: number;
18
- heldByThisProcess: false;
19
- }) => boolean;
6
+ shouldReclaim?: (params: SidecarLockReclaimParams<false>) => boolean;
20
7
  shouldRemoveStaleLock?: (snapshot: SidecarLockStaleSnapshot) => boolean;
21
- parsePayload?: (raw: string) => unknown;
22
- lockRoot?: Root;
23
- onCompromised?: (info: SidecarLockCompromisedInfo) => void;
24
- compromiseCheckIntervalMs?: number;
25
- };
8
+ }>;
26
9
  export type FileLockSyncHandle = {
27
10
  lockPath: string;
28
11
  normalizedTargetPath: string;
@@ -29,3 +29,10 @@ export declare function ensureStoreDirectorySync(params: {
29
29
  mode: number;
30
30
  messagePrefix: "private store" | "store";
31
31
  }): SyncParentGuard;
32
+ export declare function assertRelativePath(relativePath: string): string;
33
+ export declare function resolveStorePath(rootDir: string, relativePath: string): string;
34
+ export declare function assertFileStoreMaxBytes(size: number, limit: number | undefined): void;
35
+ export declare function readFileStoreCopySource(params: {
36
+ sourcePath: string;
37
+ maxBytes: number;
38
+ }): Promise<Buffer>;
@@ -5,13 +5,15 @@ import { createByteLimitTransform } from "./bounded-read-stream.js";
5
5
  import { pipeline } from "node:stream/promises";
6
6
  import { FsSafeError } from "./errors.js";
7
7
  import { assertSyncStoreDirectoryReceipt, ensureSyncStoreDirectory, } from "./file-store-sync-directory.js";
8
- import { isPathInside } from "./path.js";
8
+ import { isPathInside, splitSafeRelativePath } from "./path.js";
9
9
  import { resolveOpenedFileRealPathForHandle, root } from "./root.js";
10
10
  import { ensureTrailingSep } from "./root-context.js";
11
11
  import { RootHandle } from "./root-impl.js";
12
12
  import { prepareSecretFileWrite } from "./secret-file.js";
13
13
  import { resolveSecureTempRoot } from "./secure-temp-dir.js";
14
14
  import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
15
+ import { readRegularFile } from "./regular-file.js";
16
+ import { errorCauseOptions } from "./root-errors.js";
15
17
  import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
16
18
  export async function ensureParentInRoot(scopedRoot, relativePath, mode) {
17
19
  const parent = path.posix.dirname(relativePath);
@@ -118,3 +120,58 @@ export function ensureStoreDirectorySync(params) {
118
120
  assertSyncStoreDirectoryReceipt(guard);
119
121
  return guard;
120
122
  }
123
+ export function assertRelativePath(relativePath) {
124
+ const raw = relativePath.trim();
125
+ if (!raw || raw !== relativePath) {
126
+ throw new FsSafeError("invalid-path", "store key must be non-empty and unpadded");
127
+ }
128
+ const segments = splitSafeRelativePath(raw);
129
+ if (segments.length === 0 ||
130
+ segments.join("/") !== raw ||
131
+ raw.normalize("NFC") !== raw ||
132
+ segments.some((segment) => /[ .]$/u.test(segment))) {
133
+ throw new FsSafeError("invalid-path", "store key must use one canonical relative spelling");
134
+ }
135
+ return raw;
136
+ }
137
+ export function resolveStorePath(rootDir, relativePath) {
138
+ const key = assertRelativePath(relativePath);
139
+ // FileStore constructors snapshot an absolute root before this helper runs.
140
+ // Do not re-resolve it: Node drops the trailing separator from an exact
141
+ // Windows namespace drive root such as `\\?\C:\`.
142
+ const root = path.isAbsolute(rootDir) ? rootDir : path.resolve(rootDir);
143
+ // The immutable key already passed segment validation; keep the containment check.
144
+ const target = path.resolve(root, key);
145
+ if (!isPathInside(root, target)) {
146
+ throw new FsSafeError("outside-workspace", "relative path escapes root");
147
+ }
148
+ return target;
149
+ }
150
+ // Store operation boundaries admit the primitive limit before reaching this helper.
151
+ export function assertFileStoreMaxBytes(size, limit) {
152
+ if (limit !== undefined && size > limit) {
153
+ throw new FsSafeError("too-large", `file exceeds maximum size of ${limit} bytes`);
154
+ }
155
+ }
156
+ export async function readFileStoreCopySource(params) {
157
+ assertNoWindowsPathAlias(params.sourcePath, "filesystem", "source path uses a Windows filesystem namespace alias");
158
+ const sourceStat = syncFs.lstatSync(params.sourcePath);
159
+ if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
160
+ throw new FsSafeError("not-file", "source path is not a file");
161
+ }
162
+ assertFileStoreMaxBytes(sourceStat.size, params.maxBytes);
163
+ try {
164
+ return (await readRegularFile({ filePath: params.sourcePath, maxBytes: params.maxBytes }))
165
+ .buffer;
166
+ }
167
+ catch (error) {
168
+ const message = error instanceof Error ? error.message : String(error);
169
+ if (message.includes("regular file") || message.includes("not a regular file")) {
170
+ throw new FsSafeError("not-file", "source path is not a file", errorCauseOptions(error));
171
+ }
172
+ if (message.includes(`exceeds ${params.maxBytes} bytes`)) {
173
+ throw new FsSafeError("too-large", `file exceeds maximum size of ${params.maxBytes} bytes`, errorCauseOptions(error));
174
+ }
175
+ throw error;
176
+ }
177
+ }
@@ -10,6 +10,7 @@ import { isPathInside } from "./path.js";
10
10
  import { resolveReadOpenFlags } from "./read-open-flags.js";
11
11
  import { writeTempFileSync } from "./replace-file-descriptor.js";
12
12
  import { SyncAtomicTempOwner } from "./replace-file-temp-owner.js";
13
+ import { errorCauseOptions } from "./root-errors.js";
13
14
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
14
15
  import { getFsSafeTestHooks } from "./test-hooks.js";
15
16
  function verifyStoreFile(fd, expected, filePath) {
@@ -108,9 +109,7 @@ export function writeFileSyncAtomic(params) {
108
109
  if (error instanceof FsSafeError) {
109
110
  throw error;
110
111
  }
111
- throw new FsSafeError("path-mismatch", "store target changed after write", {
112
- cause: error instanceof Error ? error : undefined,
113
- });
112
+ throw new FsSafeError("path-mismatch", "store target changed after write", errorCauseOptions(error));
114
113
  }
115
114
  assertSyncStoreDirectoryReceipt(parentGuard);
116
115
  if (params.durable) {
@@ -1,13 +1,9 @@
1
1
  import syncFs from "node:fs";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
2
  import { normalizeMaxBytes } from "./byte-budget.js";
5
3
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
6
4
  import { FsSafeError } from "./errors.js";
7
- import { assertFileStoreMaxBytes } from "./file-store-limit.js";
8
- import { assertRelativePath, resolveStorePath } from "./file-store-path.js";
9
5
  import { pruneExpiredStoreEntries } from "./file-store-prune.js";
10
- import { ensureParentInRoot, openPrivateStoreLockRoot, openWritableStoreRoot, writeStreamToTempSource, } from "./file-store-boundary.js";
6
+ import { assertFileStoreMaxBytes, assertRelativePath, ensureParentInRoot, openPrivateStoreLockRoot, openWritableStoreRoot, readFileStoreCopySource, resolveStorePath, writeStreamToTempSource, } from "./file-store-boundary.js";
11
7
  import { writeFileSyncAtomic } from "./file-store-sync-write.js";
12
8
  import { createJsonStore } from "./json-document-store.js";
13
9
  import { stringifyJsonDocument } from "./json-stringify.js";
@@ -18,7 +14,6 @@ import { DEFAULT_ROOT_MAX_BYTES } from "./root-impl.js";
18
14
  import { matchRootFileOpenFailure, openRootFileSync } from "./root-file.js";
19
15
  import { writeSecretFileAtomic } from "./secret-file.js";
20
16
  import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
21
- import { readFileStoreCopySource } from "./file-store-copy-source.js";
22
17
  function snapshotWriteOptions(options, durabilityFirst = true) {
23
18
  if (options == null)
24
19
  return undefined;
@@ -1,6 +1,6 @@
1
1
  import { type RootBoundaryIdentity } from "./root-boundary.js";
2
2
  import { type MutationDirectoryObservation } from "./pinned-mutation-observation.js";
3
- import type { PinnedCreatedDirectoryReceipt, PinnedMutationAdmissionReceipt, PinnedMutationAuthorizationToken } from "./pinned-write.js";
3
+ import type { PinnedCreatedDirectoryReceipt, PinnedMutationAdmissionReceipt, PinnedMutationAuthorizationToken } from "./pinned-write-types.js";
4
4
  /**
5
5
  * Creates each missing path component from `rootReal` down to `targetPath`,
6
6
  * guarding every step. Returns the real (symlink-resolved) path of the final
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- export { FsSafeError, categorizeFsSafeError, type FsSafeErrorCategory, type FsSafeErrorCode, type FsSafeErrorDetails, } from "./errors.js";
2
- export { DEFAULT_ROOT_MAX_BYTES, root, type DenyMutationPolicy, type HardlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCopySource, type RootCopyPublicationReceipt, type CopyCloneMode, type RootCreateJsonOptions, type RootCreateOptions, type RootCreateStreamOptions, type RootDefaults, type RootEntriesOptions, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type RootWalkDataEntry, type RootWalkDataEntryKind, type RootWalkDirectoryErrorBehavior, type RootWalkEntry, type RootWalkEntryFilter, type RootWalkEntryFilterResult, type RootWalkEntryKind, type RootWalkLimitBehavior, type RootWalkOptions, type RootWalkSymlinkPolicy, type SymlinkPolicy, type MutationSymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root.js";
3
- export type { ContainmentGuarantee } from "./containment.js";
4
- export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, type FsSafePythonConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
5
- export { writeExternalFileWithinRoot, type ExternalFileWriteOptions, type ExternalFileWriteResult, } from "./output.js";
6
- export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
1
+ export { FsSafeError, categorizeFsSafeError, } from "./errors.js";
2
+ export type * from "./errors.js";
3
+ export { DEFAULT_ROOT_MAX_BYTES, root, type Root, } from "./root.js";
4
+ export type * from "./root-public-types.js";
5
+ export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
6
+ export type * from "./config.js";
7
+ export { writeExternalFileWithinRoot, } from "./output.js";
8
+ export type * from "./output.js";
9
+ export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
@@ -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;
@@ -43,6 +43,7 @@ export async function withJsonDurableQueueEntry(filePath, options, run) {
43
43
  const handle = await fs.promises.open(filePath, resolveReadOpenFlags());
44
44
  let closePromise;
45
45
  const releaseReadPin = () => closePromise ??= (async () => { await handle.close(); })();
46
+ let result;
46
47
  try {
47
48
  const openedStat = inspectQueueEntry(() => fs.fstatSync(handle.fd, { bigint: true }), maxBytes, initialStat);
48
49
  inspectQueueEntry(inspectPath, maxBytes, openedStat);
@@ -53,9 +54,12 @@ export async function withJsonDurableQueueEntry(filePath, options, run) {
53
54
  });
54
55
  // The migration owner may release this pin only at the verified publication
55
56
  // boundary; until then an unlinked inode cannot be recycled into a new claim.
56
- return await run(JSON.parse(bytes.toString("utf8")), openedStat, releaseReadPin);
57
+ result = await run(JSON.parse(bytes.toString("utf8")), openedStat, releaseReadPin);
57
58
  }
58
- finally {
59
- await releaseReadPin();
59
+ catch (error) {
60
+ await releaseReadPin().catch(() => undefined);
61
+ throw error;
60
62
  }
63
+ await releaseReadPin();
64
+ return result;
61
65
  }
@@ -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;
@@ -7,9 +7,7 @@ import { withJsonDurableQueueEntry } from "./json-durable-queue-read.js";
7
7
  import { stringifyJsonDocument } from "./json-stringify.js";
8
8
  import { replaceFileAtomicWithDirectorySync } from "./replace-file.js";
9
9
  import { assertSafePathSegment } from "./safe-path-segment.js";
10
- import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
11
- import { assertNoWindowsPathAlias, hasWindowsPathAlias, } from "./windows-path-alias.js";
12
- export { DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES } from "./json-durable-queue-read.js";
10
+ import { admitStandalonePublicationPath, assertNoWindowsPathAlias, hasWindowsPathAlias, } from "./windows-path-alias.js";
13
11
  function assertSafeQueueEntryId(id) {
14
12
  assertSafePathSegment(id, { label: "queue entry id" });
15
13
  }
@@ -1,8 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { fileStore } from "./file-store.js";
3
3
  import {} from "./json-document-store.js";
4
- import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
5
- import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
4
+ import { admitStandalonePublicationPath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
6
5
  export function jsonStore(options) {
7
6
  const filePathInput = admitStandalonePublicationPath(options.filePath);
8
7
  const filePath = path.resolve(filePathInput);
package/dist/json.js CHANGED
@@ -9,10 +9,9 @@ import { openRootFileSync } from "./root-file.js";
9
9
  import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
10
10
  import { writeTempFileSync } from "./replace-file-descriptor.js";
11
11
  import { SyncAtomicTempOwner } from "./replace-file-temp-owner.js";
12
- import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
12
+ import { admitStandalonePublicationPath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
13
13
  import { writeTextAtomic } from "./text-atomic.js";
14
14
  import { sleep } from "./timing.js";
15
- import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
16
15
  const READ_RETRY_MAX_ATTEMPTS = 5;
17
16
  const READ_RETRY_BASE_DELAY_MS = 50;
18
17
  function isRetryableReadError(err) {
package/dist/move-path.js CHANGED
@@ -10,8 +10,7 @@ import { resolveReadOpenFlags } from "./read-open-flags.js";
10
10
  import { realpathSync } from "./realpath.js";
11
11
  import { cleanupPinnedFilePath } from "./file-cleanup.js";
12
12
  import { createMoveStageOwner } from "./move-path-stage.js";
13
- import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
14
- import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
13
+ import { admitStandalonePublicationPath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
15
14
  export function moveCopyFallbackReasonForRenameError(error, platform = process.platform) {
16
15
  const code = error?.code;
17
16
  if (code === "EXDEV") {
@@ -1,7 +1,6 @@
1
1
  import fsSync, {} from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { FsSafeError } from "./errors.js";
5
4
  import { sameFileIdentityForCleanup } from "./file-identity.js";
6
5
  import { getNativeBinding } from "./native.js";
7
6
  import { captureNativeFdClose } from "./native-binding.js";
@@ -2,5 +2,5 @@ import type { FileHandle } from "node:fs/promises";
2
2
  import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
3
3
  import type { FileIdentityStat } from "./file-identity.js";
4
4
  import type { NativeBinding } from "./native.js";
5
- import type { PinnedWriteParams } from "./pinned-write.js";
5
+ import type { PinnedWriteParams } from "./pinned-write-types.js";
6
6
  export declare function runPinnedWriteWindows(binding: NativeBinding, params: PinnedWriteParams, root: FileHandle, parentFd: number, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import fsSync, {} from "node:fs";
4
4
  import path from "node:path";
5
5
  import { createFileHandle } from "./create.js";
6
- import { hasPreservedCreationArtifacts } from "./creation-file-state.js";
6
+ import { hasPreservedCreationArtifacts } from "./creation-boundary.js";
7
7
  import { assertSyncDirectoryGuard } from "./directory-guard.js";
8
8
  import { FsSafeError } from "./errors.js";
9
9
  import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, } from "./native-operations.js";
@@ -1,4 +1,4 @@
1
1
  import type { FileIdentityStat } from "./file-identity.js";
2
2
  import type { NativeBinding } from "./native.js";
3
- import type { PinnedWriteParams } from "./pinned-write.js";
3
+ import type { PinnedWriteParams } from "./pinned-write-types.js";
4
4
  export declare function runPinnedWriteNative(binding: NativeBinding, params: PinnedWriteParams): Promise<FileIdentityStat>;
@@ -63,6 +63,7 @@ import { NativePolicyDirectoryMismatch } from "./native-policy-directory-observa
63
63
  import { assertPolicyStagedDirectoryCurrent, assertStagedDirectoryCurrent, describePolicyStagedDirectory, describeStagedDirectory, refreshPolicyStagedDirectoryObservation, } from "./staged-directory.js";
64
64
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
65
65
  import { realpathSync } from "./realpath.js";
66
+ import { errorCauseOptions } from "./root-errors.js";
66
67
  import { isNotFoundPathError, isSymlinkOpenError } from "./path.js";
67
68
  import { checkedMutationDirectory, } from "./pinned-mutation-observation.js";
68
69
  import { createPathSegmentRoute, joinPathSegmentRoute } from "./path-segment-route.js";
@@ -95,12 +96,8 @@ function normalizePolicyParentOpenError(error, params) {
95
96
  if (!isSymlinkOpenError(error))
96
97
  return error;
97
98
  return params.mutationAdmission?.rejectParentSymlinks
98
- ? new FsSafeError("symlink", "symlink path component not allowed", {
99
- cause: error instanceof Error ? error : undefined,
100
- })
101
- : new FsSafeError("path-mismatch", "native write parent changed during policy admission", {
102
- cause: error instanceof Error ? error : undefined,
103
- });
99
+ ? new FsSafeError("symlink", "symlink path component not allowed", errorCauseOptions(error))
100
+ : new FsSafeError("path-mismatch", "native write parent changed during policy admission", errorCauseOptions(error));
104
101
  }
105
102
  async function describePosixParent(parentFd, pathname) {
106
103
  const parentPath = realpathSync.native(pathname);
@@ -2,7 +2,7 @@ import { type BigIntStats } from "node:fs";
2
2
  import { type AsyncDirectoryGuard } from "./directory-guard.js";
3
3
  import { type NativeRootAdmission } from "./native-parent-admission.js";
4
4
  import type { NativeBinding } from "./native.js";
5
- import type { PinnedWriteParams } from "./pinned-write.js";
5
+ import type { PinnedWriteParams } from "./pinned-write-types.js";
6
6
  import { type MutationDirectoryObservation } from "./pinned-mutation-observation.js";
7
7
  type PolicyParent = {
8
8
  fd: number;
@@ -3,7 +3,7 @@ import { type BigIntStats, type Stats } from "node:fs";
3
3
  import type { AnyAsyncDirectoryGuard } from "./directory-guard.js";
4
4
  import type { FileIdentityStat } from "./file-identity.js";
5
5
  import { type NativeBinding } from "./native-binding.js";
6
- import type { PinnedWriteInput, PinnedWriteParams } from "./pinned-write.js";
6
+ import type { PinnedWriteInput, PinnedWriteParams } from "./pinned-write-types.js";
7
7
  import type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileReceipt } from "./staged-file-types.js";
8
8
  export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding, "createStagedFile" | "stagedFileMatches" | "removeStagedFile">>;
9
9
  export declare function assertNativeStaging(binding: NativeBinding): asserts binding is NativeStagingBinding;
@@ -36,10 +36,10 @@ declare class NativeStagedFile implements StagedFile {
36
36
  }
37
37
  export declare const createNativeStage: (...args: Parameters<typeof NativeStagedFile.create>) => Promise<StagedFile>;
38
38
  export declare const writeNativeStage: typeof NativeStagedFile.write;
39
- export type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileFailureDetails, StagedFilePublication, StagedFileReceipt, } from "./staged-file-types.js";
40
39
  export declare function stageFileInDirectory(options: {
41
40
  directory: string | DirectoryReceipt<Stats | BigIntStats>;
42
41
  content: string | Uint8Array;
43
42
  /** Published mode; the unpublished stage stays at 0600. Defaults to 0600. */
44
43
  mode?: number;
45
44
  }): Promise<StagedFile>;
45
+ export {};
@@ -50,8 +50,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
50
50
  var e = new Error(message);
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
- import { assertDarwinCreationAcl } from "./creation-darwin.js";
54
- import { assertPrivateCreationFile } from "./creation-file-state.js";
53
+ import { assertDarwinCreationAcl, assertPrivateCreationFile } from "./creation-boundary.js";
55
54
  import { requireNativeBinding } from "./native.js";
56
55
  import { syncFileBestEffortSync } from "./file-sync.js";
57
56
  import { randomUUID } from "node:crypto";
@@ -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,6 +1,6 @@
1
1
  import { type DenyMutationPolicy } from "./deny-mutations.js";
2
2
  import { type RootBoundaryIdentity } from "./root-boundary.js";
3
- import type { PinnedWriteMutationAdmission } from "./pinned-write.js";
3
+ import type { PinnedWriteMutationAdmission } from "./pinned-write-types.js";
4
4
  import type { MutationSymlinkPolicy } from "./root-symlink-policy.js";
5
5
  export type PinnedMutationPolicySnapshot = Readonly<{
6
6
  denyMutations?: DenyMutationPolicy;
@@ -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
  }
@@ -77,7 +77,12 @@ export function openPinnedFileSync(params) {
77
77
  }
78
78
  finally {
79
79
  if (fd !== null) {
80
- ioFs.closeSync(fd);
80
+ try {
81
+ ioFs.closeSync(fd);
82
+ }
83
+ catch {
84
+ // Preserve the failed admission result; success transfers fd above.
85
+ }
81
86
  }
82
87
  }
83
88
  }
@@ -1 +1 @@
1
- export declare function validatePinnedOperationPayload(payload: Record<string, unknown>): void;
1
+ export declare function validatePinnedRelativePath(relativePath: unknown): void;
@@ -1,19 +1,7 @@
1
1
  import { FsSafeError } from "./errors.js";
2
- export function validatePinnedOperationPayload(payload) {
3
- if (typeof payload.relativePath === "string") {
4
- validatePinnedRelativePath(payload.relativePath);
5
- }
6
- if (typeof payload.relativeParentPath === "string") {
7
- validatePinnedRelativePath(payload.relativeParentPath);
8
- }
9
- if (typeof payload.from === "string") {
10
- validatePinnedRelativePath(payload.from);
11
- }
12
- if (typeof payload.to === "string") {
13
- validatePinnedRelativePath(payload.to);
14
- }
15
- }
16
- function validatePinnedRelativePath(relativePath) {
2
+ export function validatePinnedRelativePath(relativePath) {
3
+ if (typeof relativePath !== "string")
4
+ return;
17
5
  if (relativePath.length === 0 || relativePath === ".") {
18
6
  return;
19
7
  }
@@ -21,7 +9,6 @@ function validatePinnedRelativePath(relativePath) {
21
9
  throw new FsSafeError("invalid-path", "relative path contains a NUL byte");
22
10
  }
23
11
  if (relativePath.startsWith("/") ||
24
- relativePath.startsWith("//") ||
25
12
  relativePath === ".." ||
26
13
  relativePath.startsWith("../") ||
27
14
  relativePath.startsWith("..\\")) {
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import { FsSafeError } from "./errors.js";
3
3
  import { assertSynchronousCallbackResult } from "./mutation-authority.js";
4
- import { assertPrivateCreationFile } from "./creation-file-state.js";
4
+ import { assertPrivateCreationFile } from "./creation-boundary.js";
5
5
  export function assertPinnedWriteMode(fd, mode, privateCreation = false) {
6
6
  if (process.platform === "win32")
7
7
  return;
@@ -3,8 +3,7 @@ import fsSync, {} from "node:fs";
3
3
  import fs, {} from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { createFileHandle } from "./create.js";
6
- import { hasPreservedCreationArtifacts } from "./creation-file-state.js";
7
- import { creationAdmissionFromParent } from "./creation-path.js";
6
+ import { hasPreservedCreationArtifacts, creationAdmissionFromParent } from "./creation-boundary.js";
8
7
  import { syncDirectoryBestEffort } from "./directory-durability.js";
9
8
  import { FsSafeError } from "./errors.js";
10
9
  import { sameFileIdentity, sha256Hex } from "./file-identity.js";
@@ -1,6 +1,5 @@
1
1
  import type { FileIdentityStat } from "./file-identity.js";
2
2
  import type { PinnedWriteParams, RenameIdentityPolicy } from "./pinned-write-types.js";
3
- export type { PinnedWriteInput, RenameIdentityPolicy, PublishedWriteIdentity, PinnedMutationAdmissionReceipt, PinnedMutationAuthorizationToken, PinnedCreatedDirectoryReceipt, PinnedMutationParentWalkSession, PinnedWriteMutationAdmission, PinnedWriteParams, } from "./pinned-write-types.js";
4
3
  export declare function runPinnedWriteHelper(params: PinnedWriteParams): Promise<FileIdentityStat>;
5
4
  export declare function runPinnedWriteWithRenamePolicy(params: PinnedWriteParams & {
6
5
  targetPath: string;
@@ -3,7 +3,7 @@ import fsSync, {} from "node:fs";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { createFileHandle } from "./create.js";
6
- import { creationAdmissionFromParent } from "./creation-path.js";
6
+ import { creationAdmissionFromParent } from "./creation-boundary.js";
7
7
  import { normalizeMaxBytes } from "./byte-budget.js";
8
8
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, createNearestExistingDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
9
9
  import { FsSafeError } from "./errors.js";
@@ -13,7 +13,7 @@ import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
13
13
  import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
14
14
  import { runPinnedWriteNative } from "./native-pinned-write.js";
15
15
  import { getNativeBinding } from "./native.js";
16
- import { validatePinnedOperationPayload } from "./pinned-operation.js";
16
+ import { validatePinnedRelativePath } from "./pinned-operation.js";
17
17
  import { cleanupPinnedFilePath } from "./file-cleanup.js";
18
18
  import { withSidecarLock } from "./sidecar-lock.js";
19
19
  import { getFsSafeTestHooks } from "./test-hooks.js";
@@ -71,9 +71,7 @@ export async function runPinnedWriteHelper(params) {
71
71
  rootIdentity,
72
72
  };
73
73
  assertSafeBasename(normalizedParams.basename);
74
- validatePinnedOperationPayload({
75
- relativeParentPath: normalizedParams.relativeParentPath,
76
- });
74
+ validatePinnedRelativePath(normalizedParams.relativeParentPath);
77
75
  assertNoWindowsPathAlias(normalizedParams.rootPath, "filesystem", "pinned write root uses a Windows filesystem namespace alias");
78
76
  assertNoWindowsPathAlias(normalizedParams.relativeParentPath, "relative", "pinned write parent uses a Windows filesystem namespace alias");
79
77
  assertNoWindowsPathAlias(normalizedParams.basename, "relative", "pinned write basename uses a Windows filesystem namespace alias");