@vibe-agent-toolkit/utils 0.2.0-rc.10 → 0.2.0-rc.12

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 (330) hide show
  1. package/README.md +112 -10
  2. package/dist/archive/archive.d.ts +129 -0
  3. package/dist/archive/archive.d.ts.map +1 -0
  4. package/dist/archive/archive.js +223 -0
  5. package/dist/archive/archive.js.map +1 -0
  6. package/dist/archive/entries.d.ts +132 -0
  7. package/dist/archive/entries.d.ts.map +1 -0
  8. package/dist/archive/entries.js +223 -0
  9. package/dist/archive/entries.js.map +1 -0
  10. package/dist/archive/limits.d.ts +39 -0
  11. package/dist/archive/limits.d.ts.map +1 -0
  12. package/dist/archive/limits.js +59 -0
  13. package/dist/archive/limits.js.map +1 -0
  14. package/dist/archive/records.d.ts +14 -0
  15. package/dist/archive/records.d.ts.map +1 -0
  16. package/dist/archive/records.js +3 -0
  17. package/dist/archive/records.js.map +1 -0
  18. package/dist/archive/tar-reader.d.ts +18 -0
  19. package/dist/archive/tar-reader.d.ts.map +1 -0
  20. package/dist/archive/tar-reader.js +173 -0
  21. package/dist/archive/tar-reader.js.map +1 -0
  22. package/dist/archive/zip-reader.d.ts +76 -0
  23. package/dist/archive/zip-reader.d.ts.map +1 -0
  24. package/dist/archive/zip-reader.js +376 -0
  25. package/dist/archive/zip-reader.js.map +1 -0
  26. package/dist/archive.d.ts +14 -0
  27. package/dist/archive.d.ts.map +1 -0
  28. package/dist/archive.js +12 -0
  29. package/dist/archive.js.map +1 -0
  30. package/dist/asset-reference.d.ts +17 -1
  31. package/dist/asset-reference.d.ts.map +1 -1
  32. package/dist/asset-reference.js +31 -7
  33. package/dist/asset-reference.js.map +1 -1
  34. package/dist/dirent-kind.d.ts +13 -40
  35. package/dist/dirent-kind.d.ts.map +1 -1
  36. package/dist/dirent-kind.js +21 -69
  37. package/dist/dirent-kind.js.map +1 -1
  38. package/dist/entrypoint.d.ts +2 -2
  39. package/dist/entrypoint.d.ts.map +1 -1
  40. package/dist/entrypoint.js +4 -3
  41. package/dist/entrypoint.js.map +1 -1
  42. package/dist/env-flag.d.ts +13 -5
  43. package/dist/env-flag.d.ts.map +1 -1
  44. package/dist/env-flag.js +13 -5
  45. package/dist/env-flag.js.map +1 -1
  46. package/dist/errors/confirmed-absent.d.ts +41 -0
  47. package/dist/errors/confirmed-absent.d.ts.map +1 -0
  48. package/dist/errors/confirmed-absent.js +43 -0
  49. package/dist/errors/confirmed-absent.js.map +1 -0
  50. package/dist/errors/errno-table.d.ts +177 -0
  51. package/dist/errors/errno-table.d.ts.map +1 -0
  52. package/dist/errors/errno-table.js +343 -0
  53. package/dist/errors/errno-table.js.map +1 -0
  54. package/dist/errors/fs-boundary.d.ts +36 -0
  55. package/dist/errors/fs-boundary.d.ts.map +1 -0
  56. package/dist/errors/fs-boundary.js +121 -0
  57. package/dist/errors/fs-boundary.js.map +1 -0
  58. package/dist/errors/fs-fault.d.ts +176 -0
  59. package/dist/errors/fs-fault.d.ts.map +1 -0
  60. package/dist/errors/fs-fault.js +215 -0
  61. package/dist/errors/fs-fault.js.map +1 -0
  62. package/dist/errors/git-errno.d.ts +13 -0
  63. package/dist/errors/git-errno.d.ts.map +1 -0
  64. package/dist/errors/git-errno.js +45 -0
  65. package/dist/errors/git-errno.js.map +1 -0
  66. package/dist/errors/path-present.d.ts +72 -0
  67. package/dist/errors/path-present.d.ts.map +1 -0
  68. package/dist/errors/path-present.js +110 -0
  69. package/dist/errors/path-present.js.map +1 -0
  70. package/dist/errors/rag-index-empty.d.ts +16 -0
  71. package/dist/errors/rag-index-empty.d.ts.map +1 -0
  72. package/dist/errors/rag-index-empty.js +16 -0
  73. package/dist/errors/rag-index-empty.js.map +1 -0
  74. package/dist/errors/suppressed-faults.d.ts +34 -0
  75. package/dist/errors/suppressed-faults.d.ts.map +1 -0
  76. package/dist/errors/suppressed-faults.js +66 -0
  77. package/dist/errors/suppressed-faults.js.map +1 -0
  78. package/dist/file-crawler.d.ts +13 -18
  79. package/dist/file-crawler.d.ts.map +1 -1
  80. package/dist/file-crawler.js +140 -24
  81. package/dist/file-crawler.js.map +1 -1
  82. package/dist/fs-utils.d.ts +75 -50
  83. package/dist/fs-utils.d.ts.map +1 -1
  84. package/dist/fs-utils.js +148 -158
  85. package/dist/fs-utils.js.map +1 -1
  86. package/dist/fs.d.ts +5 -4
  87. package/dist/fs.d.ts.map +1 -1
  88. package/dist/fs.js +5 -4
  89. package/dist/fs.js.map +1 -1
  90. package/dist/git-snapshot.d.ts +25 -17
  91. package/dist/git-snapshot.d.ts.map +1 -1
  92. package/dist/git-snapshot.js +65 -11
  93. package/dist/git-snapshot.js.map +1 -1
  94. package/dist/git-tracker.d.ts +11 -3
  95. package/dist/git-tracker.d.ts.map +1 -1
  96. package/dist/git-tracker.js +29 -6
  97. package/dist/git-tracker.js.map +1 -1
  98. package/dist/git-utils.d.ts.map +1 -1
  99. package/dist/git-utils.js +7 -35
  100. package/dist/git-utils.js.map +1 -1
  101. package/dist/git.d.ts +1 -1
  102. package/dist/git.d.ts.map +1 -1
  103. package/dist/git.js +1 -1
  104. package/dist/git.js.map +1 -1
  105. package/dist/gitignore-checker.d.ts.map +1 -1
  106. package/dist/gitignore-checker.js +5 -4
  107. package/dist/gitignore-checker.js.map +1 -1
  108. package/dist/in-order.d.ts +93 -0
  109. package/dist/in-order.d.ts.map +1 -0
  110. package/dist/in-order.js +165 -0
  111. package/dist/in-order.js.map +1 -0
  112. package/dist/index.d.ts +20 -4
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +30 -4
  115. package/dist/index.js.map +1 -1
  116. package/dist/kill-process-tree.d.ts +43 -0
  117. package/dist/kill-process-tree.d.ts.map +1 -0
  118. package/dist/kill-process-tree.js +48 -0
  119. package/dist/kill-process-tree.js.map +1 -0
  120. package/dist/link-following.d.ts +115 -0
  121. package/dist/link-following.d.ts.map +1 -0
  122. package/dist/link-following.js +283 -0
  123. package/dist/link-following.js.map +1 -0
  124. package/dist/listing-refusal.d.ts +44 -0
  125. package/dist/listing-refusal.d.ts.map +1 -1
  126. package/dist/listing-refusal.js +79 -2
  127. package/dist/listing-refusal.js.map +1 -1
  128. package/dist/name-fold.d.ts +25 -0
  129. package/dist/name-fold.d.ts.map +1 -0
  130. package/dist/name-fold.js +40 -0
  131. package/dist/name-fold.js.map +1 -0
  132. package/dist/path-containment.d.ts +17 -0
  133. package/dist/path-containment.d.ts.map +1 -1
  134. package/dist/path-containment.js +28 -5
  135. package/dist/path-containment.js.map +1 -1
  136. package/dist/path-utils.d.ts +14 -17
  137. package/dist/path-utils.d.ts.map +1 -1
  138. package/dist/path-utils.js +37 -55
  139. package/dist/path-utils.js.map +1 -1
  140. package/dist/process.d.ts +2 -0
  141. package/dist/process.d.ts.map +1 -1
  142. package/dist/process.js +2 -0
  143. package/dist/process.js.map +1 -1
  144. package/dist/project-utils.d.ts.map +1 -1
  145. package/dist/project-utils.js +8 -9
  146. package/dist/project-utils.js.map +1 -1
  147. package/dist/safe-exec.d.ts +6 -2
  148. package/dist/safe-exec.d.ts.map +1 -1
  149. package/dist/safe-exec.js +1 -1
  150. package/dist/safe-exec.js.map +1 -1
  151. package/dist/skill-targets.d.ts.map +1 -1
  152. package/dist/skill-targets.js +3 -1
  153. package/dist/skill-targets.js.map +1 -1
  154. package/dist/skill-test/spawn-claude.d.ts +0 -15
  155. package/dist/skill-test/spawn-claude.d.ts.map +1 -1
  156. package/dist/skill-test/spawn-claude.js +11 -50
  157. package/dist/skill-test/spawn-claude.js.map +1 -1
  158. package/dist/special-file.d.ts +18 -0
  159. package/dist/special-file.d.ts.map +1 -0
  160. package/dist/special-file.js +21 -0
  161. package/dist/special-file.js.map +1 -0
  162. package/dist/terminating-signals.d.ts +129 -0
  163. package/dist/terminating-signals.d.ts.map +1 -0
  164. package/dist/terminating-signals.js +210 -0
  165. package/dist/terminating-signals.js.map +1 -0
  166. package/dist/test-helpers.d.ts +9 -5
  167. package/dist/test-helpers.d.ts.map +1 -1
  168. package/dist/test-helpers.js +18 -21
  169. package/dist/test-helpers.js.map +1 -1
  170. package/dist/test-scratch-guard.d.ts +34 -0
  171. package/dist/test-scratch-guard.d.ts.map +1 -0
  172. package/dist/test-scratch-guard.js +45 -0
  173. package/dist/test-scratch-guard.js.map +1 -0
  174. package/dist/testing/archive-fixtures.d.ts +147 -0
  175. package/dist/testing/archive-fixtures.d.ts.map +1 -0
  176. package/dist/testing/archive-fixtures.js +449 -0
  177. package/dist/testing/archive-fixtures.js.map +1 -0
  178. package/dist/testing/executables.d.ts +6 -0
  179. package/dist/testing/executables.d.ts.map +1 -1
  180. package/dist/testing/executables.js +20 -9
  181. package/dist/testing/executables.js.map +1 -1
  182. package/dist/testing/fault-fs-preload.d.ts +16 -0
  183. package/dist/testing/fault-fs-preload.d.ts.map +1 -0
  184. package/dist/testing/fault-fs-preload.js +22 -0
  185. package/dist/testing/fault-fs-preload.js.map +1 -0
  186. package/dist/testing/fault-fs.d.ts +115 -0
  187. package/dist/testing/fault-fs.d.ts.map +1 -0
  188. package/dist/testing/fault-fs.js +411 -0
  189. package/dist/testing/fault-fs.js.map +1 -0
  190. package/dist/testing/fault-spec.d.ts +83 -0
  191. package/dist/testing/fault-spec.d.ts.map +1 -0
  192. package/dist/testing/fault-spec.js +132 -0
  193. package/dist/testing/fault-spec.js.map +1 -0
  194. package/dist/testing/hostile-tree.d.ts.map +1 -1
  195. package/dist/testing/hostile-tree.js +3 -4
  196. package/dist/testing/hostile-tree.js.map +1 -1
  197. package/dist/testing/platform-gates.d.ts +8 -0
  198. package/dist/testing/platform-gates.d.ts.map +1 -1
  199. package/dist/testing/platform-gates.js +24 -0
  200. package/dist/testing/platform-gates.js.map +1 -1
  201. package/dist/testing/temp-dir.d.ts +31 -0
  202. package/dist/testing/temp-dir.d.ts.map +1 -1
  203. package/dist/testing/temp-dir.js +63 -0
  204. package/dist/testing/temp-dir.js.map +1 -1
  205. package/dist/testing/tier-timeout.d.ts +34 -0
  206. package/dist/testing/tier-timeout.d.ts.map +1 -0
  207. package/dist/testing/tier-timeout.js +45 -0
  208. package/dist/testing/tier-timeout.js.map +1 -0
  209. package/dist/testing/tree-snapshot.d.ts +71 -0
  210. package/dist/testing/tree-snapshot.d.ts.map +1 -0
  211. package/dist/testing/tree-snapshot.js +163 -0
  212. package/dist/testing/tree-snapshot.js.map +1 -0
  213. package/dist/testing.d.ts +6 -0
  214. package/dist/testing.d.ts.map +1 -1
  215. package/dist/testing.js +8 -1
  216. package/dist/testing.js.map +1 -1
  217. package/dist/text-content.d.ts +29 -0
  218. package/dist/text-content.d.ts.map +1 -1
  219. package/dist/text-content.js +64 -0
  220. package/dist/text-content.js.map +1 -1
  221. package/dist/text-file.d.ts +58 -6
  222. package/dist/text-file.d.ts.map +1 -1
  223. package/dist/text-file.js +111 -11
  224. package/dist/text-file.js.map +1 -1
  225. package/dist/text.d.ts +1 -1
  226. package/dist/text.d.ts.map +1 -1
  227. package/dist/text.js +1 -1
  228. package/dist/text.js.map +1 -1
  229. package/dist/timing-dump.d.ts.map +1 -1
  230. package/dist/timing-dump.js +2 -10
  231. package/dist/timing-dump.js.map +1 -1
  232. package/dist/tree-change/apply-decisions.d.ts +101 -0
  233. package/dist/tree-change/apply-decisions.d.ts.map +1 -0
  234. package/dist/tree-change/apply-decisions.js +139 -0
  235. package/dist/tree-change/apply-decisions.js.map +1 -0
  236. package/dist/tree-change/apply.d.ts +98 -0
  237. package/dist/tree-change/apply.d.ts.map +1 -0
  238. package/dist/tree-change/apply.js +364 -0
  239. package/dist/tree-change/apply.js.map +1 -0
  240. package/dist/tree-change/copy-decisions.d.ts +58 -0
  241. package/dist/tree-change/copy-decisions.d.ts.map +1 -0
  242. package/dist/tree-change/copy-decisions.js +66 -0
  243. package/dist/tree-change/copy-decisions.js.map +1 -0
  244. package/dist/tree-change/copy-tree.d.ts +125 -0
  245. package/dist/tree-change/copy-tree.d.ts.map +1 -0
  246. package/dist/tree-change/copy-tree.js +279 -0
  247. package/dist/tree-change/copy-tree.js.map +1 -0
  248. package/dist/tree-change/files.d.ts +167 -0
  249. package/dist/tree-change/files.d.ts.map +1 -0
  250. package/dist/tree-change/files.js +423 -0
  251. package/dist/tree-change/files.js.map +1 -0
  252. package/dist/tree-change/followed-walk.d.ts +54 -0
  253. package/dist/tree-change/followed-walk.d.ts.map +1 -0
  254. package/dist/tree-change/followed-walk.js +81 -0
  255. package/dist/tree-change/followed-walk.js.map +1 -0
  256. package/dist/tree-change/host-chain.d.ts +48 -0
  257. package/dist/tree-change/host-chain.d.ts.map +1 -0
  258. package/dist/tree-change/host-chain.js +85 -0
  259. package/dist/tree-change/host-chain.js.map +1 -0
  260. package/dist/tree-change/identity-compare.d.ts +61 -0
  261. package/dist/tree-change/identity-compare.d.ts.map +1 -0
  262. package/dist/tree-change/identity-compare.js +115 -0
  263. package/dist/tree-change/identity-compare.js.map +1 -0
  264. package/dist/tree-change/identity.d.ts +62 -0
  265. package/dist/tree-change/identity.d.ts.map +1 -0
  266. package/dist/tree-change/identity.js +152 -0
  267. package/dist/tree-change/identity.js.map +1 -0
  268. package/dist/tree-change/index.d.ts +24 -0
  269. package/dist/tree-change/index.d.ts.map +1 -0
  270. package/dist/tree-change/index.js +14 -0
  271. package/dist/tree-change/index.js.map +1 -0
  272. package/dist/tree-change/link-chain.d.ts +180 -0
  273. package/dist/tree-change/link-chain.d.ts.map +1 -0
  274. package/dist/tree-change/link-chain.js +377 -0
  275. package/dist/tree-change/link-chain.js.map +1 -0
  276. package/dist/tree-change/link-containment.d.ts +105 -0
  277. package/dist/tree-change/link-containment.d.ts.map +1 -0
  278. package/dist/tree-change/link-containment.js +196 -0
  279. package/dist/tree-change/link-containment.js.map +1 -0
  280. package/dist/tree-change/link-walk.d.ts +49 -0
  281. package/dist/tree-change/link-walk.d.ts.map +1 -0
  282. package/dist/tree-change/link-walk.js +69 -0
  283. package/dist/tree-change/link-walk.js.map +1 -0
  284. package/dist/tree-change/plan.d.ts +206 -0
  285. package/dist/tree-change/plan.d.ts.map +1 -0
  286. package/dist/tree-change/plan.js +320 -0
  287. package/dist/tree-change/plan.js.map +1 -0
  288. package/dist/tree-change/readable-tree.d.ts +44 -0
  289. package/dist/tree-change/readable-tree.d.ts.map +1 -0
  290. package/dist/tree-change/readable-tree.js +52 -0
  291. package/dist/tree-change/readable-tree.js.map +1 -0
  292. package/dist/tree-change/rollback-error.d.ts +29 -0
  293. package/dist/tree-change/rollback-error.d.ts.map +1 -0
  294. package/dist/tree-change/rollback-error.js +31 -0
  295. package/dist/tree-change/rollback-error.js.map +1 -0
  296. package/dist/tree-change/staging-names.d.ts +27 -0
  297. package/dist/tree-change/staging-names.d.ts.map +1 -0
  298. package/dist/tree-change/staging-names.js +40 -0
  299. package/dist/tree-change/staging-names.js.map +1 -0
  300. package/dist/tree-change/tree-walk.d.ts +81 -0
  301. package/dist/tree-change/tree-walk.d.ts.map +1 -0
  302. package/dist/tree-change/tree-walk.js +212 -0
  303. package/dist/tree-change/tree-walk.js.map +1 -0
  304. package/dist/win32-namespace.d.ts +24 -0
  305. package/dist/win32-namespace.d.ts.map +1 -0
  306. package/dist/win32-namespace.js +31 -0
  307. package/dist/win32-namespace.js.map +1 -0
  308. package/dist/yaml/surgical-yaml.d.ts +12 -3
  309. package/dist/yaml/surgical-yaml.d.ts.map +1 -1
  310. package/dist/yaml/surgical-yaml.js +16 -6
  311. package/dist/yaml/surgical-yaml.js.map +1 -1
  312. package/eslint/README.md +12 -4
  313. package/eslint/rules/fs-binding-scan.cjs +278 -0
  314. package/eslint/rules/no-adhoc-errno.cjs +364 -0
  315. package/eslint/rules/no-bare-symlink-in-tests.cjs +2 -3
  316. package/eslint/rules/no-blind-catch.cjs +1 -1
  317. package/eslint/rules/no-destructive-fs.cjs +359 -0
  318. package/eslint/rules/no-existssync.cjs +70 -0
  319. package/eslint/rules/no-fragile-entrypoint-guard.cjs +3 -2
  320. package/eslint/rules/no-link-following-fs.cjs +68 -0
  321. package/eslint/rules/no-literal-process-exit.cjs +96 -8
  322. package/eslint/rules/no-literal-test-timeout.cjs +214 -0
  323. package/eslint/rules/no-raw-signal-listener.cjs +224 -0
  324. package/eslint/rules/no-stdout-outside-writer.cjs +135 -0
  325. package/package.json +10 -2
  326. package/dist/errors/errno.d.ts +0 -51
  327. package/dist/errors/errno.d.ts.map +0 -1
  328. package/dist/errors/errno.js +0 -112
  329. package/dist/errors/errno.js.map +0 -1
  330. package/eslint/rules/no-fs-promises-cp.cjs +0 -43
@@ -0,0 +1,206 @@
1
+ /**
2
+ * The plan half of the tree-change primitive: every destructive verb describes
3
+ * what it wants as {@link TreeChange}s, and {@link planTreeChanges} decides, with
4
+ * no side effect, what will actually happen to each — before anything is written.
5
+ *
6
+ * The decisions, in order:
7
+ *
8
+ * 1. **What is there** at each destination (`lstat`; a destination the OS refuses
9
+ * to examine throws, classified `destination`; "absent" is believed only when
10
+ * the parent's listing agrees).
11
+ * 2. **Keep.** A `remove` with nothing there, or whose `keepIfSameAs` names an
12
+ * entry that is `same` **or `unknown`** to it, is kept, with the reason. Only
13
+ * the target's own unexaminability throws, never a sibling's. A `remove` that
14
+ * holds a kept entry (or cannot be proven not to) is kept too: a kept entry is
15
+ * kept whole, never taken away with a directory above it.
16
+ * 3. **Aliasing and containment, by identity.** A `remove` that is the same entry
17
+ * as a `replace` (`plugins/Old` and `plugins/old` on APFS or NTFS, a link and its
18
+ * target), or an earlier `remove`, or lies inside any other change's
19
+ * destination, is `subsumed`: that change parks it. `unknown` is never a merge
20
+ * and never a refusal — the two changes run as two, and a wrong guess fails the
21
+ * apply, which rolls back; refusing would refuse every change on a filesystem
22
+ * that reports `ino` 0. Any other two changes still active whose destinations
23
+ * are one entry or nest are refused ({@link TREE_DESTS_OVERLAP_CODE}): the
24
+ * calling verb's defect, never the user's input.
25
+ * 4. **Ownership** of whatever is there: `must-be-free` refuses anything but an
26
+ * empty directory ({@link TREE_DEST_OCCUPIED_CODE}); `vat-made` refuses what its
27
+ * `recognise` disowns ({@link TREE_DEST_NOT_OWNED_CODE}); `force` and
28
+ * `vat-state` take anything — except that a `replace-file` never takes a directory
29
+ * (or a link to one), under any ownership ({@link TREE_DEST_OCCUPIED_CODE}).
30
+ * 5. **Holding.** A `copy` source, or a `write` fill's declared `reads`, that is the
31
+ * destination or inside it ({@link TREE_DEST_HOLDS_SOURCE_CODE}: the replace
32
+ * would delete it — never asked of a destination that is a link, which holds
33
+ * nothing: only the link is parked), or that holds the destination
34
+ * ({@link TREE_SOURCE_HOLDS_DEST_CODE}: a copy into itself), is refused.
35
+ * 6. **Readable source.** Every `copy` source is proven readable (the one
36
+ * special-file and link policy, `proveTreeReadable`, on the fill's declared `side`).
37
+ *
38
+ * Utils names no refusal code: these are library codes, and the CLI's refusal map
39
+ * decides which refusal each is.
40
+ */
41
+ import { type FsSide } from '../errors/fs-fault.js';
42
+ import type { EntryContainment, EntrySameness } from './identity-compare.js';
43
+ import type { LinkPolicy } from './tree-walk.js';
44
+ /** A destination that must be absent or an empty directory already holds something. */
45
+ export declare const TREE_DEST_OCCUPIED_CODE = "TREE_DEST_OCCUPIED";
46
+ /** A destination VAT may replace only when it made it holds something its recogniser disowns. */
47
+ export declare const TREE_DEST_NOT_OWNED_CODE = "TREE_DEST_NOT_OWNED";
48
+ /**
49
+ * Two changes of one plan whose destinations are one entry or nest (other than a remove the other
50
+ * change takes with it): they cannot be one transaction — the outer park would carry the inner
51
+ * change's staging away. The calling verb built a plan it must not, so it is VAT's defect.
52
+ */
53
+ export declare const TREE_DESTS_OVERLAP_CODE = "TREE_DESTS_OVERLAP";
54
+ /** A copy's destination lies inside its source: the copy would copy into itself. */
55
+ export declare const TREE_SOURCE_HOLDS_DEST_CODE = "TREE_SOURCE_HOLDS_DEST";
56
+ /** A copy's source is its destination or lies inside it: the replace would delete what it copies. */
57
+ export declare const TREE_DEST_HOLDS_SOURCE_CODE = "TREE_DEST_HOLDS_SOURCE";
58
+ /** What a recogniser says about a destination that is there. */
59
+ export type OwnershipVerdict = {
60
+ readonly owned: true;
61
+ } | {
62
+ readonly owned: false;
63
+ readonly reason: string;
64
+ };
65
+ /** Who may lose whatever is at a destination. */
66
+ export type Ownership =
67
+ /** Only an absent destination or an empty directory; anything else is refused. */
68
+ {
69
+ readonly kind: 'must-be-free';
70
+ }
71
+ /** The user said `--force`: whatever is there may go. */
72
+ | {
73
+ readonly kind: 'force';
74
+ }
75
+ /** Something VAT made before, as `recognise` judges it; anything else is refused. */
76
+ | {
77
+ readonly kind: 'vat-made';
78
+ readonly recognise: (dest: string) => OwnershipVerdict;
79
+ }
80
+ /** State VAT owns outright (a cache, a marketplace under `~/.claude`, `dist/`). */
81
+ | {
82
+ readonly kind: 'vat-state';
83
+ };
84
+ /** How a replacement tree is made, in a staged place beside its destination. */
85
+ export type TreeFill =
86
+ /**
87
+ * `copy` copies `source` by the readable-tree walk. `side` is the side of the verb `source` is on — an input
88
+ * (`source`), VAT's staging (`environment`), or a copy already made into user state (`destination`) — and
89
+ * every read of it, by the proof and by the copy, is classified on it. Required: only the caller knows.
90
+ */
91
+ {
92
+ readonly from: 'copy';
93
+ readonly source: string;
94
+ readonly side: FsSide;
95
+ readonly links: LinkPolicy;
96
+ readonly filter?: (relative: string) => boolean;
97
+ }
98
+ /** `write` fills the empty staged directory; `reads` are the inputs it reads, held to the same holding check as a copy source. */
99
+ | {
100
+ readonly from: 'write';
101
+ readonly write: (staged: string) => Promise<void>;
102
+ readonly reads?: readonly string[];
103
+ }
104
+ /** The staged entry is a link to `target` (a `--dev` lane). */
105
+ | {
106
+ readonly from: 'link';
107
+ readonly target: string;
108
+ };
109
+ /**
110
+ * A `replace-file`'s bytes: given, or made when the file is staged. Staging runs in the plan's order, so a
111
+ * function is called only after every earlier change of the plan has staged — a file made FROM a tree the
112
+ * same plan stages (an archive of a staged bundle) is one change of that plan, not a second transaction.
113
+ */
114
+ export type FileContents = string | Uint8Array | (() => string | Uint8Array);
115
+ /** One change a verb wants. `label` names it in the dry run and in every message. */
116
+ export type TreeChange = {
117
+ readonly op: 'replace';
118
+ readonly dest: string;
119
+ readonly ownership: Ownership;
120
+ readonly fill: TreeFill;
121
+ readonly label: string;
122
+ } | {
123
+ readonly op: 'replace-file';
124
+ readonly dest: string;
125
+ readonly ownership: Ownership;
126
+ readonly contents: FileContents;
127
+ readonly label: string;
128
+ } | {
129
+ readonly op: 'remove';
130
+ readonly dest: string;
131
+ readonly ownership: Ownership;
132
+ readonly keepIfSameAs?: () => readonly string[];
133
+ readonly label: string;
134
+ };
135
+ /** What is at a destination. */
136
+ export type EntryKind = 'absent' | 'directory' | 'file' | 'link' | 'special';
137
+ /** What will happen to a destination. */
138
+ export type PlannedAction = 'create' | 'replace' | 'remove' | 'keep' | 'subsumed';
139
+ /** One change, decided. */
140
+ export interface PlannedChange {
141
+ readonly change: TreeChange;
142
+ readonly existing: EntryKind;
143
+ readonly action: PlannedAction;
144
+ /** Why it is kept or subsumed. */
145
+ readonly reason?: string;
146
+ /**
147
+ * Set on a remove kept because the OS refused to examine an entry it may be (its `keepIfSameAs`):
148
+ * that entry. A caller that drops its own record of the kept entry names it, so the user can tell
149
+ * what stayed and why (ruling R7 d-I-1: never delete what may BE a kept sibling).
150
+ */
151
+ readonly unexaminedSibling?: string;
152
+ }
153
+ /** Every change, decided; `describe()` is the dry run, one line per change. */
154
+ export interface TreePlan {
155
+ readonly changes: readonly PlannedChange[];
156
+ describe(): readonly string[];
157
+ }
158
+ /** The filesystem facts the planner decides by: live in {@link planTreeChanges}, faked in its unit test. */
159
+ export interface PlanFacts {
160
+ /** What is at `dest`; throws when the OS refuses to examine it. */
161
+ readonly existing: (dest: string) => EntryKind;
162
+ readonly isEmptyDirectory: (dest: string) => boolean;
163
+ /** Whether the link at `dest` leads to a directory (false for a dangling link, or one to a file). */
164
+ readonly linksToDirectory: (dest: string) => boolean;
165
+ readonly sameEntry: (a: string, b: string) => EntrySameness;
166
+ readonly isInside: (child: string, ancestor: string) => EntryContainment;
167
+ /** Throws when the OS refuses to examine `dest`'s identity: a remove's own unexaminability is never a keep. */
168
+ readonly requireExaminable: (dest: string) => void;
169
+ /** Whether the OS refuses to examine `entry`'s identity (an `unknown` that is a refusal, not a fold that cannot tell). */
170
+ readonly refusesToExamine: (entry: string) => boolean;
171
+ }
172
+ /** Whether a planned change touches its destination (`keep` and `subsumed` do not). */
173
+ export declare const isActive: ({ action }: PlannedChange) => boolean;
174
+ /**
175
+ * The pure planner: every decision of {@link planTreeChanges} but the source
176
+ * proof, made from `facts`.
177
+ *
178
+ * @throws VatError coded `TREE_DESTS_OVERLAP` (a defect in the caller's plan), `TREE_DEST_OCCUPIED`,
179
+ * `TREE_DEST_NOT_OWNED`, `TREE_DEST_HOLDS_SOURCE` or `TREE_SOURCE_HOLDS_DEST`; whatever `facts` throws
180
+ */
181
+ export declare function planFromFacts(changes: readonly TreeChange[], facts: PlanFacts): TreePlan;
182
+ /**
183
+ * What is at `dest` NOW, for the apply to hold against what the plan decided
184
+ * (`changedSincePlan`). The emptiness of a directory is read only where the plan took it as free.
185
+ *
186
+ * @throws FsFaultError side `destination` when the OS refuses to examine or list it
187
+ */
188
+ export declare function destinationNow(dest: string, planned: PlannedChange): {
189
+ kind: EntryKind;
190
+ emptyDirectory: boolean;
191
+ };
192
+ /**
193
+ * Decide every change against the filesystem as it is, with no side effect. Each
194
+ * destination is taken in its canonical spelling (`safePath.resolve`: absolute,
195
+ * `.`/`..` resolved, forward slashes); `describe()` prints that spelling.
196
+ * Hand the result to `applyTreePlan`; print `describe()` for a dry run — the two
197
+ * cannot disagree, because the apply does exactly what the plan says.
198
+ *
199
+ * @param changes - What the verb wants, in the order it wants it
200
+ * @throws VatError coded `TREE_DEST_OCCUPIED`, `TREE_DEST_NOT_OWNED`,
201
+ * `TREE_DEST_HOLDS_SOURCE` or `TREE_SOURCE_HOLDS_DEST`; `FsFaultError` side
202
+ * `destination` for a destination the OS refuses to examine, on the fill's
203
+ * declared side for a copy source it refuses to read
204
+ */
205
+ export declare function planTreeChanges(changes: readonly TreeChange[]): Promise<TreePlan>;
206
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/tree-change/plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,320 @@
1
+ /**
2
+ * The plan half of the tree-change primitive: every destructive verb describes
3
+ * what it wants as {@link TreeChange}s, and {@link planTreeChanges} decides, with
4
+ * no side effect, what will actually happen to each — before anything is written.
5
+ *
6
+ * The decisions, in order:
7
+ *
8
+ * 1. **What is there** at each destination (`lstat`; a destination the OS refuses
9
+ * to examine throws, classified `destination`; "absent" is believed only when
10
+ * the parent's listing agrees).
11
+ * 2. **Keep.** A `remove` with nothing there, or whose `keepIfSameAs` names an
12
+ * entry that is `same` **or `unknown`** to it, is kept, with the reason. Only
13
+ * the target's own unexaminability throws, never a sibling's. A `remove` that
14
+ * holds a kept entry (or cannot be proven not to) is kept too: a kept entry is
15
+ * kept whole, never taken away with a directory above it.
16
+ * 3. **Aliasing and containment, by identity.** A `remove` that is the same entry
17
+ * as a `replace` (`plugins/Old` and `plugins/old` on APFS or NTFS, a link and its
18
+ * target), or an earlier `remove`, or lies inside any other change's
19
+ * destination, is `subsumed`: that change parks it. `unknown` is never a merge
20
+ * and never a refusal — the two changes run as two, and a wrong guess fails the
21
+ * apply, which rolls back; refusing would refuse every change on a filesystem
22
+ * that reports `ino` 0. Any other two changes still active whose destinations
23
+ * are one entry or nest are refused ({@link TREE_DESTS_OVERLAP_CODE}): the
24
+ * calling verb's defect, never the user's input.
25
+ * 4. **Ownership** of whatever is there: `must-be-free` refuses anything but an
26
+ * empty directory ({@link TREE_DEST_OCCUPIED_CODE}); `vat-made` refuses what its
27
+ * `recognise` disowns ({@link TREE_DEST_NOT_OWNED_CODE}); `force` and
28
+ * `vat-state` take anything — except that a `replace-file` never takes a directory
29
+ * (or a link to one), under any ownership ({@link TREE_DEST_OCCUPIED_CODE}).
30
+ * 5. **Holding.** A `copy` source, or a `write` fill's declared `reads`, that is the
31
+ * destination or inside it ({@link TREE_DEST_HOLDS_SOURCE_CODE}: the replace
32
+ * would delete it — never asked of a destination that is a link, which holds
33
+ * nothing: only the link is parked), or that holds the destination
34
+ * ({@link TREE_SOURCE_HOLDS_DEST_CODE}: a copy into itself), is refused.
35
+ * 6. **Readable source.** Every `copy` source is proven readable (the one
36
+ * special-file and link policy, `proveTreeReadable`, on the fill's declared `side`).
37
+ *
38
+ * Utils names no refusal code: these are library codes, and the CLI's refusal map
39
+ * decides which refusal each is.
40
+ */
41
+ import { lstatSync, readdirSync } from 'node:fs';
42
+ import { requireConfirmedAbsent } from '../errors/confirmed-absent.js';
43
+ import { isPathAbsentError } from '../errors/errno-table.js';
44
+ import { classifyFsFault, everywhere, withFsFaultSync } from '../errors/fs-fault.js';
45
+ import { VatError } from '../errors/vat-error.js';
46
+ import { forEachInOrder } from '../in-order.js';
47
+ import { followedStatSync } from '../link-following.js';
48
+ import { relativeEscapesRoot, safePath } from '../path-core.js';
49
+ import { identityOracle } from './identity.js';
50
+ import { proveTreeReadable } from './readable-tree.js';
51
+ /** A destination that must be absent or an empty directory already holds something. */
52
+ export const TREE_DEST_OCCUPIED_CODE = 'TREE_DEST_OCCUPIED';
53
+ /** A destination VAT may replace only when it made it holds something its recogniser disowns. */
54
+ export const TREE_DEST_NOT_OWNED_CODE = 'TREE_DEST_NOT_OWNED';
55
+ /**
56
+ * Two changes of one plan whose destinations are one entry or nest (other than a remove the other
57
+ * change takes with it): they cannot be one transaction — the outer park would carry the inner
58
+ * change's staging away. The calling verb built a plan it must not, so it is VAT's defect.
59
+ */
60
+ export const TREE_DESTS_OVERLAP_CODE = 'TREE_DESTS_OVERLAP';
61
+ /** A copy's destination lies inside its source: the copy would copy into itself. */
62
+ export const TREE_SOURCE_HOLDS_DEST_CODE = 'TREE_SOURCE_HOLDS_DEST';
63
+ /** A copy's source is its destination or lies inside it: the replace would delete what it copies. */
64
+ export const TREE_DEST_HOLDS_SOURCE_CODE = 'TREE_DEST_HOLDS_SOURCE';
65
+ function refusal(code, message) {
66
+ return new VatError(code, message);
67
+ }
68
+ /** Why a remove is kept — and the sibling the OS refused to examine, when that is why — or `undefined` when it goes ahead. */
69
+ function keepReason(change, existing, facts) {
70
+ if (change.op !== 'remove')
71
+ return undefined;
72
+ if (existing === 'absent')
73
+ return { reason: 'nothing there' };
74
+ const others = change.keepIfSameAs?.() ?? [];
75
+ // The target's own refusal throws; only an OTHER entry's refusal is `unknown`, a keep.
76
+ if (others.length > 0)
77
+ facts.requireExaminable(change.dest);
78
+ for (const other of others) {
79
+ const verdict = facts.sameEntry(change.dest, other);
80
+ if (verdict === 'same')
81
+ return { reason: `${other} is the same entry` };
82
+ if (verdict === 'unknown') {
83
+ const reason = `could not tell whether ${other} is the same entry`;
84
+ return facts.refusesToExamine(other) ? { reason, unexaminedSibling: other } : { reason };
85
+ }
86
+ }
87
+ return undefined;
88
+ }
89
+ /** The kept entry `planned` (a remove) holds, or may hold: removing it would take that entry with it. */
90
+ function heldKeep(planned, all, facts) {
91
+ if (planned.action !== 'remove')
92
+ return undefined;
93
+ return all.find((other) => other !== planned && other.action === 'keep' && other.existing !== 'absent' && facts.isInside(other.change.dest, planned.change.dest) !== 'outside');
94
+ }
95
+ /**
96
+ * Keep every remove that holds — or cannot be proven not to hold — a kept entry: a
97
+ * kept entry is kept whole, never taken away with a directory above it. Repeated until
98
+ * nothing changes, so a remove holding one that was just kept is kept as well.
99
+ */
100
+ function keepingHolders(planned, facts) {
101
+ const next = planned.map((each) => {
102
+ const held = heldKeep(each, planned, facts);
103
+ return held === undefined ? each : { ...each, action: 'keep', reason: `holds ${held.change.dest}, which is kept` };
104
+ });
105
+ return next.some((each, index) => each !== planned[index]) ? keepingHolders(next, facts) : planned;
106
+ }
107
+ function initialAction(change, existing) {
108
+ if (change.op === 'remove')
109
+ return 'remove';
110
+ return existing === 'absent' ? 'create' : 'replace';
111
+ }
112
+ /** Whether `other` takes `change`'s destination with it: the same entry as a replace or an earlier remove, or an ancestor of it. */
113
+ function subsumes(change, index, other, otherIndex, facts) {
114
+ if (otherIndex === index || other.action === 'keep' || other.existing === 'absent')
115
+ return false;
116
+ const sameAllowed = other.change.op !== 'remove' || otherIndex < index;
117
+ if (sameAllowed && facts.sameEntry(change.change.dest, other.change.dest) === 'same')
118
+ return true;
119
+ return facts.isInside(change.change.dest, other.change.dest) === 'inside';
120
+ }
121
+ function withSubsumption(planned, index, all, facts) {
122
+ if (planned.action !== 'remove')
123
+ return planned;
124
+ const by = all.find((other, otherIndex) => subsumes(planned, index, other, otherIndex, facts));
125
+ return by === undefined ? planned : { ...planned, action: 'subsumed', reason: `parked by ${by.change.op} ${by.change.label}` };
126
+ }
127
+ /** Whether a planned change touches its destination (`keep` and `subsumed` do not). */
128
+ export const isActive = ({ action }) => action === 'create' || action === 'replace' || action === 'remove';
129
+ /**
130
+ * Whether `inner`'s destination is `outer`'s or lies under it. Judged by identity where `outer`
131
+ * exists; an absent destination has no identity, so there it is judged by the spelling.
132
+ */
133
+ function holds(outer, inner, facts) {
134
+ if (outer.existing === 'absent')
135
+ return !relativeEscapesRoot(safePath.relative(outer.change.dest, inner.change.dest));
136
+ return facts.sameEntry(inner.change.dest, outer.change.dest) === 'same' || facts.isInside(inner.change.dest, outer.change.dest) === 'inside';
137
+ }
138
+ /** Refuse two active changes over one tree: the plan is the calling verb's defect. `unknown` is never proof. */
139
+ function checkOverlap(planned, facts) {
140
+ const active = planned.filter((each) => isActive(each));
141
+ for (const [index, first] of active.entries()) {
142
+ const second = active.slice(index + 1).find((other) => holds(first, other, facts) || holds(other, first, facts));
143
+ if (second !== undefined) {
144
+ throw refusal(TREE_DESTS_OVERLAP_CODE, `${first.change.label} (${first.change.dest}) and ${second.change.label} (${second.change.dest}) change one tree; a plan may not overlap its own destinations`);
145
+ }
146
+ }
147
+ }
148
+ /**
149
+ * Refuse a FILE change whose destination is a directory, or a link to one — under every ownership,
150
+ * `force` included: `--force` means "overwrite the file", and a file landing on a directory's name
151
+ * would park the whole tree and delete it.
152
+ */
153
+ function checkFileOverDirectory({ change, existing }, facts) {
154
+ if (change.op !== 'replace-file')
155
+ return;
156
+ const { dest, label } = change;
157
+ if (existing === 'directory') {
158
+ throw refusal(TREE_DEST_OCCUPIED_CODE, `${label}: ${dest} is a directory; a file is never written over a directory`);
159
+ }
160
+ if (existing === 'link' && facts.linksToDirectory(dest)) {
161
+ throw refusal(TREE_DEST_OCCUPIED_CODE, `${label}: ${dest} is a link to a directory; a file is never written over a directory`);
162
+ }
163
+ }
164
+ /** Refuse a destination its ownership does not let this change take. */
165
+ function checkOwnership(planned, facts) {
166
+ const { change, existing, action } = planned;
167
+ if (existing === 'absent' || action === 'keep' || action === 'subsumed')
168
+ return;
169
+ checkFileOverDirectory(planned, facts);
170
+ const { ownership, dest, label } = change;
171
+ // An empty directory is free for a tree that lands in its place, never for a FILE: a file
172
+ // replacing a directory the user made (a folder they named as an output) is a surprise, not a fill.
173
+ const freeDirectory = change.op !== 'replace-file' && existing === 'directory' && facts.isEmptyDirectory(dest);
174
+ // The refusal says what this change would have done to the destination.
175
+ const verb = change.op === 'remove' ? 'remove' : 'replace';
176
+ if (ownership.kind === 'must-be-free' && !freeDirectory) {
177
+ throw refusal(TREE_DEST_OCCUPIED_CODE, `${label}: ${dest} already holds something (a ${existing}); refusing to ${verb} it`);
178
+ }
179
+ if (ownership.kind === 'vat-made') {
180
+ const verdict = ownership.recognise(dest);
181
+ if (!verdict.owned)
182
+ throw refusal(TREE_DEST_NOT_OWNED_CODE, `${label}: refusing to ${verb} ${dest}: ${verdict.reason}`);
183
+ }
184
+ }
185
+ /** The inputs a replace reads, which its destination must neither be, hold, nor lie inside. */
186
+ function readsOf(change) {
187
+ if (change.op !== 'replace')
188
+ return [];
189
+ if (change.fill.from === 'copy')
190
+ return [change.fill.source];
191
+ return change.fill.from === 'write' ? change.fill.reads ?? [] : [];
192
+ }
193
+ function checkHolding(planned, facts) {
194
+ if (planned.action !== 'create' && planned.action !== 'replace')
195
+ return;
196
+ const { dest, label } = planned.change;
197
+ // A destination that is a LINK holds nothing: the replace parks the link itself, never what it points
198
+ // at — so a source the link leads to (a `--dev` link to the build being installed) is not lost with it.
199
+ const destHoldsEntries = planned.existing !== 'link';
200
+ for (const source of readsOf(planned.change)) {
201
+ if (destHoldsEntries && (facts.sameEntry(source, dest) === 'same' || facts.isInside(source, dest) === 'inside')) {
202
+ throw refusal(TREE_DEST_HOLDS_SOURCE_CODE, `${label}: ${source} is ${dest} or lies inside it, so replacing ${dest} would delete it`);
203
+ }
204
+ if (facts.isInside(dest, source) === 'inside') {
205
+ throw refusal(TREE_SOURCE_HOLDS_DEST_CODE, `${label}: ${dest} lies inside ${source}, which it is made from`);
206
+ }
207
+ }
208
+ }
209
+ function describeChange({ change, action, reason }) {
210
+ const line = `${action} ${change.label} ${change.dest}`;
211
+ return reason === undefined ? line : `${line} (${reason})`;
212
+ }
213
+ /**
214
+ * The pure planner: every decision of {@link planTreeChanges} but the source
215
+ * proof, made from `facts`.
216
+ *
217
+ * @throws VatError coded `TREE_DESTS_OVERLAP` (a defect in the caller's plan), `TREE_DEST_OCCUPIED`,
218
+ * `TREE_DEST_NOT_OWNED`, `TREE_DEST_HOLDS_SOURCE` or `TREE_SOURCE_HOLDS_DEST`; whatever `facts` throws
219
+ */
220
+ export function planFromFacts(changes, facts) {
221
+ const drafted = changes.map((change) => {
222
+ const existing = facts.existing(change.dest);
223
+ const kept = keepReason(change, existing, facts);
224
+ return kept === undefined ? { change, existing, action: initialAction(change, existing) } : { change, existing, action: 'keep', ...kept };
225
+ });
226
+ const kept = keepingHolders(drafted, facts);
227
+ const planned = kept.map((draft, index) => withSubsumption(draft, index, kept, facts));
228
+ checkOverlap(planned, facts);
229
+ for (const each of planned)
230
+ checkOwnership(each, facts);
231
+ for (const each of planned)
232
+ checkHolding(each, facts);
233
+ return { changes: planned, describe: () => planned.map((each) => describeChange(each)) };
234
+ }
235
+ const EXAMINE_DEST = { side: 'destination', action: 'examine the destination' };
236
+ function kindOf(dest) {
237
+ let stats;
238
+ try {
239
+ stats = lstatSync(dest);
240
+ }
241
+ catch (error) {
242
+ if (!isPathAbsentError(error))
243
+ throw classifyFsFault(error, { ...EXAMINE_DEST, path: dest });
244
+ // Absent only when the parent's listing agrees: a refused probe of an entry that is there is a fault.
245
+ requireConfirmedAbsent(dest, error, { action: EXAMINE_DEST.action, path: dest }, { follows: false, sideOf: everywhere(EXAMINE_DEST.side) });
246
+ return 'absent';
247
+ }
248
+ if (stats.isSymbolicLink())
249
+ return 'link';
250
+ if (stats.isDirectory())
251
+ return 'directory';
252
+ return stats.isFile() ? 'file' : 'special';
253
+ }
254
+ /** Whether the link at `dest` leads to a directory: a dangling link leads nowhere; a target the OS refuses to examine is a fault. */
255
+ function linksToDirectory(dest) {
256
+ try {
257
+ // `dest` is the destination the verb names: the operator's own path, its own link the edge.
258
+ return followedStatSync(dest, dest)?.isDirectory() ?? false;
259
+ }
260
+ catch (error) {
261
+ if (isPathAbsentError(error))
262
+ return false;
263
+ throw classifyFsFault(error, { ...EXAMINE_DEST, path: dest });
264
+ }
265
+ }
266
+ /**
267
+ * What is at `dest` NOW, for the apply to hold against what the plan decided
268
+ * (`changedSincePlan`). The emptiness of a directory is read only where the plan took it as free.
269
+ *
270
+ * @throws FsFaultError side `destination` when the OS refuses to examine or list it
271
+ */
272
+ export function destinationNow(dest, planned) {
273
+ const kind = kindOf(dest);
274
+ const askEmpty = kind === 'directory' && planned.existing === 'directory' && planned.change.ownership.kind === 'must-be-free';
275
+ return { kind, emptyDirectory: askEmpty && withFsFaultSync({ side: 'destination', action: 'list the destination', path: dest }, () => readdirSync(dest).length === 0) };
276
+ }
277
+ /** The live facts of one plan: every identity question asks each entry once, so no two decisions see the filesystem differently. */
278
+ function liveFacts() {
279
+ const oracle = identityOracle();
280
+ return {
281
+ existing: kindOf,
282
+ isEmptyDirectory: (dest) => withFsFaultSync({ side: 'destination', action: 'list the destination', path: dest }, () => readdirSync(dest).length === 0),
283
+ linksToDirectory,
284
+ sameEntry: oracle.sameEntry,
285
+ isInside: oracle.isInside,
286
+ requireExaminable: (dest) => {
287
+ oracle.identities(dest);
288
+ },
289
+ refusesToExamine: (entry) => oracle.refuses(entry),
290
+ };
291
+ }
292
+ /** Prove a copy source readable, as the copy will read it. */
293
+ function proveSource({ change, action }) {
294
+ if (change.op !== 'replace' || change.fill.from !== 'copy' || (action !== 'create' && action !== 'replace'))
295
+ return Promise.resolve();
296
+ const { source, links, side, filter } = change.fill;
297
+ return proveTreeReadable(source, { links, side, ...(filter === undefined ? {} : { filter }) });
298
+ }
299
+ /**
300
+ * Decide every change against the filesystem as it is, with no side effect. Each
301
+ * destination is taken in its canonical spelling (`safePath.resolve`: absolute,
302
+ * `.`/`..` resolved, forward slashes); `describe()` prints that spelling.
303
+ * Hand the result to `applyTreePlan`; print `describe()` for a dry run — the two
304
+ * cannot disagree, because the apply does exactly what the plan says.
305
+ *
306
+ * @param changes - What the verb wants, in the order it wants it
307
+ * @throws VatError coded `TREE_DEST_OCCUPIED`, `TREE_DEST_NOT_OWNED`,
308
+ * `TREE_DEST_HOLDS_SOURCE` or `TREE_SOURCE_HOLDS_DEST`; `FsFaultError` side
309
+ * `destination` for a destination the OS refuses to examine, on the fill's
310
+ * declared side for a copy source it refuses to read
311
+ */
312
+ export async function planTreeChanges(changes) {
313
+ // One canonical spelling of every destination (absolute, normalized, forward slashes), which the
314
+ // plan, the dry run and the apply — down to the parents a create makes and removes — all use.
315
+ const canonical = changes.map((change) => ({ ...change, dest: safePath.resolve(change.dest) }));
316
+ const plan = planFromFacts(canonical, liveFacts());
317
+ await forEachInOrder(plan.changes, proveSource);
318
+ return plan;
319
+ }
320
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/tree-change/plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The readable-tree proof: before a verb writes anything for a tree it is about to
3
+ * read whole, it proves every entry readable, so an unreadable input is refused as
4
+ * the input it is rather than surfacing half-way through as an output failure.
5
+ */
6
+ import type { FsSide } from '../errors/fs-fault.js';
7
+ import { type TreeWalkOptions } from './tree-walk.js';
8
+ /** What {@link proveTreeReadable} is told: the walk's link policy and filter, and the side its faults are on. */
9
+ export interface ProveTreeReadableOptions extends TreeWalkOptions {
10
+ /**
11
+ * The side of the verb the tree is on: an input (`source`), VAT's staging
12
+ * (`environment`), or a copy already made into user state (`destination`).
13
+ */
14
+ readonly side: FsSide;
15
+ }
16
+ /**
17
+ * Prove the directory `root` readable: every directory listed, every regular file
18
+ * opened without blocking and judged by `fstat` on that handle, each entry held to
19
+ * the one special-file and link policy `copyTree` copies by (see `tree-walk.ts`).
20
+ * A root that is not a directory is refused by its listing (`ENOTDIR`), as
21
+ * `copyTree` refuses it. Each file is opened, never `access`ed: on Windows `access`
22
+ * does not consult ACLs.
23
+ *
24
+ * @param root - The directory the caller is about to read
25
+ * @param options - `links` and `filter` as the read will use them, and the `side` the tree is on
26
+ * @throws FsFaultError on `options.side` (origin `content`) naming the first entry
27
+ * the OS refused, in listing order — a named pipe, socket or device as class
28
+ * `wrong-type`; `CopyLinkEscapesSourceError` / `DirectoryWalkRevisitedError` for
29
+ * a link out of, or back into, a `follow-contained` tree
30
+ */
31
+ export declare function proveTreeReadable(root: string, options: ProveTreeReadableOptions): Promise<void>;
32
+ /**
33
+ * Read a regular file whole, through a handle opened without blocking and judged
34
+ * by `fstat` on that handle: a named pipe, socket or device is refused before a
35
+ * byte is read, never waited on.
36
+ *
37
+ * @param path - The file to read
38
+ * @returns Its bytes
39
+ * @throws an `EFTYPE` errno for a special file, `EISDIR` for a directory, and
40
+ * whatever `open` / `read` throw otherwise — raw, for the caller to classify on
41
+ * the side it knows the file is on
42
+ */
43
+ export declare function readRegularFile(path: string): Promise<Buffer>;
44
+ //# sourceMappingURL=readable-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readable-tree.d.ts","sourceRoot":"","sources":["../../src/tree-change/readable-tree.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EAAoB,KAAK,eAAe,EAAY,MAAM,gBAAgB,CAAC;AAElF,iHAAiH;AACjH,MAAM,WAAW,wBAAyB,SAAQ,eAAe;IAC/D;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AASD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtG;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnE"}