@openclaw/signal 2026.9.6 → 2026.9.8

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 (279) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/.setup/{accounts-BV6tDOTc.mjs → accounts-DMtj2E-4.mjs} +6 -35
  3. package/dist/.setup/api-DEB0Obj8.mjs +16 -0
  4. package/dist/.setup/{api-CGh4eWR8.mjs → api-DmCb6e7E.mjs} +10 -10
  5. package/dist/.setup/{approval-auth-BBI9BTRm.mjs → approval-auth-CszS7gwf.mjs} +7 -13
  6. package/dist/.setup/{approval-handler.runtime-C3vN5OWd.mjs → approval-handler.runtime-D9ofZcZW.mjs} +10 -29
  7. package/dist/.setup/{approval-reactions-DDQvPP_z.mjs → approval-reactions-BKkjIAoi.mjs} +25 -36
  8. package/dist/.setup/{channel-DhzTcFsK.mjs → channel-DrUxv5cf.mjs} +567 -136
  9. package/dist/.setup/{client-BseAt7F7.mjs → client-AXcDteBZ.mjs} +63 -152
  10. package/dist/.setup/client-adapter-c_O1ouxZ.mjs +30 -0
  11. package/dist/.setup/{config-schema-aQwwMGUj.mjs → config-schema-DvxZ18SD.mjs} +3 -13
  12. package/dist/.setup/{doctor-contract-api-Rbq_sRTI.mjs → doctor-contract-api-Db9V9gsY.mjs} +212 -91
  13. package/dist/.setup/{identity-YXPmgFMu.mjs → identity-B3JyPqGO.mjs} +2 -6
  14. package/dist/.setup/{install-signal-cli-BDbzFcU2.mjs → install-signal-cli-ChvjzM6X.mjs} +0 -4
  15. package/dist/.setup/legacy-transport-QIpaMNST.mjs +18 -0
  16. package/dist/.setup/{monitor-Dnz6Ygz5.mjs → monitor-BfwQr78f.mjs} +95 -124
  17. package/dist/.setup/{probe-DXT2eLnw.mjs → probe-BYLzra6D.mjs} +9 -9
  18. package/dist/.setup/{reaction-runtime-api-BnQGHcrB.mjs → reaction-runtime-api-DDi5x2wC.mjs} +3 -17
  19. package/dist/.setup/{question-reactions-C8cSEjVf.mjs → reaction-targets-CEdyjtrC.mjs} +11 -8
  20. package/dist/.setup/{rpc-context-CwwADZ1b.mjs → rpc-context-D26RxFIm.mjs} +1 -1
  21. package/dist/.setup/runtime-8sxUF217.mjs +8 -0
  22. package/dist/.setup/{send-B78M_eFp.mjs → send-DEjrbpIC.mjs} +25 -55
  23. package/dist/.setup/{channel.runtime-CG79z_Vy.mjs → setup-surface-Drt-4t3R.mjs} +4 -6
  24. package/dist/.setup/transport-detection-BVUDE42e.mjs +25 -0
  25. package/dist/.setup/{transport-policy-5VSnj5ma.mjs → transport-policy-B6C_LogW.mjs} +19 -1
  26. package/dist/.setup/transport-probes.runtime-DwXEHnXr.mjs +2 -0
  27. package/dist/api.js +10 -10
  28. package/dist/channel-config-api.js +1 -1
  29. package/dist/channel-plugin-api.js +1 -1
  30. package/dist/contract-api.js +2 -2
  31. package/dist/doctor-contract-api.js +1 -1
  32. package/dist/reaction-runtime-api.js +1 -1
  33. package/dist/runtime-api.js +2 -19
  34. package/dist/setup-entry.js +1 -1
  35. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +102 -0
  36. package/node_modules/@openclaw/fs-safe/README.md +24 -6
  37. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +6 -1
  38. package/node_modules/@openclaw/fs-safe/dist/advanced.js +3 -1
  39. package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
  40. package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +1 -2
  41. package/node_modules/@openclaw/fs-safe/dist/archive-plan.d.ts +2 -7
  42. package/node_modules/@openclaw/fs-safe/dist/archive-read.js +9 -18
  43. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -2
  44. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +3 -3
  45. package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.js +4 -0
  46. package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.d.ts +11 -11
  47. package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.js +3 -35
  48. package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.d.ts +2 -2
  49. package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.js +2 -12
  50. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.d.ts +7 -3
  51. package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +13 -10
  52. package/node_modules/@openclaw/fs-safe/dist/archive-zip-manifest.js +3 -1
  53. package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.d.ts +2 -3
  54. package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.js +16 -8
  55. package/node_modules/@openclaw/fs-safe/dist/archive.d.ts +6 -3
  56. package/node_modules/@openclaw/fs-safe/dist/archive.js +22 -19
  57. package/node_modules/@openclaw/fs-safe/dist/atomic.d.ts +1 -1
  58. package/node_modules/@openclaw/fs-safe/dist/copy-publication.d.ts +1 -3
  59. package/node_modules/@openclaw/fs-safe/dist/copy-publication.js +2 -6
  60. package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.d.ts +2 -0
  61. package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.js +71 -0
  62. package/node_modules/@openclaw/fs-safe/dist/deny-mutations.js +4 -3
  63. package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +0 -1
  64. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +0 -3
  65. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +20 -2
  66. package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +77 -1
  67. package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +5 -7
  68. package/node_modules/@openclaw/fs-safe/dist/effective-uid.js +1 -4
  69. package/node_modules/@openclaw/fs-safe/dist/errors.d.ts +3 -1
  70. package/node_modules/@openclaw/fs-safe/dist/errors.js +3 -2
  71. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +10 -2
  72. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-acquisition.js +1 -1
  73. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +5 -1
  74. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.d.ts +4 -11
  75. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.js +1 -4
  76. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.d.ts +3 -6
  77. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +1 -1
  78. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-options.d.ts +2 -10
  79. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.d.ts +2 -4
  80. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +2 -2
  81. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.d.ts +3 -7
  82. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +7 -10
  83. package/node_modules/@openclaw/fs-safe/dist/file-store-prune.js +7 -6
  84. package/node_modules/@openclaw/fs-safe/dist/file-store.d.ts +4 -7
  85. package/node_modules/@openclaw/fs-safe/dist/file-store.js +19 -21
  86. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +23 -31
  87. package/node_modules/@openclaw/fs-safe/dist/guest.js +21 -12
  88. package/node_modules/@openclaw/fs-safe/dist/json-document-store.d.ts +4 -9
  89. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.d.ts +1 -5
  90. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.d.ts +0 -1
  91. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.js +2 -7
  92. package/node_modules/@openclaw/fs-safe/dist/local-file-access.js +2 -5
  93. package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.d.ts +2 -5
  94. package/node_modules/@openclaw/fs-safe/dist/local-roots.d.ts +2 -7
  95. package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.js +4 -4
  96. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +5 -0
  97. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +51 -14
  98. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +8 -2
  99. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.d.ts +13 -0
  100. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +303 -0
  101. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.d.ts +1 -0
  102. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.js +54 -0
  103. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.d.ts +5 -0
  104. package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +64 -0
  105. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
  106. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +3 -0
  107. package/node_modules/@openclaw/fs-safe/dist/path.js +17 -1
  108. package/node_modules/@openclaw/fs-safe/dist/permission-exec.d.ts +6 -0
  109. package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +3 -6
  110. package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +3 -1
  111. package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
  112. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +3 -6
  113. package/node_modules/@openclaw/fs-safe/dist/permissions.d.ts +3 -10
  114. package/node_modules/@openclaw/fs-safe/dist/permissions.js +0 -1
  115. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.d.ts +0 -1
  116. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +4 -2
  117. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-shared-route.d.ts +2 -8
  118. package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +1 -1
  119. package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +2 -3
  120. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +4 -0
  121. package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -5
  122. package/node_modules/@openclaw/fs-safe/dist/regular-file.js +3 -3
  123. package/node_modules/@openclaw/fs-safe/dist/replace-directory.js +5 -17
  124. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +4 -0
  125. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +36 -0
  126. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +4 -10
  127. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +66 -38
  128. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +7 -11
  129. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +10 -2
  130. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +21 -0
  131. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +123 -0
  132. package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.d.ts +26 -0
  133. package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.js +47 -0
  134. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +2 -2
  135. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +16 -4
  136. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +55 -0
  137. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.js +1 -0
  138. package/node_modules/@openclaw/fs-safe/dist/replace-file.d.ts +3 -55
  139. package/node_modules/@openclaw/fs-safe/dist/replace-file.js +31 -11
  140. package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.d.ts +1 -0
  141. package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.js +1 -1
  142. package/node_modules/@openclaw/fs-safe/dist/retained-file-types.d.ts +50 -0
  143. package/node_modules/@openclaw/fs-safe/dist/retained-file-types.js +1 -0
  144. package/node_modules/@openclaw/fs-safe/dist/retained-file.d.ts +3 -0
  145. package/node_modules/@openclaw/fs-safe/dist/retained-file.js +121 -0
  146. package/node_modules/@openclaw/fs-safe/dist/root-context.js +3 -2
  147. package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +9 -0
  148. package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +28 -0
  149. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +9 -1
  150. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +88 -37
  151. package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.d.ts +1 -1
  152. package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +1 -6
  153. package/node_modules/@openclaw/fs-safe/dist/root-file.js +2 -5
  154. package/node_modules/@openclaw/fs-safe/dist/root-handle-context.d.ts +4 -0
  155. package/node_modules/@openclaw/fs-safe/dist/root-handle-context.js +12 -0
  156. package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +3 -3
  157. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +8 -5
  158. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +2 -7
  159. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +3 -3
  160. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +0 -1
  161. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -3
  162. package/node_modules/@openclaw/fs-safe/dist/root-path-observation.d.ts +4 -11
  163. package/node_modules/@openclaw/fs-safe/dist/root-path.js +7 -10
  164. package/node_modules/@openclaw/fs-safe/dist/root-paths.d.ts +2 -6
  165. package/node_modules/@openclaw/fs-safe/dist/root-remove-identity.d.ts +1 -3
  166. package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +19 -12
  167. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +56 -20
  168. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +1 -6
  169. package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.d.ts +2 -0
  170. package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.js +1 -1
  171. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +1 -0
  172. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +8 -2
  173. package/node_modules/@openclaw/fs-safe/dist/secret-file.d.ts +6 -2
  174. package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -16
  175. package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.js +1 -1
  176. package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +5 -14
  177. package/node_modules/@openclaw/fs-safe/dist/secure-file.js +3 -2
  178. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +2 -7
  179. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +4 -4
  180. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission-parser.d.ts +1 -2
  181. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.d.ts +2 -8
  182. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.d.ts +2 -7
  183. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +2 -2
  184. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +11 -12
  185. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-stale-admission.d.ts +1 -5
  186. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +9 -7
  187. package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.d.ts +39 -0
  188. package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.js +1 -0
  189. package/node_modules/@openclaw/fs-safe/dist/stat-observation.js +3 -10
  190. package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.d.ts +2 -0
  191. package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.js +6 -6
  192. package/node_modules/@openclaw/fs-safe/dist/symlink-parents.js +58 -7
  193. package/node_modules/@openclaw/fs-safe/dist/temp-target.js +8 -10
  194. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +28 -33
  195. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
  196. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +16 -17
  197. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +1 -2
  198. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +4 -9
  199. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +8 -0
  200. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +52 -0
  201. package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +5 -1
  202. package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +2 -1
  203. package/node_modules/@openclaw/fs-safe/dist/text-atomic.js +2 -0
  204. package/node_modules/@openclaw/fs-safe/dist/trash.js +27 -1
  205. package/node_modules/@openclaw/fs-safe/dist/walk.d.ts +2 -5
  206. package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +6 -0
  207. package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +88 -0
  208. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +9 -0
  209. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +93 -0
  210. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +36 -0
  211. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +73 -0
  212. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +28 -0
  213. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +300 -0
  214. package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +8 -0
  215. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +32 -0
  216. package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +60 -0
  217. package/node_modules/@openclaw/fs-safe/dist/watch-types.js +1 -0
  218. package/node_modules/@openclaw/fs-safe/dist/watch.d.ts +5 -0
  219. package/node_modules/@openclaw/fs-safe/dist/watch.js +530 -0
  220. package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +3 -7
  221. package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +0 -1
  222. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +6 -6
  223. package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +78 -3
  224. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.d.ts +8 -0
  225. package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +66 -15
  226. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +4 -0
  227. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +6 -2
  228. package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -2
  229. package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
  230. package/node_modules/@openclaw/fs-safe/docs/atomic.md +93 -3
  231. package/node_modules/@openclaw/fs-safe/docs/contributing.md +102 -4
  232. package/node_modules/@openclaw/fs-safe/docs/copy.md +2 -0
  233. package/node_modules/@openclaw/fs-safe/docs/durability.md +7 -0
  234. package/node_modules/@openclaw/fs-safe/docs/file-store.md +5 -0
  235. package/node_modules/@openclaw/fs-safe/docs/guest.md +7 -1
  236. package/node_modules/@openclaw/fs-safe/docs/index.md +1 -0
  237. package/node_modules/@openclaw/fs-safe/docs/install.md +30 -0
  238. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +20 -4
  239. package/node_modules/@openclaw/fs-safe/docs/native.md +63 -1
  240. package/node_modules/@openclaw/fs-safe/docs/permissions.md +66 -0
  241. package/node_modules/@openclaw/fs-safe/docs/public-api.md +30 -3
  242. package/node_modules/@openclaw/fs-safe/docs/retained-file.md +115 -0
  243. package/node_modules/@openclaw/fs-safe/docs/root.md +35 -9
  244. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +19 -0
  245. package/node_modules/@openclaw/fs-safe/docs/security-model.md +4 -1
  246. package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +5 -1
  247. package/node_modules/@openclaw/fs-safe/docs/staged-symlink.md +124 -0
  248. package/node_modules/@openclaw/fs-safe/docs/store.md +3 -1
  249. package/node_modules/@openclaw/fs-safe/docs/temp.md +24 -4
  250. package/node_modules/@openclaw/fs-safe/docs/testing.md +210 -0
  251. package/node_modules/@openclaw/fs-safe/docs/types.md +6 -0
  252. package/node_modules/@openclaw/fs-safe/docs/walk.md +34 -1
  253. package/node_modules/@openclaw/fs-safe/docs/watch.md +251 -0
  254. package/node_modules/@openclaw/fs-safe/docs/writing.md +21 -0
  255. package/node_modules/@openclaw/fs-safe/package.json +13 -9
  256. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  257. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  258. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  259. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  260. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  261. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  262. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  263. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  264. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  265. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  266. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  267. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  268. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  269. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  270. package/package.json +5 -5
  271. package/dist/.setup/api-B9-B1L7Q.mjs +0 -17
  272. package/dist/.setup/client-adapter-dvFa47YL.mjs +0 -63
  273. package/dist/.setup/rolldown-runtime-8H4AJuhK.mjs +0 -14
  274. package/dist/.setup/send.runtime-CLnCWvrM.mjs +0 -2
  275. package/dist/.setup/transport-detection-CoiRl7Gx.mjs +0 -576
  276. package/dist/.setup/transport-detection.runtime-Tlhuv3T_.mjs +0 -2
  277. package/dist/.setup/transport-probes.runtime-WprAyBbL.mjs +0 -2
  278. package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.d.ts +0 -20
  279. package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.js +0 -78
@@ -1,42 +1,10 @@
1
- import { resolveArchiveEntryMode, } from "./archive-plan.js";
2
- const admittedMetadata = new WeakMap();
3
- /** Internal: register only after the complete decoder/admission association. */
4
- export function registerAdmittedZipEntry(entry, physical) {
1
+ /** Internal: create only after the complete decoder/admission association. */
2
+ export function createAdmittedZipEntry(entry, name, physical) {
5
3
  const mode = physical.creatorSystem === 3 ? physical.externalAttributes >>> 16 : undefined;
6
- admittedMetadata.set(entry, { kind: physical.kind, mode, size: physical.size });
7
4
  entry.dir = physical.kind === "directory";
8
5
  // Previously unsupported non-UNIX symlinks must also be recognizable to
9
6
  // public preflight consumers using JSZip's conventional type inspection.
10
7
  entry.unixPermissions = mode ?? (physical.kind === "symlink" ? 0o120000 : null);
11
8
  entry.dosPermissions = physical.creatorSystem === 0 ? physical.externalAttributes & 0x3f : null;
12
- }
13
- export function zipEntryIntegrityMetadata(entry) {
14
- const data = entry._data;
15
- if (!data || "then" in data)
16
- return undefined;
17
- return data;
18
- }
19
- const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
20
- const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
21
- export function isZipSymlinkEntry(entry) {
22
- return zipEntryKind(entry) === "symlink";
23
- }
24
- export function zipEntryKind(entry) {
25
- const metadata = admittedMetadata.get(entry);
26
- if (metadata)
27
- return metadata.kind;
28
- return typeof entry.unixPermissions === "number" &&
29
- (entry.unixPermissions & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE
30
- ? "symlink" : entry.dir ? "directory" : "file";
31
- }
32
- export function zipEntryMode(entry, policy) {
33
- const metadata = admittedMetadata.get(entry);
34
- return resolveArchiveEntryMode({
35
- kind: entry.dir ? "directory" : "file",
36
- archivedMode: metadata ? metadata.mode : entry.unixPermissions,
37
- policy,
38
- });
39
- }
40
- export function zipEntryDeclaredSize(entry) {
41
- return admittedMetadata.get(entry)?.size ?? Math.max(0, Math.floor(zipEntryIntegrityMetadata(entry)?.uncompressedSize ?? 0));
9
+ return { entry, name, kind: physical.kind, mode, size: physical.size, crc32: physical.crc32 };
42
10
  }
@@ -1,4 +1,4 @@
1
1
  import { Transform } from "node:stream";
2
- import { type ZipEntry } from "./archive-zip-entry.js";
2
+ import type { AdmittedZipEntry } from "./archive-zip-entry.js";
3
3
  export declare function normalizeZipIntegrityError(error: unknown): Error;
4
- export declare function createZipIntegrityTransform(entry: ZipEntry): Transform;
4
+ export declare function createZipIntegrityTransform(record: AdmittedZipEntry): Transform;
@@ -1,6 +1,5 @@
1
1
  import { Transform } from "node:stream";
2
2
  import { ArchiveFormatError } from "./archive-errors.js";
3
- import { zipEntryIntegrityMetadata, } from "./archive-zip-entry.js";
4
3
  import { updateCrc32 } from "./archive-crc32.js";
5
4
  export function normalizeZipIntegrityError(error) {
6
5
  if (error instanceof Error &&
@@ -9,17 +8,8 @@ export function normalizeZipIntegrityError(error) {
9
8
  }
10
9
  return error instanceof Error ? error : new Error(String(error));
11
10
  }
12
- export function createZipIntegrityTransform(entry) {
13
- const metadata = zipEntryIntegrityMetadata(entry);
14
- const expectedCrc32 = metadata?.crc32;
15
- const expectedSize = metadata?.uncompressedSize;
16
- if (typeof expectedCrc32 !== "number" ||
17
- !Number.isInteger(expectedCrc32) ||
18
- typeof expectedSize !== "number" ||
19
- !Number.isSafeInteger(expectedSize) ||
20
- expectedSize < 0) {
21
- throw new ArchiveFormatError(`zip entry has invalid integrity metadata: ${entry.name}`);
22
- }
11
+ export function createZipIntegrityTransform(record) {
12
+ const { entry, crc32: expectedCrc32, size: expectedSize } = record;
23
13
  let actualCrc32 = 0;
24
14
  let actualSize = 0;
25
15
  return new Transform({
@@ -1,8 +1,12 @@
1
1
  import type { ZipDirectoryEntry } from "./archive-zip-directory.js";
2
- import { type ZipEntry } from "./archive-zip-entry.js";
2
+ import { type AdmittedZipEntry } from "./archive-zip-entry.js";
3
3
  export type ZipArchiveWithFiles = {
4
4
  files: Record<string, unknown>;
5
5
  };
6
- export declare function admittedZipEntries(archive: ZipArchiveWithFiles): readonly ZipEntry[];
6
+ export type ZipArchiveAdmission = {
7
+ archive: ZipArchiveWithFiles;
8
+ entries: ReadonlyMap<string, AdmittedZipEntry>;
9
+ };
10
+ export declare function assertZipEntryBinding(archive: ZipArchiveWithFiles, record: AdmittedZipEntry, path: string): void;
7
11
  /** Internal: the caller has admitted these unchanged bytes and their metadata. */
8
- export declare function loadAdmittedZipArchive(buffer: Buffer | Uint8Array, admitted: ZipDirectoryEntry[]): Promise<ZipArchiveWithFiles>;
12
+ export declare function loadAdmittedZipArchive(buffer: Buffer | Uint8Array, admitted: ZipDirectoryEntry[]): Promise<ZipArchiveAdmission>;
@@ -1,10 +1,13 @@
1
1
  import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
2
- import { validateArchiveEntryPath } from "./archive-entry.js";
3
- import { registerAdmittedZipEntry } from "./archive-zip-entry.js";
2
+ import { createArchiveOutputPathTracker, validateArchiveEntryPath } from "./archive-entry.js";
3
+ import { createAdmittedZipEntry } from "./archive-zip-entry.js";
4
4
  import { zipPathKey } from "./archive-zip-names.js";
5
- const orderedEntries = new WeakMap();
6
- export function admittedZipEntries(archive) {
7
- return orderedEntries.get(archive) ?? disagreement();
5
+ export function assertZipEntryBinding(archive, record, path) {
6
+ const { entry } = record;
7
+ const name = entry.name;
8
+ validateArchiveEntryPath(name, { escapeLabel: "archive root" });
9
+ if (archive.files[record.name] !== entry || zipPathKey(name) !== path)
10
+ disagreement();
8
11
  }
9
12
  function disagreement() {
10
13
  throw new ArchiveFormatError("ZIP decoder disagrees with admitted directory metadata");
@@ -24,7 +27,9 @@ function matchesData(value, physical) {
24
27
  /** Internal: the caller has admitted these unchanged bytes and their metadata. */
25
28
  export async function loadAdmittedZipArchive(buffer, admitted) {
26
29
  const physicalByPath = new Map();
30
+ const trackOutputPath = createArchiveOutputPathTracker();
27
31
  for (const entry of admitted) {
32
+ trackOutputPath(entry.portableKey, entry.portableKey);
28
33
  if (physicalByPath.has(entry.portableKey))
29
34
  collision();
30
35
  physicalByPath.set(entry.portableKey, entry);
@@ -110,7 +115,7 @@ export async function loadAdmittedZipArchive(buffer, admitted) {
110
115
  if (remaining.size || inserted.size)
111
116
  disagreement();
112
117
  const files = Object.create(null);
113
- const entries = [];
118
+ const entries = new Map();
114
119
  for (const [name, entry, physical] of normalized) {
115
120
  const appendSlash = physical.kind === "directory" && !name.endsWith("/");
116
121
  const normalizedName = appendSlash ? `${name}/` : name;
@@ -120,12 +125,10 @@ export async function loadAdmittedZipArchive(buffer, admitted) {
120
125
  entry.name = `${entry.name}/`;
121
126
  }
122
127
  files[normalizedName] = entry;
123
- registerAdmittedZipEntry(entry, physical);
124
- entries.push(entry);
128
+ entries.set(physical.portableKey, createAdmittedZipEntry(entry, normalizedName, physical));
125
129
  }
126
130
  archive.files = files;
127
- orderedEntries.set(archive, entries);
128
- return archive;
131
+ return { archive, entries };
129
132
  }
130
133
  async function importOptionalJsZip() {
131
134
  try {
@@ -1,10 +1,11 @@
1
1
  import { ArchiveFormatError, ArchiveSecurityError } from "./archive-errors.js";
2
- import { stripArchivePath } from "./archive-entry.js";
2
+ import { createArchiveOutputPathTracker, stripArchivePath } from "./archive-entry.js";
3
3
  /** Associate all native entries with physical admission before exposing any. */
4
4
  export function validateNativeZipManifest(manifest, admitted) {
5
5
  if (manifest.length !== admitted.length) {
6
6
  throw new ArchiveSecurityError("entry-path", "zip decoder collapsed entry names");
7
7
  }
8
+ const trackOutputPath = createArchiveOutputPathTracker();
8
9
  for (const [ordinal, entry] of manifest.entries()) {
9
10
  const physical = admitted[ordinal];
10
11
  const physicalPath = physical?.path;
@@ -18,5 +19,6 @@ export function validateNativeZipManifest(manifest, admitted) {
18
19
  (physical.creatorSystem === 3 && entry.mode !== physical.externalAttributes >>> 16)) {
19
20
  throw new ArchiveFormatError("ZIP decoder disagrees with admitted directory metadata");
20
21
  }
22
+ trackOutputPath(entry.path, entry.path);
21
23
  }
22
24
  }
@@ -1,5 +1,4 @@
1
1
  import { type ArchiveExtractLimits } from "./archive-limits.js";
2
- import { type ZipArchiveWithFiles } from "./archive-zip-loader.js";
3
- export { readZipCentralDirectoryEntryCount } from "./archive-zip-count.js";
4
- export type { ZipArchiveWithFiles } from "./archive-zip-loader.js";
2
+ import { type ZipArchiveAdmission, type ZipArchiveWithFiles } from "./archive-zip-loader.js";
3
+ export declare function loadZipArchiveWithAdmission(buffer: Buffer | Uint8Array, limits?: ArchiveExtractLimits): Promise<ZipArchiveAdmission>;
5
4
  export declare function loadZipArchiveWithPreflight(buffer: Buffer | Uint8Array, limits?: ArchiveExtractLimits): Promise<ZipArchiveWithFiles>;
@@ -1,13 +1,21 @@
1
1
  import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, resolveExtractLimits, } from "./archive-limits.js";
2
2
  import { admitZipBuffer } from "./archive-zip-admission.js";
3
3
  import { loadAdmittedZipArchive } from "./archive-zip-loader.js";
4
- export { readZipCentralDirectoryEntryCount } from "./archive-zip-count.js";
5
- export async function loadZipArchiveWithPreflight(buffer, limits) {
6
- const resolvedLimits = resolveExtractLimits(limits);
7
- if (buffer.byteLength > resolvedLimits.maxArchiveBytes) {
8
- throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
4
+ export function loadZipArchiveWithAdmission(buffer, limits) {
5
+ try {
6
+ const resolvedLimits = resolveExtractLimits(limits);
7
+ if (buffer.byteLength > resolvedLimits.maxArchiveBytes) {
8
+ throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
9
+ }
10
+ const entries = [];
11
+ admitZipBuffer(buffer, resolvedLimits, entry => { entries.push(entry); });
12
+ return loadAdmittedZipArchive(buffer, entries);
13
+ }
14
+ catch (error) {
15
+ // Keep validation failures in the promise handed to the extraction deadline.
16
+ return Promise.reject(error);
9
17
  }
10
- const entries = [];
11
- admitZipBuffer(buffer, resolvedLimits, entry => { entries.push(entry); });
12
- return await loadAdmittedZipArchive(buffer, entries);
18
+ }
19
+ export async function loadZipArchiveWithPreflight(buffer, limits) {
20
+ return (await loadZipArchiveWithAdmission(buffer, limits)).archive;
13
21
  }
@@ -6,10 +6,13 @@ export { resolveArchiveKind, resolvePackedRootDir, type ArchiveKind } from "./ar
6
6
  export { readArchiveEntry } from "./archive-read.js";
7
7
  export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, type ArchiveExtractLimits, type ArchiveLimitErrorCode, } from "./archive-limits.js";
8
8
  export { ArchiveFormatError, type ArchiveFormatErrorCode } from "./archive-errors.js";
9
- export { ArchiveSecurityError, type ArchiveSecurityErrorCode } from "./archive-staging.js";
10
- export { createArchiveSymlinkTraversalError, mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
9
+ export { ArchiveSecurityError, type ArchiveSecurityErrorCode } from "./archive-errors.js";
10
+ export { createArchiveSymlinkTraversalError, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
11
+ export { mergeExtractedTreeIntoDestination } from "./archive-merge.js";
11
12
  export { createTarEntryPreflightChecker, type TarEntryInfo } from "./archive-plan.js";
12
- export { loadZipArchiveWithPreflight, readZipCentralDirectoryEntryCount, type ZipArchiveWithFiles, } from "./archive-zip-preflight.js";
13
+ export { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
14
+ export { readZipCentralDirectoryEntryCount } from "./archive-zip-count.js";
15
+ export type { ZipArchiveWithFiles } from "./archive-zip-loader.js";
13
16
  export declare function extractArchive(params: ExtractArchiveOptions): Promise<void>;
14
17
  export type InspectTarArchiveOptions = Pick<ExtractArchiveOptions, "archivePath" | "timeoutMs" | "limits" | "entryFilter" | "onFiltered">;
15
18
  export type InspectedTarEntry = Readonly<Pick<ArchivePlanEntry, "path" | "kind" | "size">>;
@@ -1,4 +1,4 @@
1
- import { createTarEntryPlanner, createArchiveEntrySelector, resolveArchiveFilteredEntryPolicy, createArchiveEntryPlanner, } from "./archive-plan.js";
1
+ import { createTarEntryPlanner, createArchiveEntrySelector, resolveArchiveFilteredEntryPolicy, resolveArchiveEntryMode, createArchiveEntryPlanner, } from "./archive-plan.js";
2
2
  import { inspectTar, replayTar } from "./archive-tar-stream.js";
3
3
  import { runPinnedWriteHelper } from "./pinned-write.js";
4
4
  import { constants as fsConstants } from "node:fs";
@@ -11,9 +11,7 @@ import { assertArchiveEntryCountWithinLimit, createByteBudgetTracker, createExtr
11
11
  import { resolveArchiveKind } from "./archive-kind.js";
12
12
  import { prepareArchiveDestinationGuard, preparePrivateArchiveOutputPath, } from "./archive-staging.js";
13
13
  import { withStagedArchivePublication } from "./archive-merge.js";
14
- import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
15
- import { admittedZipEntries } from "./archive-zip-loader.js";
16
- import { zipEntryKind, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
14
+ import { loadZipArchiveWithAdmission } from "./archive-zip-preflight.js";
17
15
  import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
18
16
  import { ArchiveSecurityError, ArchiveFormatError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
19
17
  import { stageArchiveFileForExtraction } from "./archive-input.js";
@@ -29,10 +27,12 @@ export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
29
27
  export { readArchiveEntry } from "./archive-read.js";
30
28
  export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, } from "./archive-limits.js";
31
29
  export { ArchiveFormatError } from "./archive-errors.js";
32
- export { ArchiveSecurityError } from "./archive-staging.js";
33
- export { createArchiveSymlinkTraversalError, mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
30
+ export { ArchiveSecurityError } from "./archive-errors.js";
31
+ export { createArchiveSymlinkTraversalError, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
32
+ export { mergeExtractedTreeIntoDestination } from "./archive-merge.js";
34
33
  export { createTarEntryPreflightChecker } from "./archive-plan.js";
35
- export { loadZipArchiveWithPreflight, readZipCentralDirectoryEntryCount, } from "./archive-zip-preflight.js";
34
+ export { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
35
+ export { readZipCentralDirectoryEntryCount } from "./archive-zip-count.js";
36
36
  const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants;
37
37
  const OPEN_WRITE_CREATE_FLAGS = fsConstants.O_WRONLY |
38
38
  fsConstants.O_CREAT |
@@ -49,7 +49,7 @@ async function readZipEntryStream(entry) {
49
49
  async function writeZipFileEntry(params) {
50
50
  params.deadline.check();
51
51
  params.budget.startEntry();
52
- const readable = await readZipEntryStream(params.entry);
52
+ const readable = await readZipEntryStream(params.record.entry);
53
53
  const destinationPath = params.outPath;
54
54
  let tempHandle = null;
55
55
  let handleClosedByStream = false;
@@ -67,7 +67,7 @@ async function writeZipFileEntry(params) {
67
67
  handleClosedByStream = true;
68
68
  });
69
69
  try {
70
- await pipeline(readable, createExtractBudgetTransform({ onChunkBytes: params.budget.addBytes }), createZipIntegrityTransform(params.entry), writable, { signal: params.deadline.signal });
70
+ await pipeline(readable, createExtractBudgetTransform({ onChunkBytes: params.budget.addBytes }), createZipIntegrityTransform(params.record), writable, { signal: params.deadline.signal });
71
71
  }
72
72
  catch (err) {
73
73
  throw normalizeZipIntegrityError(createPipelineTimeoutError(err, params.deadline));
@@ -101,26 +101,29 @@ async function extractZip(params) {
101
101
  deadline.check();
102
102
  const buffer = await fs.readFile(params.archivePath, { signal: deadline.signal });
103
103
  deadline.check();
104
- const zip = await waitForDeadline(loadZipArchiveWithPreflight(buffer, limits), deadline);
104
+ const { entries } = await waitForDeadline(loadZipArchiveWithAdmission(buffer, limits), deadline);
105
105
  deadline.check();
106
- const entries = admittedZipEntries(zip);
107
- assertArchiveEntryCountWithinLimit(entries.length, limits);
106
+ assertArchiveEntryCountWithinLimit(entries.size, limits);
108
107
  const budget = createByteBudgetTracker(limits);
109
108
  await withStagedArchivePublication({ ...params, destinationGuard }, async (stagingDir) => {
110
109
  const { select } = createArchiveEntrySelector({ ...params, rootDir: stagingDir });
111
110
  const acceptedEntries = [];
112
- for (const entry of entries) {
111
+ for (const record of entries.values()) {
113
112
  deadline.check();
114
- const entryKind = zipEntryKind(entry);
115
- const relPath = select({ path: entry.name, kind: entryKind, size: zipEntryDeclaredSize(entry) });
113
+ const { entry, kind: entryKind, size, mode: archivedMode } = record;
114
+ const relPath = select({ path: record.name, kind: entryKind, size });
116
115
  if (relPath === null)
117
116
  continue;
118
117
  if (entryKind === "symlink") {
119
- throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${entry.name}`);
118
+ throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${record.name}`);
120
119
  }
121
120
  if (entryKind === "other")
122
121
  continue;
123
- const mode = zipEntryMode(entry, params.entryModes);
122
+ const mode = resolveArchiveEntryMode({
123
+ kind: entry.dir ? "directory" : "file",
124
+ archivedMode,
125
+ policy: params.entryModes,
126
+ });
124
127
  acceptedEntries.push({ path: relPath, kind: entry.dir ? "directory" : "file", mode });
125
128
  const outPath = path.join(stagingDir, relPath);
126
129
  await preparePrivateArchiveOutputPath({
@@ -128,12 +131,12 @@ async function extractZip(params) {
128
131
  destinationRealDir: stagingDir,
129
132
  relPath,
130
133
  outPath,
131
- originalPath: entry.name,
134
+ originalPath: record.name,
132
135
  isDirectory: entry.dir,
133
136
  deadline,
134
137
  });
135
138
  if (!entry.dir) {
136
- await writeZipFileEntry({ entry, outPath, budget, deadline });
139
+ await writeZipFileEntry({ record, outPath, budget, deadline });
137
140
  }
138
141
  }
139
142
  return acceptedEntries;
@@ -1,4 +1,4 @@
1
- export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicResult, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
1
+ export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicResult, type ReplaceFileAtomicDestinationState, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
2
2
  export type { RenameIdentityPolicy } from "./pinned-write-types.js";
3
3
  export type { ReplaceFileAtomicRestoreCleanup, ReplaceFileAtomicRestoreFailureDetails, ReplaceFileCopyFallbackRestorePolicy, ReplaceFileDestinationHardlinkPolicy, } from "./replace-file-copy-fallback.js";
4
4
  export { writeTextAtomic, type WriteTextAtomicOptions } from "./text-atomic.js";
@@ -1,9 +1,7 @@
1
1
  import type { BigIntStats } from "node:fs";
2
2
  import type { PinnedWriteParams, PublishedWriteIdentity } from "./pinned-write-types.js";
3
- export type RootCopyPublicationReceipt = Readonly<{
3
+ export type RootCopyPublicationReceipt = Readonly<PublishedWriteIdentity & {
4
4
  path: string;
5
- dev: bigint;
6
- ino: bigint;
7
5
  }>;
8
6
  export declare function createCopyPublicationObserver(path: string, notify?: (receipt: RootCopyPublicationReceipt) => void): {
9
7
  onPublished(identity: PublishedWriteIdentity): void;
@@ -1,16 +1,12 @@
1
1
  import { FsSafeError } from "./errors.js";
2
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
2
3
  export function createCopyPublicationObserver(path, notify) {
3
4
  let rejected;
4
5
  return {
5
6
  onPublished(identity) {
6
7
  const receipt = Object.freeze({ path, dev: identity.dev, ino: identity.ino });
7
8
  try {
8
- const result = notify?.(receipt);
9
- if (result !== null && (typeof result === "object" || typeof result === "function") &&
10
- "then" in result && typeof result.then === "function") {
11
- void Promise.resolve(result).catch(() => undefined);
12
- throw new TypeError("onDestinationPublished must be synchronous");
13
- }
9
+ assertSynchronousCallbackResult(notify?.(receipt), "onDestinationPublished");
14
10
  }
15
11
  catch (error) {
16
12
  rejected = { error };
@@ -0,0 +1,2 @@
1
+ /** Cache observations only within one synchronous policy check, never across mutations or callbacks. */
2
+ export declare function createMutationDenyMatcher(): (target: string, denied: string, prefix: boolean, protectAncestors?: boolean) => boolean;
@@ -0,0 +1,71 @@
1
+ import path from "node:path";
2
+ import { isPathInside } from "./path.js";
3
+ import { probePathCaseInsensitiveSync } from "./path-case.js";
4
+ import { observeMutationPath } from "./pinned-mutation-observation.js";
5
+ function foldedName(name) {
6
+ // Decompose before folding so combining marks precede an expanded Greek subscript.
7
+ // Default folding preserves dotless i even though its uppercase mapping is I.
8
+ return name.normalize("NFD").replace(/[^\u0131]+/gu, part => part.toLowerCase().toUpperCase().toLowerCase()).normalize("NFC");
9
+ }
10
+ function asciiCasePair(left, right) {
11
+ return /^[\x00-\x7f]*$/.test(left) && /^[\x00-\x7f]*$/.test(right) &&
12
+ left.toLowerCase() === right.toLowerCase();
13
+ }
14
+ /** Cache observations only within one synchronous policy check, never across mutations or callbacks. */
15
+ export function createMutationDenyMatcher() {
16
+ const observations = new Map();
17
+ const caseObservations = new Map();
18
+ const observe = (pathname) => {
19
+ if (!observations.has(pathname))
20
+ observations.set(pathname, observeMutationPath(pathname));
21
+ return observations.get(pathname);
22
+ };
23
+ const prospectiveInside = (parent, child) => {
24
+ const root = path.parse(parent).root;
25
+ if (root !== path.parse(child).root)
26
+ return false;
27
+ const left = parent.slice(root.length).split(path.sep);
28
+ const right = child.slice(root.length).split(path.sep).slice(0, left.length);
29
+ if (left.length !== right.length || !left.every((name, i) => foldedName(name) === foldedName(right[i]))) {
30
+ return false;
31
+ }
32
+ // Byte-identical relations were already handled without any observations.
33
+ if (left.every((name, i) => name === right[i]))
34
+ return false;
35
+ const alignedChild = path.join(root, ...right);
36
+ const a = observe(parent);
37
+ const b = observe(alignedChild);
38
+ if (!a || !b)
39
+ return true;
40
+ // Distinct existing ancestors are real objects, not prospective aliases.
41
+ if (a.canonicalAncestor !== b.canonicalAncestor ||
42
+ a.missingSegments.length === 0 || a.missingSegments.length !== b.missingSegments.length)
43
+ return false;
44
+ const missingLeft = a.missingSegments;
45
+ const missingRight = b.missingSegments;
46
+ if (!missingLeft.every((name, i) => foldedName(name) === foldedName(missingRight[i])))
47
+ return false;
48
+ const difference = missingLeft.findIndex((name, i) => name !== missingRight[i]);
49
+ if (difference === -1)
50
+ return true;
51
+ // A read-only ASCII observation can disprove aliasing at this existing parent.
52
+ // It cannot prove Unicode sensitivity or the behavior of a future directory.
53
+ if (difference === 0 && asciiCasePair(missingLeft[0], missingRight[0])) {
54
+ if (!caseObservations.has(a.canonicalAncestor)) {
55
+ caseObservations.set(a.canonicalAncestor, probePathCaseInsensitiveSync(path.join(a.canonicalAncestor, missingLeft[0]), { allowTemporaryProbe: false }));
56
+ }
57
+ if (caseObservations.get(a.canonicalAncestor) === false)
58
+ return false;
59
+ }
60
+ // Suffix probing writes directories. Policy admission has no authority for
61
+ // those side effects, so unproven case/normalization equivalence fails closed.
62
+ return true;
63
+ };
64
+ return (target, denied, prefix, protectAncestors = false) => {
65
+ if ((isPathInside(denied, target) && (prefix || isPathInside(target, denied))) ||
66
+ (protectAncestors && isPathInside(target, denied)))
67
+ return true;
68
+ return ((prefix || target.split(path.sep).length === denied.split(path.sep).length) &&
69
+ prospectiveInside(denied, target)) || (protectAncestors && prospectiveInside(target, denied));
70
+ };
71
+ }
@@ -1,7 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { createMutationDenyMatcher } from "./deny-mutation-match.js";
3
4
  import { FsSafeError } from "./errors.js";
4
- import { assertNoNulPathInput, isNotFoundPathError, isPathInside } from "./path.js";
5
+ import { assertNoNulPathInput, isNotFoundPathError } from "./path.js";
5
6
  import { realpathSync } from "./realpath.js";
6
7
  import { resolveExistingAncestor } from "./root-path-existing.js";
7
8
  import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
@@ -65,6 +66,7 @@ export function assertMutationNotDenied(filePath, policy, options = {}, mode = "
65
66
  const strict = mode === "sync-root-lock";
66
67
  const comparablePaths = strict ? strictMutationComparablePaths : resolveMutationComparablePaths;
67
68
  const targets = comparablePaths(filePath);
69
+ const matches = createMutationDenyMatcher();
68
70
  // Validate one complete phase at a time. A paths denial must not read or
69
71
  // canonicalize prefixes, and invalid later paths retain validation precedence.
70
72
  for (const kind of ["paths", "prefixes"]) {
@@ -72,8 +74,7 @@ export function assertMutationNotDenied(filePath, policy, options = {}, mode = "
72
74
  const deniedPaths = comparablePaths(entry);
73
75
  for (const target of targets) {
74
76
  for (const denied of deniedPaths) {
75
- if ((isPathInside(denied, target) && (kind === "prefixes" || isPathInside(target, denied))) ||
76
- (options.protectAncestors === true && isPathInside(target, denied))) {
77
+ if (matches(target, denied, kind === "prefixes", options.protectAncestors)) {
77
78
  throw new FsSafeError("denied-path", "path is denied by denyMutations policy");
78
79
  }
79
80
  }
@@ -36,7 +36,6 @@ export declare function createNearestExistingDirectoryGuard(rootReal: string, ta
36
36
  export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string, options: {
37
37
  bigint: boolean;
38
38
  }): Promise<AnyAsyncDirectoryGuard>;
39
- export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
40
39
  export declare function inspectDirectoryIdentity(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): Promise<BigIntStats>;
41
40
  export declare function observeDirectoryIdentitySync(dir: string, options: {
42
41
  bigint: true;
@@ -84,9 +84,6 @@ function assertDirectoryGuard(guard, mode) {
84
84
  export async function createNearestExistingDirectoryGuard(rootReal, targetPath, options = { bigint: false }) {
85
85
  return nearestExistingDirectoryGuard(rootReal, targetPath, "native", options);
86
86
  }
87
- export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
88
- return nearestExistingDirectoryGuard(rootReal, targetPath, "normalized");
89
- }
90
87
  function nearestExistingDirectoryGuard(rootReal, targetPath, mode, options) {
91
88
  assertNoWindowsPathAlias(rootReal, "filesystem");
92
89
  assertNoWindowsPathAlias(targetPath, "filesystem");
@@ -1,5 +1,23 @@
1
- import { type BigIntStats } from "node:fs";
2
- import { type DirectoryModeOwner } from "./directory-mode-owner.js";
1
+ import { type BigIntStats, type Stats } from "node:fs";
2
+ export type DirectoryModeChecks = {
3
+ check?: () => void;
4
+ beforeChmod?: () => Promise<void>;
5
+ };
6
+ export type DirectoryModeOwner = {
7
+ verify(check?: () => void): Promise<void>;
8
+ apply(mode: number, checks?: DirectoryModeChecks): Promise<void>;
9
+ close(): Promise<void>;
10
+ };
11
+ export declare function assertOwnedDirectory(expected: Stats | BigIntStats, actual: Stats | BigIntStats): void;
12
+ /** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
13
+ export declare function ownDirectoryMode(params: {
14
+ inspect: () => Promise<number>;
15
+ chmod: (mode: number) => Promise<void>;
16
+ prepareChmod?: () => Promise<void>;
17
+ verifyChmod?: () => Promise<void>;
18
+ close: () => Promise<void>;
19
+ ignoreChmodError?: boolean;
20
+ }): DirectoryModeOwner;
3
21
  /** Darwin descriptor inspection avoids requesting directory-content reads. */
4
22
  export declare function nodeDarwinDirectoryMetadataFlags(): number;
5
23
  export declare function nodeDirectorySearchOnlyFlags(): {
@@ -3,7 +3,83 @@ import fs from "node:fs/promises";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
5
5
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
6
- import { assertOwnedDirectory, ownDirectoryMode } from "./directory-mode-owner.js";
6
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
7
+ export function assertOwnedDirectory(expected, actual) {
8
+ if (actual.isSymbolicLink() || !actual.isDirectory()) {
9
+ throw new FsSafeError("not-file", "directory mode target must be a real directory");
10
+ }
11
+ if (!sameFileIdentityForCleanup(expected, actual)) {
12
+ throw new FsSafeError("path-mismatch", "directory changed before its mode could be applied");
13
+ }
14
+ }
15
+ /** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
16
+ export function ownDirectoryMode(params) {
17
+ let pending = Promise.resolve();
18
+ let closing;
19
+ const enqueue = (run) => {
20
+ if (closing)
21
+ return Promise.reject(new FsSafeError("path-mismatch", "directory mode owner is closed"));
22
+ const operation = pending.then(run);
23
+ pending = operation.catch(() => undefined);
24
+ return operation;
25
+ };
26
+ return {
27
+ verify: (check) => enqueue(async () => {
28
+ check?.();
29
+ await params.inspect();
30
+ check?.();
31
+ }),
32
+ apply: (mode, checks = {}) => enqueue(async () => {
33
+ // inspect() reports permission bits only; chmod ignores file-type bits,
34
+ // so tolerate raw stat modes (e.g. S_IFDIR | 0o755) by masking up front.
35
+ mode &= 0o7777;
36
+ checks.check?.();
37
+ const currentMode = await params.inspect();
38
+ checks.check?.();
39
+ if (currentMode === mode && !checks.beforeChmod && !checks.check)
40
+ return;
41
+ if (currentMode !== mode) {
42
+ await params.prepareChmod?.();
43
+ checks.check?.();
44
+ }
45
+ await checks.beforeChmod?.();
46
+ checks.check?.();
47
+ // Hooks/ancestor checks can yield; recheck the original named association.
48
+ await params.inspect();
49
+ checks.check?.();
50
+ let dispatchDeadlineFailure;
51
+ if (currentMode !== mode) {
52
+ checks.check?.();
53
+ try {
54
+ await params.chmod(mode);
55
+ }
56
+ catch (error) {
57
+ if (!params.ignoreChmodError)
58
+ throw error;
59
+ }
60
+ // Expiry cannot release the fd while post-dispatch verification is pending.
61
+ try {
62
+ checks.check?.();
63
+ }
64
+ catch (error) {
65
+ dispatchDeadlineFailure = { error };
66
+ }
67
+ await params.verifyChmod?.();
68
+ }
69
+ const finalMode = await params.inspect();
70
+ if (dispatchDeadlineFailure)
71
+ throw dispatchDeadlineFailure.error;
72
+ checks.check?.();
73
+ if (!params.ignoreChmodError && finalMode !== mode) {
74
+ throw new FsSafeError("path-mismatch", "directory final mode could not be verified");
75
+ }
76
+ }),
77
+ close() {
78
+ closing ??= pending.then(params.close);
79
+ return closing;
80
+ },
81
+ };
82
+ }
7
83
  /** Darwin descriptor inspection avoids requesting directory-content reads. */
8
84
  export function nodeDarwinDirectoryMetadataFlags() {
9
85
  if (process.platform !== "darwin" || (process.arch !== "x64" && process.arch !== "arm64")) {