@openclaw/fs-safe 0.13.1 → 0.14.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 (39) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/dist/file-store-sync-directory.d.ts.map +1 -1
  3. package/dist/file-store-sync-directory.js +14 -7
  4. package/dist/guest-dispatch-python.js +1 -1
  5. package/dist/native-binding.d.ts +6 -0
  6. package/dist/native-binding.d.ts.map +1 -1
  7. package/dist/native-pinned-write.d.ts.map +1 -1
  8. package/dist/native-pinned-write.js +195 -161
  9. package/dist/native-policy-directory-observation.d.ts +12 -0
  10. package/dist/native-policy-directory-observation.d.ts.map +1 -0
  11. package/dist/native-policy-directory-observation.js +61 -0
  12. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  13. package/dist/pinned-mutation-admission.js +13 -24
  14. package/dist/pinned-mutation-observation.d.ts +7 -1
  15. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  16. package/dist/pinned-mutation-observation.js +87 -5
  17. package/dist/root-directory-creation.d.ts.map +1 -1
  18. package/dist/root-directory-creation.js +5 -4
  19. package/dist/root-impl.d.ts.map +1 -1
  20. package/dist/root-impl.js +2 -1
  21. package/dist/root-path-existing.d.ts.map +1 -1
  22. package/dist/root-path-existing.js +1 -2
  23. package/dist/root-write-compatibility.d.ts +1 -2
  24. package/dist/root-write-compatibility.d.ts.map +1 -1
  25. package/dist/root-write-compatibility.js +17 -68
  26. package/dist/root-write-complete-parent.d.ts.map +1 -1
  27. package/dist/root-write-complete-parent.js +3 -1
  28. package/dist/root-write-lock-binding.d.ts +15 -0
  29. package/dist/root-write-lock-binding.d.ts.map +1 -0
  30. package/dist/root-write-lock-binding.js +162 -0
  31. package/dist/staged-directory.d.ts +5 -2
  32. package/dist/staged-directory.d.ts.map +1 -1
  33. package/dist/staged-directory.js +37 -1
  34. package/dist/temp-workspace-admission.d.ts.map +1 -1
  35. package/dist/temp-workspace-admission.js +28 -4
  36. package/docs/guest.md +1 -1
  37. package/docs/native.md +8 -0
  38. package/docs/writing.md +6 -0
  39. package/package.json +8 -8
package/CHANGELOG.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.14.0 - 2026-09-17
4
+
5
+ ### Highlights
6
+
7
+ - **Safer guest directory selection:** descriptor-relative listings now report regular files as well as directories without following symlinks, so consumers can select files without reopening path-type checks.
8
+
9
+ ### Filesystem boundaries and performance
10
+
11
+ - Report both directory and regular-file kinds from descriptor-relative guest listings. Symlinks and special entries report neither kind.
12
+ - Reduce repeated filesystem observations during policy-bound native parent creation, Windows compatibility writes and metadata reads, synchronous store-directory permission repair, and missing temporary-root creation. Fresh identity, canonical-path, lock-destination, callback, and publication checks remain at mutation boundaries.
13
+
14
+ ### Validation
15
+
16
+ - Register the path-prefix and synchronous `lockRoot` performance workflows on the default branch. Their dispatch stubs fail closed without checking out or executing repository code.
4
17
 
5
18
  ## 0.13.1 - 2026-09-16
6
19
 
@@ -1 +1 @@
1
- {"version":3,"file":"file-store-sync-directory.d.ts","sourceRoot":"","sources":["../src/file-store-sync-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAc/C,KAAK,kBAAkB,GAAG,eAAe,GAAG,OAAO,CAAC;AAOpD,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,CAAC;CACxB,CAAC,CAAC;AAkVH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,kBAAkB,CAAC;CACnC,GAAG,yBAAyB,CA4D5B;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,yBAAyB,GACjC,IAAI,CAsBN"}
1
+ {"version":3,"file":"file-store-sync-directory.d.ts","sourceRoot":"","sources":["../src/file-store-sync-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAc/C,KAAK,kBAAkB,GAAG,eAAe,GAAG,OAAO,CAAC;AAOpD,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,CAAC;CACxB,CAAC,CAAC;AA0VH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,kBAAkB,CAAC;CACnC,GAAG,yBAAyB,CA4D5B;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,yBAAyB,GACjC,IAAI,CAsBN"}
@@ -11,7 +11,9 @@ function changedStoreDirectory(messagePrefix, cause) {
11
11
  return new FsSafeError("outside-workspace", `${messagePrefix} directory escapes root`, { cause });
12
12
  }
13
13
  function storeDirectoryRealPath(dir) {
14
- const realPath = realpathSync(pathForWindowsFilesystem(dir));
14
+ const operationPath = pathForWindowsFilesystem(dir);
15
+ const realPath = process.platform === "win32"
16
+ ? realpathSync(operationPath) : realpathSync.native(operationPath);
15
17
  assertNoWindowsPathAlias(realPath, "filesystem");
16
18
  return realPath;
17
19
  }
@@ -72,7 +74,9 @@ function assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix) {
72
74
  if (!rootReceipt || !targetReceipt) {
73
75
  throw new FsSafeError("helper-failed", "store directory receipt is missing");
74
76
  }
75
- const rootStat = assertRootCurrent(rootReceipt, rootReal, messagePrefix);
77
+ const rootStat = rootReal === undefined
78
+ ? inspectReceiptCurrent(rootReceipt, messagePrefix, true)
79
+ : assertRootCurrent(rootReceipt, rootReal, messagePrefix);
76
80
  if (targetIndex === 0)
77
81
  return rootStat;
78
82
  const parent = receipts[targetIndex - 1];
@@ -87,10 +91,13 @@ function assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix) {
87
91
  return targetStat;
88
92
  }
89
93
  function assertModeTargetCurrent(receipts, rootReal, messagePrefix) {
94
+ const root = receipts[0];
90
95
  const target = receipts.at(-1);
91
- if (!target)
96
+ if (!root || !target)
92
97
  throw new FsSafeError("helper-failed", "store directory receipt is missing");
93
- const stat = assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix);
98
+ // The target's canonical observation shares the exact identity fence. Keep
99
+ // the trailing root canonical check: an ancestor relocation can retain IDs.
100
+ assertAdmittedEdgeCurrent(receipts, undefined, messagePrefix);
94
101
  let targetReal;
95
102
  try {
96
103
  targetReal = storeDirectoryRealPath(target.dir);
@@ -98,10 +105,10 @@ function assertModeTargetCurrent(receipts, rootReal, messagePrefix) {
98
105
  catch (error) {
99
106
  throw changedStoreDirectory(messagePrefix, error);
100
107
  }
101
- if (!isPathInside(rootReal, targetReal))
108
+ if (target === root ? targetReal !== rootReal : !isPathInside(rootReal, targetReal)) {
102
109
  throw changedStoreDirectory(messagePrefix);
103
- assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix);
104
- return stat;
110
+ }
111
+ return assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix);
105
112
  }
106
113
  function assertReceiptChain(receipts, rootReal, messagePrefix) {
107
114
  const rootReceipt = receipts[0];
@@ -59,7 +59,7 @@ export const GUEST_FILESYSTEM_DISPATCH_PYTHON = [
59
59
  " try:",
60
60
  " target_fd = walk_dir(root_fd, sys.argv[3], False)",
61
61
  " with os.scandir(target_fd) as entries:",
62
- " print(json.dumps([{'name': entry.name, 'isDirectory': entry.is_dir(follow_symlinks=False)} for entry in entries]))",
62
+ " print(json.dumps([{'name': entry.name, 'isDirectory': entry.is_dir(follow_symlinks=False), 'isFile': entry.is_file(follow_symlinks=False)} for entry in entries]))",
63
63
  " finally:",
64
64
  " if target_fd is not None:",
65
65
  " os.close(target_fd)",
@@ -20,6 +20,10 @@ export interface NativeDirectoryObservation {
20
20
  ino: bigint;
21
21
  realPath: string;
22
22
  }
23
+ export interface NativeDirectoryFdObservation extends NativeDirectoryObservation {
24
+ mode: bigint;
25
+ nlink: bigint;
26
+ }
23
27
  export interface NativeArchiveEntry {
24
28
  index: number;
25
29
  path: string;
@@ -100,6 +104,8 @@ export interface NativeBinding {
100
104
  };
101
105
  /** Internal: exact directory identity and canonical path from one no-follow handle. */
102
106
  observeDirectory?(path: string): NativeDirectoryObservation;
107
+ /** Internal POSIX-only exact name/descriptor observation; the caller retains its fd. */
108
+ observeDirectoryFd?(fd: number, expectedPath: string): NativeDirectoryFdObservation;
103
109
  /** Linux/Windows byte transfer; callers retain both admitted descriptors until settlement. */
104
110
  copyFileContents?(sourceFd: number, targetFd: number, signal?: AbortSignal): Promise<void>;
105
111
  /** Internal: same private, immutable input ownership as the ZIP buffer reader. */
@@ -1 +1 @@
1
- {"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAIV,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AAqV3B,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsGvH"}
1
+ {"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,KAAK,EAIV,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AA8W3B,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsGvH"}
@@ -58,6 +58,7 @@ import { runPinnedWriteWindows } from "./native-pinned-write-windows.js";
58
58
  import { openNativeParentAdmission, openNativeRootAdmission } from "./native-parent-admission.js";
59
59
  import { assertNativeStaging, writeNativeStage } from "./native-staged-file.js";
60
60
  import { captureNativeFdClose } from "./native-binding.js";
61
+ import { NativePolicyDirectoryMismatch } from "./native-policy-directory-observation.js";
61
62
  import { assertPolicyStagedDirectoryCurrent, assertStagedDirectoryCurrent, describePolicyStagedDirectory, describeStagedDirectory, refreshPolicyStagedDirectoryObservation, } from "./staged-directory.js";
62
63
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
63
64
  import { realpathSync } from "./realpath.js";
@@ -118,8 +119,8 @@ async function describePosixParent(parentFd, pathname) {
118
119
  observation: checkedMutationDirectory(parentPath, directory.realPath, parentPathStat),
119
120
  };
120
121
  }
121
- function describePolicyPosixParent(parentFd, pathname) {
122
- const policyDirectory = describePolicyStagedDirectory(parentFd, pathname);
122
+ function describePolicyPosixParent(binding, parentFd, pathname) {
123
+ const policyDirectory = describePolicyStagedDirectory(parentFd, pathname, binding);
123
124
  return {
124
125
  parentFd,
125
126
  parentPath: policyDirectory.directory.realPath,
@@ -131,11 +132,12 @@ function describePolicyPosixParent(parentFd, pathname) {
131
132
  }
132
133
  function policyParentCaptureDeopt(error) {
133
134
  return error instanceof FsSafeError &&
135
+ !(error instanceof NativePolicyDirectoryMismatch) &&
134
136
  (error.code === "path-mismatch" || error.code === "not-file");
135
137
  }
136
- function tryDescribePolicyPosixParent(parentFd, pathname) {
138
+ function tryDescribePolicyPosixParent(binding, parentFd, pathname) {
137
139
  try {
138
- return describePolicyPosixParent(parentFd, pathname);
140
+ return describePolicyPosixParent(binding, parentFd, pathname);
139
141
  }
140
142
  catch (error) {
141
143
  if (!policyParentCaptureDeopt(error))
@@ -149,183 +151,215 @@ function assertPosixParentCurrent(parent) {
149
151
  : assertStagedDirectoryCurrent(parent.directory);
150
152
  }
151
153
  async function capturePolicyAwarePosixParent(binding, params, rootFd, directoryFlags) {
152
- const closeFd = captureNativeFdClose(binding);
153
- const segments = relativeParentSegments(params.relativeParentPath);
154
- const parentSpelling = segments.length
155
- ? path.join(params.rootPath, ...segments)
156
- : params.rootPath;
157
- const segmentRoute = createPathSegmentRoute(segments);
158
- const initialTarget = prospectiveTargetPath(params.rootPath, segmentRoute, 0, params.basename);
159
- let retainedTargetPath = params.mutationAdmission?.beginParentWalk?.();
160
- if (retainedTargetPath && !sameAbsolutePath(retainedTargetPath, initialTarget)) {
161
- retainedTargetPath = undefined;
162
- }
163
- // Preserve the one-open hot path when the complete parent still exists.
164
- // Policy is attached only after the opened descriptor is associated with its
165
- // current canonical pathname, so a contained redirect cannot retain the old
166
- // preflight authorization.
167
- let completeParentFd;
154
+ const env_1 = { stack: [], error: void 0, hasError: false };
168
155
  try {
169
- completeParentFd = binding.openBeneath(rootFd, params.relativeParentPath, directoryFlags).fd;
170
- }
171
- catch (error) {
172
- if (error?.code !== "ENOENT" || !params.mkdir)
173
- throw error;
174
- }
175
- if (completeParentFd !== undefined) {
176
- const parentFd = completeParentFd;
156
+ const closeFd = captureNativeFdClose(binding);
157
+ const observationDisposers = new Map();
158
+ const disposeObservation = (fd) => {
159
+ const dispose = observationDisposers.get(fd);
160
+ observationDisposers.delete(fd);
161
+ dispose?.();
162
+ };
163
+ const observationScope = __addDisposableResource(env_1, {
164
+ [Symbol.dispose]() { for (const dispose of observationDisposers.values())
165
+ dispose(); },
166
+ }, false);
167
+ const capturePolicyParent = (fd, pathname) => {
168
+ const captured = tryDescribePolicyPosixParent(binding, fd, pathname);
169
+ const dispose = captured?.policyDirectory?.disposeObservation;
170
+ if (dispose)
171
+ observationDisposers.set(fd, dispose);
172
+ return captured;
173
+ };
174
+ const segments = relativeParentSegments(params.relativeParentPath);
175
+ const parentSpelling = segments.length
176
+ ? path.join(params.rootPath, ...segments)
177
+ : params.rootPath;
178
+ const segmentRoute = createPathSegmentRoute(segments);
179
+ const initialTarget = prospectiveTargetPath(params.rootPath, segmentRoute, 0, params.basename);
180
+ let retainedTargetPath = params.mutationAdmission?.beginParentWalk?.();
181
+ if (retainedTargetPath && !sameAbsolutePath(retainedTargetPath, initialTarget)) {
182
+ retainedTargetPath = undefined;
183
+ }
184
+ // Preserve the one-open hot path when the complete parent still exists.
185
+ // Policy is attached only after the opened descriptor is associated with its
186
+ // current canonical pathname, so a contained redirect cannot retain the old
187
+ // preflight authorization.
188
+ let completeParentFd;
177
189
  try {
178
- let admitted = retainedTargetPath
179
- ? tryDescribePolicyPosixParent(parentFd, parentSpelling)
180
- : undefined;
181
- if (!admitted) {
182
- retainedTargetPath = undefined;
183
- admitted = await describePosixParent(parentFd, parentSpelling);
184
- }
185
- const targetPath = path.join(admitted.parentPath, params.basename);
186
- await authorizePinnedMutation(params, {
187
- targetPath,
188
- mutationPath: targetPath,
189
- phase: "parent",
190
- });
191
- assertPosixParentCurrent(admitted);
192
- return admitted;
190
+ completeParentFd = binding.openBeneath(rootFd, params.relativeParentPath, directoryFlags).fd;
193
191
  }
194
192
  catch (error) {
195
- closeFd(parentFd);
196
- throw error;
193
+ if (error?.code !== "ENOENT" || !params.mkdir)
194
+ throw error;
197
195
  }
198
- }
199
- // The canonical preflight spelling has no intentional symlinks in its
200
- // existing prefix. Walk missing-parent cases one direct child at a time so
201
- // every existing/opened object is authorized and every mkdir is authorized
202
- // before dispatch. O_NOFOLLOW makes a concurrently introduced link fail
203
- // before the next component can be created through it.
204
- const secureDirectoryFlags = directoryFlags | (fsSync.constants.O_NOFOLLOW ?? 0);
205
- let currentFd = rootFd;
206
- let currentOwnedFd;
207
- let currentPath = params.rootPath;
208
- let current = retainedTargetPath
209
- ? tryDescribePolicyPosixParent(rootFd, currentPath)
210
- : undefined;
211
- if (!current) {
212
- retainedTargetPath = undefined;
213
- current = await describePosixParent(rootFd, currentPath);
214
- }
215
- try {
216
- await authorizePinnedMutation(params, {
217
- targetPath: retainedTargetPath ?? initialTarget,
218
- mutationPath: retainedTargetPath ?? initialTarget,
219
- phase: "parent",
220
- });
221
- assertPosixParentCurrent(current);
222
- for (let index = 0; index < segments.length; index += 1) {
223
- const segment = segments[index];
224
- const childPath = path.join(currentPath, segment);
225
- let childFd;
226
- let createdByMkdir = false;
227
- let createReceipt;
196
+ if (completeParentFd !== undefined) {
197
+ const parentFd = completeParentFd;
228
198
  try {
229
- childFd = binding.openBeneath(currentFd, segment, secureDirectoryFlags).fd;
230
- }
231
- catch (openError) {
232
- if (!isNotFoundPathError(openError)) {
233
- throw normalizePolicyParentOpenError(openError, params);
199
+ let admitted = retainedTargetPath
200
+ ? capturePolicyParent(parentFd, parentSpelling)
201
+ : undefined;
202
+ if (!admitted) {
203
+ retainedTargetPath = undefined;
204
+ admitted = await describePosixParent(parentFd, parentSpelling);
234
205
  }
235
- const targetPath = retainedTargetPath ?? prospectiveTargetPath(currentPath, segmentRoute, index, params.basename);
236
- const request = Object.freeze({
206
+ const targetPath = path.join(admitted.parentPath, params.basename);
207
+ await authorizePinnedMutation(params, {
237
208
  targetPath,
238
- mutationPath: childPath,
239
- phase: "parent-create",
209
+ mutationPath: targetPath,
210
+ phase: "parent",
240
211
  });
241
- createReceipt = params.mutationAdmission?.tryAuthorizeAtParent?.(request, current.observation);
242
- if (!createReceipt) {
243
- createReceipt = await authorizePinnedMutation(params, request);
244
- assertPosixParentCurrent(current);
245
- }
246
- params.assertBeforeMutation?.();
247
- // Do not carry pathname freshness across the live authority callback.
248
- assertPosixParentCurrent(current);
249
- createdByMkdir = mkdirPolicyChild(binding, currentFd, segment, 0o777);
212
+ assertPosixParentCurrent(admitted);
213
+ return admitted;
214
+ }
215
+ catch (error) {
216
+ disposeObservation(parentFd);
217
+ closeFd(parentFd);
218
+ throw error;
219
+ }
220
+ }
221
+ // The canonical preflight spelling has no intentional symlinks in its
222
+ // existing prefix. Walk missing-parent cases one direct child at a time so
223
+ // every existing/opened object is authorized and every mkdir is authorized
224
+ // before dispatch. O_NOFOLLOW makes a concurrently introduced link fail
225
+ // before the next component can be created through it.
226
+ const secureDirectoryFlags = directoryFlags | (fsSync.constants.O_NOFOLLOW ?? 0);
227
+ let currentFd = rootFd;
228
+ let currentOwnedFd;
229
+ let currentPath = params.rootPath;
230
+ let current = retainedTargetPath
231
+ ? capturePolicyParent(rootFd, currentPath)
232
+ : undefined;
233
+ if (!current) {
234
+ retainedTargetPath = undefined;
235
+ current = await describePosixParent(rootFd, currentPath);
236
+ }
237
+ try {
238
+ await authorizePinnedMutation(params, {
239
+ targetPath: retainedTargetPath ?? initialTarget,
240
+ mutationPath: retainedTargetPath ?? initialTarget,
241
+ phase: "parent",
242
+ });
243
+ assertPosixParentCurrent(current);
244
+ for (let index = 0; index < segments.length; index += 1) {
245
+ const segment = segments[index];
246
+ const childPath = path.join(currentPath, segment);
247
+ let childFd;
248
+ let createdByMkdir = false;
249
+ let createReceipt;
250
250
  try {
251
251
  childFd = binding.openBeneath(currentFd, segment, secureDirectoryFlags).fd;
252
252
  }
253
- catch (createdOpenError) {
254
- throw normalizePolicyParentOpenError(createdOpenError, params);
255
- }
256
- }
257
- let child;
258
- try {
259
- const acceleratedChild = retainedTargetPath
260
- ? tryDescribePolicyPosixParent(childFd, childPath)
261
- : undefined;
262
- if (acceleratedChild) {
263
- child = acceleratedChild;
264
- }
265
- else {
266
- retainedTargetPath = undefined;
267
- child = await describePosixParent(childFd, childPath);
268
- }
269
- if (!sameAbsolutePath(child.parentPath, childPath))
270
- retainedTargetPath = undefined;
271
- let childAuthorization;
272
- if (createdByMkdir && createReceipt && params.mutationAdmission?.advanceCreatedDirectory) {
273
- let evidence;
253
+ catch (openError) {
254
+ if (!isNotFoundPathError(openError)) {
255
+ throw normalizePolicyParentOpenError(openError, params);
256
+ }
257
+ const targetPath = retainedTargetPath ?? prospectiveTargetPath(currentPath, segmentRoute, index, params.basename);
258
+ const request = Object.freeze({
259
+ targetPath,
260
+ mutationPath: childPath,
261
+ phase: "parent-create",
262
+ });
263
+ createReceipt = params.mutationAdmission?.tryAuthorizeAtParent?.(request, current.observation);
264
+ if (!createReceipt) {
265
+ createReceipt = await authorizePinnedMutation(params, request);
266
+ assertPosixParentCurrent(current);
267
+ }
268
+ params.assertBeforeMutation?.();
269
+ // Do not carry pathname freshness across the live authority callback.
270
+ assertPosixParentCurrent(current);
271
+ createdByMkdir = mkdirPolicyChild(binding, currentFd, segment, 0o777);
274
272
  try {
275
- const parent = current.policyDirectory
276
- ? refreshPolicyStagedDirectoryObservation(current.policyDirectory)
277
- : checkedMutationDirectory(currentPath, current.directory.realPath, assertStagedDirectoryCurrent(current.directory));
278
- evidence = Object.freeze({
279
- admission: createReceipt,
280
- parent,
281
- child: child.observation,
282
- });
273
+ childFd = binding.openBeneath(currentFd, segment, secureDirectoryFlags).fd;
283
274
  }
284
- catch {
285
- // Leave failed evidence to the full ordered admission below.
275
+ catch (createdOpenError) {
276
+ throw normalizePolicyParentOpenError(createdOpenError, params);
286
277
  }
287
- if (evidence) {
288
- childAuthorization = params.mutationAdmission.advanceCreatedDirectory(evidence);
278
+ }
279
+ let child;
280
+ try {
281
+ const acceleratedChild = retainedTargetPath
282
+ ? capturePolicyParent(childFd, childPath)
283
+ : undefined;
284
+ if (acceleratedChild) {
285
+ child = acceleratedChild;
286
+ }
287
+ else {
288
+ retainedTargetPath = undefined;
289
+ child = await describePosixParent(childFd, childPath);
290
+ }
291
+ if (!sameAbsolutePath(child.parentPath, childPath))
292
+ retainedTargetPath = undefined;
293
+ let childAuthorization;
294
+ if (createdByMkdir && createReceipt && params.mutationAdmission?.advanceCreatedDirectory) {
295
+ let evidence;
296
+ try {
297
+ const parent = current.policyDirectory
298
+ ? refreshPolicyStagedDirectoryObservation(current.policyDirectory)
299
+ : checkedMutationDirectory(currentPath, current.directory.realPath, assertStagedDirectoryCurrent(current.directory));
300
+ evidence = Object.freeze({
301
+ admission: createReceipt,
302
+ parent,
303
+ child: child.observation,
304
+ });
305
+ }
306
+ catch {
307
+ // Leave failed evidence to the full ordered admission below.
308
+ }
309
+ if (evidence) {
310
+ childAuthorization = params.mutationAdmission.advanceCreatedDirectory(evidence);
311
+ }
312
+ }
313
+ const targetPath = retainedTargetPath ?? prospectiveTargetPath(child.parentPath, segmentRoute, index + 1, params.basename);
314
+ const request = Object.freeze({
315
+ targetPath,
316
+ mutationPath: targetPath,
317
+ phase: "parent",
318
+ });
319
+ childAuthorization ??= params.mutationAdmission?.tryAuthorizeAtParent?.(request, child.observation);
320
+ if (!childAuthorization) {
321
+ await authorizePinnedMutation(params, request);
322
+ assertPosixParentCurrent(child);
289
323
  }
290
324
  }
291
- const targetPath = retainedTargetPath ?? prospectiveTargetPath(child.parentPath, segmentRoute, index + 1, params.basename);
292
- const request = Object.freeze({
293
- targetPath,
294
- mutationPath: targetPath,
295
- phase: "parent",
296
- });
297
- childAuthorization ??= params.mutationAdmission?.tryAuthorizeAtParent?.(request, child.observation);
298
- if (!childAuthorization) {
299
- await authorizePinnedMutation(params, request);
300
- assertPosixParentCurrent(child);
325
+ catch (error) {
326
+ disposeObservation(childFd);
327
+ closeFd(childFd);
328
+ throw error;
329
+ }
330
+ const previousOwnedFd = currentOwnedFd;
331
+ disposeObservation(current.parentFd);
332
+ currentOwnedFd = child.parentFd;
333
+ currentFd = child.parentFd;
334
+ currentPath = child.parentPath;
335
+ current = child;
336
+ if (previousOwnedFd !== undefined)
337
+ closeFd(previousOwnedFd);
338
+ if (index === segments.length - 1) {
339
+ currentOwnedFd = undefined;
340
+ return child;
301
341
  }
302
342
  }
303
- catch (error) {
304
- closeFd(childFd);
305
- throw error;
306
- }
307
- const previousOwnedFd = currentOwnedFd;
308
- currentOwnedFd = child.parentFd;
309
- currentFd = child.parentFd;
310
- currentPath = child.parentPath;
311
- current = child;
312
- if (previousOwnedFd !== undefined)
313
- closeFd(previousOwnedFd);
314
- if (index === segments.length - 1) {
315
- currentOwnedFd = undefined;
316
- return child;
343
+ // Empty relative parents are handled by the complete-parent fast path.
344
+ throw new FsSafeError("path-mismatch", "native write parent admission did not complete");
345
+ }
346
+ finally {
347
+ if (currentOwnedFd !== undefined) {
348
+ disposeObservation(currentOwnedFd);
349
+ closeFd(currentOwnedFd);
317
350
  }
318
351
  }
319
- // Empty relative parents are handled by the complete-parent fast path.
320
- throw new FsSafeError("path-mismatch", "native write parent admission did not complete");
352
+ }
353
+ catch (e_1) {
354
+ env_1.error = e_1;
355
+ env_1.hasError = true;
321
356
  }
322
357
  finally {
323
- if (currentOwnedFd !== undefined)
324
- closeFd(currentOwnedFd);
358
+ __disposeResources(env_1);
325
359
  }
326
360
  }
327
361
  export async function runPinnedWriteNative(binding, params) {
328
- const env_1 = { stack: [], error: void 0, hasError: false };
362
+ const env_2 = { stack: [], error: void 0, hasError: false };
329
363
  try {
330
364
  const closeFd = captureNativeFdClose(binding);
331
365
  const windows = process.platform === "win32";
@@ -339,12 +373,12 @@ export async function runPinnedWriteNative(binding, params) {
339
373
  operation: "native write",
340
374
  });
341
375
  const root = rootAdmission.root;
342
- const posixRoot = __addDisposableResource(env_1, windows ? undefined : root, true);
376
+ const posixRoot = __addDisposableResource(env_2, windows ? undefined : root, true);
343
377
  let parentFd;
344
378
  let windowsOwnsDirectories = false;
345
379
  // Until stage construction takes ownership, even admission failures must close
346
380
  // the raw POSIX parent. Disposal preserves both admission and close failures.
347
- const parentGuard = __addDisposableResource(env_1, {
381
+ const parentGuard = __addDisposableResource(env_2, {
348
382
  [Symbol.dispose]() {
349
383
  if (!windows && parentFd !== undefined) {
350
384
  closeFd(parentFd);
@@ -423,12 +457,12 @@ export async function runPinnedWriteNative(binding, params) {
423
457
  }
424
458
  }
425
459
  }
426
- catch (e_1) {
427
- env_1.error = e_1;
428
- env_1.hasError = true;
460
+ catch (e_2) {
461
+ env_2.error = e_2;
462
+ env_2.hasError = true;
429
463
  }
430
464
  finally {
431
- const result_1 = __disposeResources(env_1);
465
+ const result_1 = __disposeResources(env_2);
432
466
  if (result_1)
433
467
  await result_1;
434
468
  }
@@ -0,0 +1,12 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import type { NativeBinding } from "./native-binding.js";
3
+ import type { MutationDirectoryObserver } from "./pinned-mutation-observation.js";
4
+ export declare class NativePolicyDirectoryMismatch extends FsSafeError {
5
+ constructor(cause?: Error);
6
+ }
7
+ export type NativePolicyDirectoryObserver = MutationDirectoryObserver & {
8
+ dispose(): void;
9
+ };
10
+ /** Fresh name/metadata/canonical facts from a caller-owned POSIX descriptor. */
11
+ export declare function nativePolicyDirectoryObserver(binding: NativeBinding | undefined, fd: number, pathname: string): NativePolicyDirectoryObserver | undefined;
12
+ //# sourceMappingURL=native-policy-directory-observation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-policy-directory-observation.d.ts","sourceRoot":"","sources":["../src/native-policy-directory-observation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAElF,qBAAa,6BAA8B,SAAQ,WAAW;IAC5D,YAAY,KAAK,CAAC,EAAE,KAAK,EAExB;CACF;AAED,MAAM,MAAM,6BAA6B,GAAG,yBAAyB,GAAG;IAAE,OAAO,IAAI,IAAI,CAAA;CAAE,CAAC;AAE5F,gFAAgF;AAChF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACf,6BAA6B,GAAG,SAAS,CA2C3C"}