@openclaw/fs-safe 0.18.0 → 0.18.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.
Files changed (145) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/advanced.d.ts +2 -1
  3. package/dist/archive-durability.d.ts +1 -1
  4. package/dist/archive-parser.wasm +0 -0
  5. package/dist/archive-plan.d.ts +64 -3
  6. package/dist/archive-plan.js +55 -1
  7. package/dist/archive-tar-wasm.d.ts +1 -1
  8. package/dist/archive-zip-directory.d.ts +1 -1
  9. package/dist/archive-zip-entry.d.ts +1 -1
  10. package/dist/archive-zip-entry.js +1 -1
  11. package/dist/archive.d.ts +8 -5
  12. package/dist/archive.js +122 -9
  13. package/dist/atomic.d.ts +3 -1
  14. package/dist/copy-publication.d.ts +1 -1
  15. package/dist/create.d.ts +16 -2
  16. package/dist/create.js +388 -2
  17. package/dist/creation-boundary.d.ts +60 -0
  18. package/dist/creation-boundary.js +371 -0
  19. package/dist/directory-mode-node.js +12 -2
  20. package/dist/durability.d.ts +2 -1
  21. package/dist/file-lock-sync-acquisition.js +2 -2
  22. package/dist/file-lock-sync-root-acquire.js +2 -6
  23. package/dist/file-lock-sync-root.d.ts +2 -1
  24. package/dist/file-lock-sync-stale-admission.js +3 -10
  25. package/dist/file-lock-sync.d.ts +4 -21
  26. package/dist/file-store-boundary.d.ts +7 -0
  27. package/dist/file-store-boundary.js +58 -1
  28. package/dist/file-store-sync-write.js +2 -3
  29. package/dist/file-store.js +1 -6
  30. package/dist/guarded-mkdir.d.ts +1 -1
  31. package/dist/index.d.ts +9 -6
  32. package/dist/json-durable-queue-read.js +7 -3
  33. package/dist/json-durable-queue.js +1 -2
  34. package/dist/json-store.js +1 -2
  35. package/dist/json.js +1 -2
  36. package/dist/move-path.js +1 -2
  37. package/dist/native-operations.js +0 -1
  38. package/dist/native-pinned-write-windows.d.ts +1 -1
  39. package/dist/native-pinned-write-windows.js +1 -1
  40. package/dist/native-pinned-write.d.ts +1 -1
  41. package/dist/native-pinned-write.js +3 -6
  42. package/dist/native-policy-parent-windows.d.ts +1 -1
  43. package/dist/native-staged-file.d.ts +2 -2
  44. package/dist/native-staged-file.js +1 -2
  45. package/dist/pinned-mutation-admission.d.ts +1 -1
  46. package/dist/pinned-open.js +6 -1
  47. package/dist/pinned-operation.d.ts +1 -1
  48. package/dist/pinned-operation.js +3 -16
  49. package/dist/pinned-write-mode.js +1 -1
  50. package/dist/pinned-write-staged.js +1 -2
  51. package/dist/pinned-write.d.ts +0 -1
  52. package/dist/pinned-write.js +3 -5
  53. package/dist/private-producer-handoff-sync.d.ts +2 -12
  54. package/dist/private-producer-handoff-sync.js +1 -1
  55. package/dist/private-producer-handoff.d.ts +3 -3
  56. package/dist/private-temp-workspace.js +1 -2
  57. package/dist/publish-copy-stage.d.ts +1 -1
  58. package/dist/publish-file.d.ts +0 -1
  59. package/dist/publish-file.js +1 -2
  60. package/dist/replace-directory.js +1 -2
  61. package/dist/replace-file-rename-policy.d.ts +1 -2
  62. package/dist/replace-file.d.ts +3 -3
  63. package/dist/replace-file.js +1 -1
  64. package/dist/root-context.js +3 -7
  65. package/dist/root-create-input.d.ts +1 -1
  66. package/dist/root-directory-creation.d.ts +1 -1
  67. package/dist/root-directory-creation.js +1 -1
  68. package/dist/root-directory-list.js +3 -7
  69. package/dist/root-entries.js +2 -2
  70. package/dist/root-errors.d.ts +4 -0
  71. package/dist/root-errors.js +5 -3
  72. package/dist/root-impl.d.ts +0 -5
  73. package/dist/root-impl.js +22 -21
  74. package/dist/root-move-noreplace.js +4 -10
  75. package/dist/root-observed-path.js +2 -4
  76. package/dist/root-options.d.ts +1 -1
  77. package/dist/root-path-existing.js +2 -3
  78. package/dist/root-path-stat.js +2 -4
  79. package/dist/root-public-types.d.ts +11 -0
  80. package/dist/root-remove-identity.js +2 -4
  81. package/dist/root-remove.js +2 -4
  82. package/dist/root-write-admission.d.ts +1 -1
  83. package/dist/root-write-admission.js +3 -7
  84. package/dist/root-write-complete-parent.js +2 -3
  85. package/dist/root-write-verification.d.ts +1 -1
  86. package/dist/root.d.ts +2 -6
  87. package/dist/secret-file.js +0 -1
  88. package/dist/secure-temp-repair.js +5 -1
  89. package/dist/sidecar-lock-acquire.js +7 -10
  90. package/dist/sidecar-lock-policy.d.ts +1 -0
  91. package/dist/sidecar-lock-policy.js +7 -0
  92. package/dist/sidecar-lock-stale-admission.js +2 -6
  93. package/dist/sidecar-lock-types.d.ts +22 -16
  94. package/dist/temp-workspace-admission.js +1 -3
  95. package/dist/temp-workspace-child-admission.d.ts +16 -2
  96. package/dist/temp-workspace-child-admission.js +71 -3
  97. package/dist/temp-workspace-descriptor.d.ts +1 -1
  98. package/dist/temp-workspace-descriptor.js +1 -2
  99. package/dist/text-atomic.js +1 -1
  100. package/dist/windows-owner.js +1 -1
  101. package/dist/windows-path-alias.d.ts +1 -0
  102. package/dist/windows-path-alias.js +5 -0
  103. package/docs/advanced.md +3 -1
  104. package/docs/secret-file.md +4 -0
  105. package/docs/sidecar-lock.md +4 -0
  106. package/docs/store.md +6 -0
  107. package/docs/temp.md +8 -1
  108. package/docs/writing.md +5 -0
  109. package/package.json +8 -8
  110. package/dist/archive-native.d.ts +0 -10
  111. package/dist/archive-native.js +0 -73
  112. package/dist/archive-options.d.ts +0 -30
  113. package/dist/archive-policy.d.ts +0 -21
  114. package/dist/archive-policy.js +0 -38
  115. package/dist/archive-tar-inspect.d.ts +0 -6
  116. package/dist/archive-tar-inspect.js +0 -62
  117. package/dist/archive-tar.d.ts +0 -18
  118. package/dist/archive-tar.js +0 -21
  119. package/dist/create-directory.d.ts +0 -18
  120. package/dist/create-directory.js +0 -111
  121. package/dist/create-file-async.d.ts +0 -6
  122. package/dist/create-file-async.js +0 -121
  123. package/dist/create-file.d.ts +0 -7
  124. package/dist/create-file.js +0 -179
  125. package/dist/creation-darwin.d.ts +0 -5
  126. package/dist/creation-darwin.js +0 -70
  127. package/dist/creation-file-state.d.ts +0 -18
  128. package/dist/creation-file-state.js +0 -118
  129. package/dist/creation-path.d.ts +0 -22
  130. package/dist/creation-path.js +0 -83
  131. package/dist/creation-permissions.d.ts +0 -18
  132. package/dist/creation-permissions.js +0 -115
  133. package/dist/file-store-copy-source.d.ts +0 -4
  134. package/dist/file-store-copy-source.js +0 -31
  135. package/dist/file-store-limit.d.ts +0 -1
  136. package/dist/file-store-limit.js +0 -7
  137. package/dist/file-store-path.d.ts +0 -2
  138. package/dist/file-store-path.js +0 -30
  139. package/dist/standalone-publication-path.d.ts +0 -1
  140. package/dist/standalone-publication-path.js +0 -6
  141. package/dist/temp-workspace-identity.d.ts +0 -14
  142. package/dist/temp-workspace-identity.js +0 -41
  143. package/dist/temp-workspace-permissions.d.ts +0 -3
  144. package/dist/temp-workspace-permissions.js +0 -32
  145. /package/dist/{archive-options.js → root-public-types.js} +0 -0
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";
@@ -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
  }
@@ -7,8 +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";
10
+ import { admitStandalonePublicationPath, assertNoWindowsPathAlias, hasWindowsPathAlias, } from "./windows-path-alias.js";
12
11
  export { DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES } from "./json-durable-queue-read.js";
13
12
  function assertSafeQueueEntryId(id) {
14
13
  assertSafePathSegment(id, { label: "queue entry id" });
@@ -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";
@@ -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;
@@ -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");
@@ -1,13 +1,3 @@
1
- import { type BigIntStats } from "node:fs";
2
1
  import { type OwnedFileDescriptorSync } from "./create-owned-file.js";
3
- export declare function handoffCreatedFileSync(params: {
4
- source: OwnedFileDescriptorSync;
5
- sourcePath: string;
6
- targetPath: string;
7
- identity: BigIntStats;
8
- assertSourceParent: () => void;
9
- assertTargetParent: () => void;
10
- assertBeforeMutation?: () => void;
11
- verifyDescriptor?: (fd: number, path: string, links: number) => void;
12
- onPublished?: () => void;
13
- }): OwnedFileDescriptorSync;
2
+ import { type CreatedHandoffParams } from "./private-producer-handoff.js";
3
+ export declare function handoffCreatedFileSync(params: CreatedHandoffParams<OwnedFileDescriptorSync, void>): OwnedFileDescriptorSync;
@@ -1,4 +1,4 @@
1
- import fs, {} from "node:fs";
1
+ import fs from "node:fs";
2
2
  import { ownFileDescriptorSync } from "./create-owned-file.js";
3
3
  import { assertSynchronousCallbackResult } from "./mutation-authority.js";
4
4
  import { assertInitialSource, FileHandoff, inspectLinkedFile, } from "./private-producer-handoff.js";
@@ -18,11 +18,11 @@ type HandoffPaths = HandoffParents & {
18
18
  type ProducerHandoffParams = HandoffPaths & {
19
19
  readWrite: boolean;
20
20
  };
21
- type CreatedHandoffParams = HandoffPaths & {
22
- source: FileHandle;
21
+ export type CreatedHandoffParams<TSource = FileHandle, TVerification = Promise<void>> = HandoffPaths & {
22
+ source: TSource;
23
23
  identity: BigIntStats;
24
24
  assertBeforeMutation?: () => void;
25
- verifyDescriptor?: (fd: number, path: string, links: number) => Promise<void>;
25
+ verifyDescriptor?: (fd: number, path: string, links: number) => TVerification;
26
26
  onPublished?: () => void;
27
27
  };
28
28
  export declare function assertInitialSource(stat: BigIntStats): void;
@@ -12,9 +12,8 @@ import { registerTempPathForExit, } from "./temp-cleanup.js";
12
12
  import { TempWorkspaceCleanupCapability, TempWorkspaceCleanupOwner, throwTempWorkspaceCreationFailure, } from "./temp-workspace-owner.js";
13
13
  import { assertNoWindowsPathAlias, hasWindowsPathAlias, } from "./windows-path-alias.js";
14
14
  import { TempWorkspaceRetainedChild } from "./temp-workspace-descriptor.js";
15
- import { validateInitialTempWorkspaceChild } from "./temp-workspace-child-admission.js";
15
+ import { validateInitialTempWorkspaceChild, validateTempWorkspaceDirMode } from "./temp-workspace-child-admission.js";
16
16
  import { admitTempWorkspaceRoot, admitTempWorkspaceRootSync, } from "./temp-workspace-admission.js";
17
- import { validateTempWorkspaceDirMode } from "./temp-workspace-permissions.js";
18
17
  function resolveTempWorkspaceCleanupSafety(value) {
19
18
  if (value === undefined || value === "compatible")
20
19
  return "compatible";
@@ -1,6 +1,6 @@
1
1
  import { type BigIntStats } from "node:fs";
2
2
  import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
3
- import type { PublishedWriteIdentity } from "./pinned-write.js";
3
+ import type { PublishedWriteIdentity } from "./pinned-write-types.js";
4
4
  export declare function publishCopyStage(params: {
5
5
  temporaryPath: string;
6
6
  targetPath: string;
@@ -2,7 +2,6 @@ import { type BigIntStats, type Stats } from "node:fs";
2
2
  import { type DirectoryReceipt, type DirectorySyncOutcome } from "./directory-durability.js";
3
3
  import { type FileIdentityStat } from "./file-identity.js";
4
4
  import { type PublishFileExclusiveSyncFailurePolicy } from "./publish-file-failure.js";
5
- export type { PublishFileExclusiveCleanup, PublishFileExclusiveDirectorySyncFailure, PublishFileExclusiveFailureDetails, PublishFileExclusiveFailurePhase, PublishFileExclusiveSyncFailurePolicy, } from "./publish-file-failure.js";
6
5
  export type PublishFileExclusiveStrategy = "link-or-copy" | "link-required" | "rename-noreplace";
7
6
  export type PublishFileExclusiveResult = {
8
7
  method: "hardlink" | "exclusive-copy" | "rename-noreplace";
@@ -12,9 +12,8 @@ import { getNativeBinding, requireNativeBinding } from "./native.js";
12
12
  import { captureNativeFdClose } from "./native-binding.js";
13
13
  import { resolveReadOpenFlags } from "./read-open-flags.js";
14
14
  import { directorySyncFailure, publicationFailure, rememberCreatedTarget, } from "./publish-file-failure.js";
15
- import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
15
+ import { admitStandalonePublicationPath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
16
16
  import { getFsSafeTestHooks } from "./test-hooks.js";
17
- import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
18
17
  import { hasErrorCode } from "./file-cleanup.js";
19
18
  import { writeAllToFile } from "./write-file-handle.js";
20
19
  const HARDLINK_FALLBACK_CODES = new Set(["EPERM", "EXDEV", "ENOTSUP", "EOPNOTSUPP", "ENOSYS"]);
@@ -8,9 +8,8 @@ import { classifyNativeRenameFailure, NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH, }
8
8
  import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
9
9
  import { RetainedDirectoryReplacement } from "./retained-directory-replacement.js";
10
10
  import { assertSafePathPrefix } from "./safe-path-segment.js";
11
- import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
11
+ import { admitStandalonePublicationPath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
12
12
  import { serializePathWrite } from "./write-queue.js";
13
- import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
14
13
  function distinctGuards(source, target) {
15
14
  return source === target ? [source] : [source, target];
16
15
  }
@@ -1,5 +1,4 @@
1
- import type { RenameIdentityPolicy } from "./pinned-write.js";
2
- export type { RenameIdentityPolicy };
1
+ import type { RenameIdentityPolicy } from "./pinned-write-types.js";
3
2
  export declare function validateRenameIdentity(policy: RenameIdentityPolicy | undefined): void;
4
3
  export declare function atomicExpectedContentHash(policy: RenameIdentityPolicy | undefined, content: string | Uint8Array): string | undefined;
5
4
  export declare function withAtomicRenameIdentityLock<T>(filePath: string, fn: () => Promise<T>): Promise<T>;
@@ -1,7 +1,7 @@
1
1
  import syncFs from "node:fs";
2
2
  import fs from "node:fs/promises";
3
- import { type ReplaceFileAtomicRestoreCleanup, type ReplaceFileAtomicRestoreFailureDetails, type ReplaceFileCopyFallbackRestorePolicy, type ReplaceFileDestinationHardlinkPolicy } from "./replace-file-copy-fallback.js";
4
- import { type RenameIdentityPolicy } from "./replace-file-rename-policy.js";
3
+ import type { RenameIdentityPolicy } from "./pinned-write-types.js";
4
+ import { type ReplaceFileCopyFallbackRestorePolicy, type ReplaceFileDestinationHardlinkPolicy } from "./replace-file-copy-fallback.js";
5
5
  export type ReplaceFileAtomicFileSystem = {
6
6
  promises: Pick<typeof fs, "mkdir" | "writeFile" | "rename" | "copyFile" | "unlink" | "rm" | "open" | "stat" | "lstat"> & {
7
7
  /** @deprecated Accepted for adapter compatibility but never called. */
@@ -13,7 +13,6 @@ export type ReplaceFileAtomicSyncFileSystem = Pick<typeof syncFs, "mkdirSync" |
13
13
  chmodSync?: typeof syncFs.chmodSync;
14
14
  fchmodSync?: typeof syncFs.fchmodSync;
15
15
  };
16
- export type { RenameIdentityPolicy, ReplaceFileAtomicRestoreCleanup, ReplaceFileAtomicRestoreFailureDetails, ReplaceFileCopyFallbackRestorePolicy, ReplaceFileDestinationHardlinkPolicy, };
17
16
  type ReplaceFileAtomicBaseOptions = {
18
17
  filePath: string;
19
18
  content: string | Uint8Array;
@@ -56,3 +55,4 @@ export type ReplaceFileAtomicResult = {
56
55
  export declare function replaceFileAtomic(options: ReplaceFileAtomicOptions): Promise<ReplaceFileAtomicResult>;
57
56
  export declare function replaceFileAtomicWithDirectorySync(options: ReplaceFileAtomicOptions, syncParent?: (directoryPath: string) => Promise<unknown>): Promise<ReplaceFileAtomicResult>;
58
57
  export declare function replaceFileAtomicSync(options: ReplaceFileAtomicSyncOptions): ReplaceFileAtomicResult;
58
+ export {};
@@ -9,7 +9,7 @@ import { inheritedRegularFileMode } from "./replace-file-mode.js";
9
9
  import { atomicExpectedContentHash, validateRenameIdentity, withAtomicRenameIdentityLock, withAtomicRenameIdentityLockSync, } from "./replace-file-rename-policy.js";
10
10
  import { AsyncAtomicTempOwner, SyncAtomicTempOwner } from "./replace-file-temp-owner.js";
11
11
  import { assertSafePathPrefix } from "./safe-path-segment.js";
12
- import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
12
+ import { admitStandalonePublicationPath } from "./windows-path-alias.js";
13
13
  import { sleep, sleepSync } from "./timing.js";
14
14
  import { serializePathWrite } from "./write-queue.js";
15
15
  import { hasErrorCode, readErrorCode } from "./file-cleanup.js";
@@ -7,7 +7,7 @@ import { sameFileIdentity } from "./file-identity.js";
7
7
  import { assertNoNulPathInput, assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isPathInside, } from "./path.js";
8
8
  import { ROOT_PATH_ALIAS_POLICIES, resolveRootPath } from "./root-path.js";
9
9
  import { admitPathInsideRoot } from "./root-boundary.js";
10
- import { outsideWorkspaceError, rootPathChangedError } from "./root-errors.js";
10
+ import { errorCauseOptions, outsideWorkspaceError, rootPathChangedError } from "./root-errors.js";
11
11
  import { isDriveRelativePath } from "./safe-path-segment.js";
12
12
  import { realpathSync } from "./realpath.js";
13
13
  import { inspectFileIdentity } from "./strict-file-identity.js";
@@ -223,14 +223,10 @@ export async function resolvePathInRoot(root, relativePath, options) {
223
223
  throw error;
224
224
  }
225
225
  if (hasNodeErrorCode(error, "ENAMETOOLONG")) {
226
- throw new FsSafeError("invalid-path", "relative path is too long", {
227
- cause: error instanceof Error ? error : undefined,
228
- });
226
+ throw new FsSafeError("invalid-path", "relative path is too long", errorCauseOptions(error));
229
227
  }
230
228
  const code = options?.aliasErrorCode ?? "outside-workspace";
231
- throw new FsSafeError(code, code === "path-alias" ? "path alias escape blocked" : "file is outside workspace root", {
232
- cause: error instanceof Error ? error : undefined,
233
- });
229
+ throw new FsSafeError(code, code === "path-alias" ? "path alias escape blocked" : "file is outside workspace root", errorCauseOptions(error));
234
230
  }
235
231
  return { rootReal: root.rootReal, rootWithSep: root.rootWithSep, resolved };
236
232
  }
@@ -1,4 +1,4 @@
1
- import type { PinnedWriteInput } from "./pinned-write.js";
1
+ import type { PinnedWriteInput } from "./pinned-write-types.js";
2
2
  import type { RootCreateOptions, RootCreateStreamOptions, RootWriteOptions } from "./root-options.js";
3
3
  declare const exclusiveSidecarCreate: unique symbol;
4
4
  export declare const sidecarExclusiveCreate: Readonly<{
@@ -1,4 +1,4 @@
1
- import type { PinnedWriteMutationAdmission } from "./pinned-write.js";
1
+ import type { PinnedWriteMutationAdmission } from "./pinned-write-types.js";
2
2
  import { type RootContext } from "./root-context.js";
3
3
  /**
4
4
  * Complete the common Root.mkdir case with one exact existing parent. This is
@@ -1,7 +1,7 @@
1
1
  import fsSync, {} from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { assertPrivateDirectory } from "./creation-permissions.js";
4
+ import { assertPrivateDirectory } from "./creation-boundary.js";
5
5
  import { assertAsyncDirectoryGuard, assertDirectoryIdentitySync, assertSyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
6
6
  import { FsSafeError } from "./errors.js";
7
7
  import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
@@ -7,7 +7,7 @@ import { FsSafeError } from "./errors.js";
7
7
  import { inspectNativeDirectoryObservation, isNativeDirectoryObservationGuard, } from "./native-directory-observation.js";
8
8
  import { isNotFoundPathError } from "./path.js";
9
9
  import { assertRootIdentityCurrent } from "./root-context.js";
10
- import { rootPathChangedError } from "./root-errors.js";
10
+ import { errorCauseOptions, rootPathChangedError } from "./root-errors.js";
11
11
  import { admitPathInsideRoot } from "./root-boundary.js";
12
12
  import { createSuppressedError } from "./suppressed-error.js";
13
13
  import { realpathSync } from "./realpath.js";
@@ -50,9 +50,7 @@ export async function createRootDirectoryObservationGuard(root, directory) {
50
50
  function directoryChangedError(error) {
51
51
  if (error instanceof FsSafeError && error.code === "path-mismatch")
52
52
  return error;
53
- return new FsSafeError("path-mismatch", "directory changed during operation", {
54
- cause: error instanceof Error ? error : undefined,
55
- });
53
+ return new FsSafeError("path-mismatch", "directory changed during operation", errorCauseOptions(error));
56
54
  }
57
55
  export async function assertRootDirectoryObservationGuard(root, guard) {
58
56
  if (isNativeDirectoryObservationGuard(guard)) {
@@ -162,9 +160,7 @@ export function assertRootPathObservationReceiptCurrent(root, receipt, finalTarg
162
160
  }
163
161
  function normalizeDirectoryError(error) {
164
162
  if (isNotFoundPathError(error)) {
165
- return new FsSafeError("not-found", "directory not found", {
166
- cause: error instanceof Error ? error : undefined,
167
- });
163
+ return new FsSafeError("not-found", "directory not found", errorCauseOptions(error));
168
164
  }
169
165
  return error;
170
166
  }
@@ -1,5 +1,5 @@
1
1
  import { FsSafeError } from "./errors.js";
2
- import { validatePinnedOperationPayload } from "./pinned-operation.js";
2
+ import { validatePinnedRelativePath } from "./pinned-operation.js";
3
3
  import { resolvePathInRoot } from "./root-context.js";
4
4
  import { openRootDirectoryListing } from "./root-directory-list.js";
5
5
  import { readSymlinkResolution } from "./root-symlink-policy.js";
@@ -13,7 +13,7 @@ export async function* entriesInRoot(root, relativePath, options) {
13
13
  throw new TypeError(`invalid root entries order: ${String(options.order)}`);
14
14
  }
15
15
  options.signal?.throwIfAborted();
16
- validatePinnedOperationPayload({ relativePath });
16
+ validatePinnedRelativePath(relativePath);
17
17
  const resolved = await resolvePathInRoot(root, relativePath, {
18
18
  ...readSymlinkResolution(options.symlinks),
19
19
  resolveCanonical: true,
@@ -10,3 +10,7 @@ export declare function normalizePinnedPathError(error: unknown, details?: FsSaf
10
10
  export declare function normalizeRemoveGuardError(error: unknown, details?: FsSafeErrorDetails): Error;
11
11
  export declare function normalizeRemovePathError(error: unknown, details?: FsSafeErrorDetails): Error;
12
12
  export declare function throwFsSafeReadError(error: unknown, label: string): never;
13
+ /** Existing boundaries retain only Error instances as their cause. */
14
+ export declare function errorCauseOptions(error: unknown): {
15
+ cause: Error | undefined;
16
+ };
@@ -41,9 +41,7 @@ export function normalizePinnedWriteError(error) {
41
41
  const message = code
42
42
  ? `${PINNED_WRITE_ERRNO_MESSAGES.get(code) ?? "filesystem write failed"} (${code})`
43
43
  : "path is not a regular file under root";
44
- return new FsSafeError("invalid-path", message, {
45
- cause: error instanceof Error ? error : undefined,
46
- });
44
+ return new FsSafeError("invalid-path", message, errorCauseOptions(error));
47
45
  }
48
46
  export function normalizePinnedPathError(error, details) {
49
47
  if (error instanceof FsSafeError) {
@@ -88,3 +86,7 @@ export function throwFsSafeReadError(error, label) {
88
86
  }
89
87
  throw error;
90
88
  }
89
+ /** Existing boundaries retain only Error instances as their cause. */
90
+ export function errorCauseOptions(error) {
91
+ return { cause: error instanceof Error ? error : undefined };
92
+ }
@@ -7,13 +7,8 @@ import { type RootContext } from "./root-context.js";
7
7
  import type { DirEntry, PathStat } from "./types.js";
8
8
  import { type RootWalkEntry, type RootWalkOptions } from "./root-walk.js";
9
9
  import { type RootAppendOptions, type RootCopyOptions, type RootCopySource, type RootCreateJsonOptions, type RootCreateOptions, type RootCreateStreamOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions } from "./root-options.js";
10
- export type { HardlinkPolicy, RootAppendOptions, RootCopyOptions, RootCopySource, RootCreateJsonOptions, RootCreateOptions, RootCreateStreamOptions, RootDefaults, RootMkdirOptions, RootMoveOptions, RootOpenOptions, RootOpenWritableOptions, RootOptions, RootReadOptions, RootRemoveOptions, RootWriteJsonOptions, RootWriteOptions, WritableOpenMode, } from "./root-options.js";
11
10
  export { DEFAULT_ROOT_MAX_BYTES } from "./root-options.js";
12
- export type { DenyMutationPolicy } from "./deny-mutations.js";
13
- export type { RenameIdentityPolicy } from "./pinned-write.js";
14
- export type { MutationSymlinkPolicy, SymlinkPolicy } from "./root-symlink-policy.js";
15
11
  export { resolveOpenedFileRealPathForHandle } from "./opened-realpath.js";
16
- export type { ReadResult } from "./read-opened-file.js";
17
12
  export type OpenResult = {
18
13
  handle: FileHandle;
19
14
  containment: ContainmentGuarantee;