@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,115 @@
1
+ /**
2
+ * The ONE module that asks the kernel to FOLLOW a link — `stat`, `realpath` — and the only place
3
+ * `local/no-link-following-fs` lets such a call stand (every other `packages/*\/src` file asks
4
+ * here; the allowlist of operator's-own-path files is the rule's option, asserted both ways).
5
+ *
6
+ * The order is the guarantee. A Windows kernel asked to follow a link to `\\host\share` opens an
7
+ * SMB session and offers the operator's NTLM credentials to that host before any answer comes
8
+ * back, so every following call here first reads the chain of links to its path hop by hop, by
9
+ * `lstat` and `readlink` alone ({@link assertLocalChain}), and asks the kernel only once the chain
10
+ * is read and local. On every host: the walk is the same rule everywhere, its flavor bound from
11
+ * `process.platform` here, at the edge.
12
+ *
13
+ * Each call names the tree its path is crawled in, `within` — the operator's own input (a project
14
+ * root, a directory the operator named, VAT's own scratch). That root is resolved by the kernel
15
+ * once, as the operator's input always was, and the walk reads only what lies beneath it: the
16
+ * root's ancestors are the operator's, never probed and never blamed for a fault. A path that IS
17
+ * the operator's own input passes itself as `within`, and the kernel answers it directly.
18
+ *
19
+ * A probe the OS refuses is never a pass: it is thrown, raw, for the caller's classifier — a read
20
+ * lane records it as that one path's fault, a write lane stops on its own side.
21
+ */
22
+ import { type BigIntStats, type Stats } from 'node:fs';
23
+ import { type PathFlavor } from './tree-change/link-chain.js';
24
+ /**
25
+ * The kernel's realpath of the operator's OWN path (an edge: a root the operator named, VAT's own
26
+ * scratch), in the host's separators — the one place the kernel follows a link no walk read.
27
+ *
28
+ * @throws The resolution's own error: the caller classifies it
29
+ */
30
+ export declare function edgeRealpathSync(path: string): string;
31
+ /**
32
+ * Throw unless every link on the way to `path` beneath `within` has been read and leads somewhere
33
+ * local — the check the kernel's following call is the SECOND lock behind. The user's own paths:
34
+ * an absolute local text is followed and walked by the same rules, so a later hop's network text
35
+ * is still caught. A chain that leads to nothing (a dangle, a cycle) is answered so — the kernel is
36
+ * not asked about it.
37
+ *
38
+ * @param path - Absolute, or relative to cwd; either separator
39
+ * @param within - The tree `path` is crawled in: the operator's own input (or `path` itself, for the operator's own path)
40
+ * @param flavor - Whose kernel is about to be asked: bound from `process.platform` at the edge
41
+ * @returns `'local'` when the kernel may now be asked; `'nothing'` when the chain leads to nothing
42
+ * @throws LinkChainRefusedError when a link on the way names a network or device path, or the chain
43
+ * never ends; the raw errno of an `lstat`/`readlink` (or the root's realpath) the OS refused
44
+ */
45
+ export declare function assertLocalChain(path: string, within: string, flavor: PathFlavor): 'local' | 'nothing';
46
+ /**
47
+ * Throw what the kernel's own `syscall` would have thrown for a chain that leads to nothing — `ENOENT`
48
+ * for a dangle, `ELOOP` for a cycle — so the caller's classifier reads it as it always did; return
49
+ * when the chain is read, local, and the kernel may be asked.
50
+ */
51
+ export declare function requireSomethingAt(path: string, within: string, syscall: string): void;
52
+ /**
53
+ * The native realpath of a path crawled beneath `within` — the canonical ON-DISK spelling,
54
+ * forward-slashed — asked of the kernel only once the chain is read and local.
55
+ *
56
+ * @throws LinkChainRefusedError for a chain through a network path, or one that never ends; the
57
+ * `ENOENT` of a chain that leads to nothing; else the resolution's own error — the caller classifies it
58
+ */
59
+ export declare function realpathNativeSync(path: string, within: string): string;
60
+ /** The asynchronous {@link realpathNativeSync}. */
61
+ export declare function realpathNative(path: string, within: string): Promise<string>;
62
+ /**
63
+ * Node's JS realpath (`fs.realpath`, not `.native`: the spelling the caller wrote is kept, so it
64
+ * stays in the namespace of a lexical fallback) of a path crawled beneath `within`, asked only once
65
+ * the chain is read and local. Read off the `node:fs` default object per call, so a spy installed
66
+ * after import still sees it.
67
+ *
68
+ * @throws as {@link realpathNativeSync}
69
+ */
70
+ export declare function followedRealpath(path: string, within: string): Promise<string>;
71
+ /** The synchronous {@link followedRealpath} (`fs.realpathSync`, the JS walk). */
72
+ export declare function followedRealpathSync(path: string, within: string): string;
73
+ /**
74
+ * The kernel's `stat` of the operator's OWN path (an edge: the base a crawl was asked to start
75
+ * from), raw: the caller classifies its error.
76
+ */
77
+ export declare function edgeStatSync(path: string): Stats;
78
+ /** {@link edgeStatSync} with `bigint` fields — the `dev`/`ino` an entry identity is built from. */
79
+ export declare function edgeBigintStatSync(path: string): BigIntStats;
80
+ /**
81
+ * What `path` (crawled beneath `within`) resolves to, its links followed — `undefined` when it
82
+ * resolves to nothing: absent, a dangling link, a cycle, or a chain the kernel itself calls a loop.
83
+ *
84
+ * @throws LinkChainRefusedError for a chain VAT will not hand the kernel; the raw errno of any probe
85
+ * the OS refused (never folded into "absent")
86
+ */
87
+ export declare function followedStatSync(path: string, within: string): Stats | undefined;
88
+ /** The asynchronous {@link followedStatSync}. */
89
+ export declare function followedStat(path: string, within: string): Promise<Stats | undefined>;
90
+ /**
91
+ * {@link followedStatSync} for a caller that cannot go on without an answer: a path that resolves
92
+ * to nothing is the `ENOENT` (or, for a cycle, `ELOOP`) the kernel's own `stat` would have thrown,
93
+ * for the caller's classifier.
94
+ */
95
+ export declare function requireFollowedStatSync(path: string, within: string): Stats;
96
+ /** The asynchronous {@link requireFollowedStatSync}. */
97
+ export declare function requireFollowedStat(path: string, within: string): Promise<Stats>;
98
+ /**
99
+ * The `type` a link made to copy the link at `path` (crawled beneath `within`) is created with —
100
+ * `'dir'` when its chain, read by text, ends at a directory; `'file'` otherwise, a dangle or a cycle
101
+ * included (what Node itself picks for a target it cannot `stat`). Read by `lstat` and `readlink`
102
+ * alone: a `symlink` with no type has Node on Windows ask the KERNEL to follow the target to pick
103
+ * one, and a link text no walk read must never reach the kernel.
104
+ *
105
+ * @throws LinkChainRefusedError for a chain through a network or device path, or one that never
106
+ * ends; the raw errno of an `lstat`/`readlink` the OS refused
107
+ */
108
+ export declare function linkCopyType(path: string, within: string): 'dir' | 'file';
109
+ /** What a followed path resolves to. */
110
+ export type FollowedPathKind = 'directory' | 'file' | 'other' | 'absent';
111
+ /** What `path` beneath `within` resolves to ({@link followedStatSync}): `absent` for nothing. */
112
+ export declare function followedKindSync(path: string, within: string): FollowedPathKind;
113
+ /** The asynchronous {@link followedKindSync}. */
114
+ export declare function followedKind(path: string, within: string): Promise<FollowedPathKind>;
115
+ //# sourceMappingURL=link-following.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-following.d.ts","sourceRoot":"","sources":["../src/link-following.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAO/D,OAAO,EAAmD,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAwB/G;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD;AAmDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,GAAG,SAAS,CAEtG;AASD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAKtF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGvE;AAED,mDAAmD;AACnD,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlF;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGpF;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,mGAAmG;AACnG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAE5D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAQhF;AAED,iDAAiD;AACjD,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAQ3F;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAG3E;AAED,wDAAwD;AACxD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAGtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAMzE;AAED,wCAAwC;AACxC,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAQzE,iGAAiG;AACjG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAE/E;AAED,iDAAiD;AACjD,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAE1F"}
@@ -0,0 +1,283 @@
1
+ /**
2
+ * The ONE module that asks the kernel to FOLLOW a link — `stat`, `realpath` — and the only place
3
+ * `local/no-link-following-fs` lets such a call stand (every other `packages/*\/src` file asks
4
+ * here; the allowlist of operator's-own-path files is the rule's option, asserted both ways).
5
+ *
6
+ * The order is the guarantee. A Windows kernel asked to follow a link to `\\host\share` opens an
7
+ * SMB session and offers the operator's NTLM credentials to that host before any answer comes
8
+ * back, so every following call here first reads the chain of links to its path hop by hop, by
9
+ * `lstat` and `readlink` alone ({@link assertLocalChain}), and asks the kernel only once the chain
10
+ * is read and local. On every host: the walk is the same rule everywhere, its flavor bound from
11
+ * `process.platform` here, at the edge.
12
+ *
13
+ * Each call names the tree its path is crawled in, `within` — the operator's own input (a project
14
+ * root, a directory the operator named, VAT's own scratch). That root is resolved by the kernel
15
+ * once, as the operator's input always was, and the walk reads only what lies beneath it: the
16
+ * root's ancestors are the operator's, never probed and never blamed for a fault. A path that IS
17
+ * the operator's own input passes itself as `within`, and the kernel answers it directly.
18
+ *
19
+ * A probe the OS refuses is never a pass: it is thrown, raw, for the caller's classifier — a read
20
+ * lane records it as that one path's fault, a write lane stops on its own side.
21
+ */
22
+ // Every kernel call is reached through the DEFAULT objects, never a named binding: Node snapshots a
23
+ // builtin's named ESM exports at import time, so a `vi.spyOn(fs, 'statSync')` cannot see a call made
24
+ // through one — it attaches, counts zero, and reads exactly like "no I/O here" (the guards on
25
+ // `pathSpellingFrom`/`realpathRowFrom` and the fault matrix are assertions of exactly that).
26
+ import nodeFs from 'node:fs';
27
+ import fsPromises from 'node:fs/promises';
28
+ import { promisify } from 'node:util';
29
+ import { isFinalPathUnanswerableError, isPathAbsentError, isUnresolvableLinkError } from './errors/errno-table.js';
30
+ import { relativeEscapesRoot, safePath, toForwardSlash } from './path-core.js';
31
+ import { LinkChainRefusedError, walkHostChain } from './tree-change/host-chain.js';
32
+ import { HOST_PATH_FLAVOR, namedPathRoot } from './tree-change/link-chain.js';
33
+ /**
34
+ * Whether a native realpath's failure must be asked again of Node's JS realpath before anyone
35
+ * believes it — the ONE fallback rule every native site shares.
36
+ *
37
+ * - A volume whose driver does not implement final-path queries (a RAM disk, some redirectors and
38
+ * virtual drives) answers EVERY native realpath with `ERROR_INVALID_FUNCTION`, which libuv
39
+ * reports as `EISDIR` ({@link isFinalPathUnanswerableError}). That says nothing about the path.
40
+ * - An absent answer ({@link isPathAbsentError}) is confirmed by the JS walk: on a musl libc
41
+ * without procfs the native `realpath(3)` reports `ENOENT` for a path that exists, and Windows
42
+ * answers `ENOENT` for a file on a volume mounted into a folder with no drive letter (pinned on a
43
+ * real host by `realpath-native-folder-mount.integration.test.ts`). A path the JS walk also finds
44
+ * absent throws the walk's own `ENOENT`, so nothing that is really missing reads as present.
45
+ *
46
+ * ⚠️ The JS answer is not the on-disk spelling: it keeps the casing the caller spelled (and, on
47
+ * Windows, an 8.3 short name), and it expands only what `lstat` calls a link — a `subst` drive and
48
+ * a volume mount point (which libuv reports as a plain directory) stay as spelled. A caller that
49
+ * needs the kernel's own identity compares `stat` identities, not these strings.
50
+ */
51
+ function nativeAnswerNeedsJsWalk(error) {
52
+ return isFinalPathUnanswerableError(error) || isPathAbsentError(error);
53
+ }
54
+ /**
55
+ * The kernel's realpath of the operator's OWN path (an edge: a root the operator named, VAT's own
56
+ * scratch), in the host's separators — the one place the kernel follows a link no walk read.
57
+ *
58
+ * @throws The resolution's own error: the caller classifies it
59
+ */
60
+ export function edgeRealpathSync(path) {
61
+ try {
62
+ return nodeFs.realpathSync.native(path);
63
+ }
64
+ catch (error) {
65
+ if (!nativeAnswerNeedsJsWalk(error))
66
+ throw error;
67
+ return nodeFs.realpathSync(path);
68
+ }
69
+ }
70
+ /**
71
+ * Read the chain to `path`, hop by hop, beneath the tree `within`.
72
+ *
73
+ * - `path` is `within` itself: the operator's own input, answered by the kernel (`edge`).
74
+ * - `path` lies beneath `within`: the walk starts at `within`'s real path and reads only the
75
+ * components below it — never the root's own ancestors.
76
+ * - `path` lies outside `within` (a link target named by content, a path a caller built): the whole
77
+ * of it is walked from its own root, ancestors included, as any link target is.
78
+ */
79
+ function walkWithin(path, within, flavor) {
80
+ const start = toForwardSlash(safePath.resolve(path));
81
+ const root = toForwardSlash(safePath.resolve(within));
82
+ const below = safePath.relative(root, start);
83
+ if (below === '')
84
+ return { kind: 'edge' };
85
+ if (relativeEscapesRoot(below))
86
+ return walkWhole(start, flavor);
87
+ let real;
88
+ try {
89
+ real = realRootOf(root);
90
+ }
91
+ catch (error) {
92
+ // Nothing is beneath a root that is not there.
93
+ if (isPathAbsentError(error))
94
+ return { kind: 'nothing', at: root, cause: 'absent' };
95
+ throw error;
96
+ }
97
+ return walkHostChain(safePath.join(real, below), { flavor, root: real, outside: 'follow' });
98
+ }
99
+ /** The whole chain to `start` (forward-slashed, absolute), ancestors included, from the root it hangs from. */
100
+ function walkWhole(start, flavor) {
101
+ return walkHostChain(start, { flavor, root: namedPathRoot(start, flavor), outside: 'follow' });
102
+ }
103
+ /**
104
+ * Each tree root's real path, resolved by the kernel ONCE per process — at the edge, as the operator's
105
+ * input always was — not once per path crawled beneath it. Only an answer is kept: a root the OS
106
+ * refused, or one not there yet, is asked again next time.
107
+ */
108
+ const REAL_ROOTS = new Map();
109
+ function realRootOf(root) {
110
+ const known = REAL_ROOTS.get(root);
111
+ if (known !== undefined)
112
+ return known;
113
+ const real = toForwardSlash(edgeRealpathSync(root));
114
+ REAL_ROOTS.set(root, real);
115
+ return real;
116
+ }
117
+ /**
118
+ * Throw unless every link on the way to `path` beneath `within` has been read and leads somewhere
119
+ * local — the check the kernel's following call is the SECOND lock behind. The user's own paths:
120
+ * an absolute local text is followed and walked by the same rules, so a later hop's network text
121
+ * is still caught. A chain that leads to nothing (a dangle, a cycle) is answered so — the kernel is
122
+ * not asked about it.
123
+ *
124
+ * @param path - Absolute, or relative to cwd; either separator
125
+ * @param within - The tree `path` is crawled in: the operator's own input (or `path` itself, for the operator's own path)
126
+ * @param flavor - Whose kernel is about to be asked: bound from `process.platform` at the edge
127
+ * @returns `'local'` when the kernel may now be asked; `'nothing'` when the chain leads to nothing
128
+ * @throws LinkChainRefusedError when a link on the way names a network or device path, or the chain
129
+ * never ends; the raw errno of an `lstat`/`readlink` (or the root's realpath) the OS refused
130
+ */
131
+ export function assertLocalChain(path, within, flavor) {
132
+ return nothingOnTheWay(path, within, flavor) === undefined ? 'local' : 'nothing';
133
+ }
134
+ /** {@link assertLocalChain}, naming why a chain leads to nothing: `undefined` when the kernel may be asked. */
135
+ function nothingOnTheWay(path, within, flavor) {
136
+ const walked = walkWithin(path, within, flavor);
137
+ if (walked.kind === 'refused')
138
+ throw new LinkChainRefusedError(path, walked.refusal);
139
+ return walked.kind === 'nothing' ? walked.cause : undefined;
140
+ }
141
+ /**
142
+ * Throw what the kernel's own `syscall` would have thrown for a chain that leads to nothing — `ENOENT`
143
+ * for a dangle, `ELOOP` for a cycle — so the caller's classifier reads it as it always did; return
144
+ * when the chain is read, local, and the kernel may be asked.
145
+ */
146
+ export function requireSomethingAt(path, within, syscall) {
147
+ const cause = nothingOnTheWay(path, within, HOST_PATH_FLAVOR);
148
+ if (cause === undefined)
149
+ return;
150
+ const [code, words] = cause === 'cycle' ? ['ELOOP', 'too many symbolic links encountered'] : ['ENOENT', 'no such file or directory'];
151
+ throw Object.assign(new Error(`${code}: ${words}, ${syscall} '${path}'`), { code, syscall, path });
152
+ }
153
+ /**
154
+ * The native realpath of a path crawled beneath `within` — the canonical ON-DISK spelling,
155
+ * forward-slashed — asked of the kernel only once the chain is read and local.
156
+ *
157
+ * @throws LinkChainRefusedError for a chain through a network path, or one that never ends; the
158
+ * `ENOENT` of a chain that leads to nothing; else the resolution's own error — the caller classifies it
159
+ */
160
+ export function realpathNativeSync(path, within) {
161
+ requireSomethingAt(path, within, 'realpath');
162
+ return toForwardSlash(edgeRealpathSync(path));
163
+ }
164
+ /** The asynchronous {@link realpathNativeSync}. */
165
+ export async function realpathNative(path, within) {
166
+ requireSomethingAt(path, within, 'realpath');
167
+ try {
168
+ return toForwardSlash(await promisify(nodeFs.realpath.native)(path));
169
+ }
170
+ catch (error) {
171
+ if (!nativeAnswerNeedsJsWalk(error))
172
+ throw error;
173
+ return toForwardSlash(await promisify(nodeFs.realpath)(path));
174
+ }
175
+ }
176
+ /**
177
+ * Node's JS realpath (`fs.realpath`, not `.native`: the spelling the caller wrote is kept, so it
178
+ * stays in the namespace of a lexical fallback) of a path crawled beneath `within`, asked only once
179
+ * the chain is read and local. Read off the `node:fs` default object per call, so a spy installed
180
+ * after import still sees it.
181
+ *
182
+ * @throws as {@link realpathNativeSync}
183
+ */
184
+ export async function followedRealpath(path, within) {
185
+ requireSomethingAt(path, within, 'realpath');
186
+ return await promisify(nodeFs.realpath)(path);
187
+ }
188
+ /** The synchronous {@link followedRealpath} (`fs.realpathSync`, the JS walk). */
189
+ export function followedRealpathSync(path, within) {
190
+ requireSomethingAt(path, within, 'realpath');
191
+ return nodeFs.realpathSync(path);
192
+ }
193
+ /**
194
+ * The kernel's `stat` of the operator's OWN path (an edge: the base a crawl was asked to start
195
+ * from), raw: the caller classifies its error.
196
+ */
197
+ export function edgeStatSync(path) {
198
+ return nodeFs.statSync(path);
199
+ }
200
+ /** {@link edgeStatSync} with `bigint` fields — the `dev`/`ino` an entry identity is built from. */
201
+ export function edgeBigintStatSync(path) {
202
+ return nodeFs.statSync(path, { bigint: true });
203
+ }
204
+ /**
205
+ * What `path` (crawled beneath `within`) resolves to, its links followed — `undefined` when it
206
+ * resolves to nothing: absent, a dangling link, a cycle, or a chain the kernel itself calls a loop.
207
+ *
208
+ * @throws LinkChainRefusedError for a chain VAT will not hand the kernel; the raw errno of any probe
209
+ * the OS refused (never folded into "absent")
210
+ */
211
+ export function followedStatSync(path, within) {
212
+ if (assertLocalChain(path, within, HOST_PATH_FLAVOR) === 'nothing')
213
+ return undefined;
214
+ try {
215
+ return nodeFs.statSync(path);
216
+ }
217
+ catch (error) {
218
+ if (isUnresolvableLinkError(error))
219
+ return undefined;
220
+ throw error;
221
+ }
222
+ }
223
+ /** The asynchronous {@link followedStatSync}. */
224
+ export async function followedStat(path, within) {
225
+ if (assertLocalChain(path, within, HOST_PATH_FLAVOR) === 'nothing')
226
+ return undefined;
227
+ try {
228
+ return await fsPromises.stat(path);
229
+ }
230
+ catch (error) {
231
+ if (isUnresolvableLinkError(error))
232
+ return undefined;
233
+ throw error;
234
+ }
235
+ }
236
+ /**
237
+ * {@link followedStatSync} for a caller that cannot go on without an answer: a path that resolves
238
+ * to nothing is the `ENOENT` (or, for a cycle, `ELOOP`) the kernel's own `stat` would have thrown,
239
+ * for the caller's classifier.
240
+ */
241
+ export function requireFollowedStatSync(path, within) {
242
+ requireSomethingAt(path, within, 'stat');
243
+ return nodeFs.statSync(path);
244
+ }
245
+ /** The asynchronous {@link requireFollowedStatSync}. */
246
+ export async function requireFollowedStat(path, within) {
247
+ requireSomethingAt(path, within, 'stat');
248
+ return await fsPromises.stat(path);
249
+ }
250
+ /**
251
+ * The `type` a link made to copy the link at `path` (crawled beneath `within`) is created with —
252
+ * `'dir'` when its chain, read by text, ends at a directory; `'file'` otherwise, a dangle or a cycle
253
+ * included (what Node itself picks for a target it cannot `stat`). Read by `lstat` and `readlink`
254
+ * alone: a `symlink` with no type has Node on Windows ask the KERNEL to follow the target to pick
255
+ * one, and a link text no walk read must never reach the kernel.
256
+ *
257
+ * @throws LinkChainRefusedError for a chain through a network or device path, or one that never
258
+ * ends; the raw errno of an `lstat`/`readlink` the OS refused
259
+ */
260
+ export function linkCopyType(path, within) {
261
+ const walked = walkWithin(path, within, HOST_PATH_FLAVOR);
262
+ // The link IS the tree's root: its chain is walked whole, from its own root, as any link target is.
263
+ const chain = walked.kind === 'edge' ? walkWhole(toForwardSlash(safePath.resolve(path)), HOST_PATH_FLAVOR) : walked;
264
+ if (chain.kind === 'refused')
265
+ throw new LinkChainRefusedError(path, chain.refusal);
266
+ return chain.kind === 'resolved' && chain.final === 'directory' ? 'dir' : 'file';
267
+ }
268
+ function kindOf(stats) {
269
+ if (stats === undefined)
270
+ return 'absent';
271
+ if (stats.isDirectory())
272
+ return 'directory';
273
+ return stats.isFile() ? 'file' : 'other';
274
+ }
275
+ /** What `path` beneath `within` resolves to ({@link followedStatSync}): `absent` for nothing. */
276
+ export function followedKindSync(path, within) {
277
+ return kindOf(followedStatSync(path, within));
278
+ }
279
+ /** The asynchronous {@link followedKindSync}. */
280
+ export async function followedKind(path, within) {
281
+ return kindOf(await followedStat(path, within));
282
+ }
283
+ //# sourceMappingURL=link-following.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-following.js","sourceRoot":"","sources":["../src/link-following.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,7 @@
17
17
  * its stderr without a word); and the miss surfaced one review round later as
18
18
  * a HIGH. A required field is what makes `tsc` enumerate the callers.
19
19
  */
20
+ import { type FsSide } from './errors/fs-fault.js';
20
21
  import { VatError } from './errors/vat-error.js';
21
22
  import { type DirectoryRefusal } from './fs-utils.js';
22
23
  /**
@@ -38,6 +39,15 @@ export interface RefuseListingContext {
38
39
  * differs per caller and is why the crawler cannot supply it.
39
40
  */
40
41
  remedy: string;
42
+ /**
43
+ * Which side of the calling verb the listed directories are on — an input it reads
44
+ * (`source`), an output it wrote (`destination`), or VAT's own scratch (`environment`).
45
+ * Required: the refusal table decides the refusal by it, and only the caller knows which
46
+ * tree it is listing. In a CRAWL, a directory that is (or lies in, or holds) one of the
47
+ * crawl's declared `outputs` is the destination's whatever this says: the crawl's
48
+ * `outputs` are the one declaration of what the verb writes ({@link settleCrawlRefusal}).
49
+ */
50
+ side: FsSide;
41
51
  }
42
52
  /**
43
53
  * The caller's decision about a directory the listing cannot open.
@@ -64,6 +74,13 @@ export type UnreadablePolicy = {
64
74
  /**
65
75
  * Thrown by a listing under `{ refuse }` when a directory refused to be
66
76
  * listed. The message is the adopter's sentence — see {@link refusedListingMessage}.
77
+ *
78
+ * Coded `FS_FAULT` and carrying the classified fault as its `cause` (side from
79
+ * the caller's context, class from the errno), so the refusal is the schema
80
+ * table's for that side and class — never a code of its own. A refused listing
81
+ * is a filesystem call's refusal by construction, so every errno has a class:
82
+ * one the table does not list is the unlisted class, as {@link fsFaultOf}
83
+ * answers for the error it came from.
67
84
  */
68
85
  export declare class DirectoryListingRefusedError extends VatError {
69
86
  readonly refusal: DirectoryRefusal;
@@ -94,6 +111,33 @@ export declare function refusedListingMessage(refusal: DirectoryRefusal, context
94
111
  * @param refusal - The directory that could not be listed
95
112
  */
96
113
  export declare function settleRefusal(policy: UnreadablePolicy, refusal: DirectoryRefusal): void;
114
+ /**
115
+ * Whether `path` is on what a crawl's verb WRITES: one of its `outputs`, inside one, or holding
116
+ * one (the crawl base of a project a build writes into holds its output). THE one place a
117
+ * crawl derives a side from its `outputs` — the base `stat`, a refused listing at the base or
118
+ * beneath it, on every route. Paths are compared canonically (`canonicalPath`: real path, one
119
+ * spelling), so a directory git reports under its real path (`/private/var` for `/var`) or
120
+ * another drive-letter case is still the output.
121
+ *
122
+ * @param path - A directory the crawl met
123
+ * @param outputs - The trees the calling verb writes, `[]` for a verb that only reads
124
+ */
125
+ export declare function onCrawlOutput(path: string, outputs: readonly string[]): boolean;
126
+ /**
127
+ * The side of a path a crawl met: the destination's when it is on the verb's `outputs`
128
+ * ({@link onCrawlOutput}), else the side of the tree the verb reads — the policy's own
129
+ * (`source` when the policy degrades).
130
+ */
131
+ export declare function crawlSideOf(path: string, outputs: readonly string[], policy: UnreadablePolicy): FsSide;
132
+ /**
133
+ * Settle one refusal a CRAWL met: a refused directory that is on the crawl's `outputs`
134
+ * ({@link onCrawlOutput}) is the destination's; any other keeps the policy's own side.
135
+ *
136
+ * @param policy - The caller's decision
137
+ * @param refusal - The directory that could not be listed
138
+ * @param outputs - The trees the calling verb writes (the crawl's one declaration of them)
139
+ */
140
+ export declare function settleCrawlRefusal(policy: UnreadablePolicy, refusal: DirectoryRefusal, outputs: readonly string[]): void;
97
141
  /**
98
142
  * Refuse an omitted policy up front, by name, before any directory is listed.
99
143
  *
@@ -1 +1 @@
1
- {"version":3,"file":"listing-refusal.d.ts","sourceRoot":"","sources":["../src/listing-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,KAAK,gBAAgB,EAA0B,MAAM,eAAe,CAAC;AAG9E;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GACzC;IAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;CAAE,CAAC;AAE9D;;;GAGG;AACH,qBAAa,4BAA6B,SAAQ,QAAQ;IACxD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBAEvB,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,oBAAoB;CAIrE;AAmBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAOtG;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAGvF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,gBAAgB,CAO7H"}
1
+ {"version":3,"file":"listing-refusal.d.ts","sourceRoot":"","sources":["../src/listing-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAA+B,KAAK,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,KAAK,gBAAgB,EAA0B,MAAM,eAAe,CAAC;AAK9E;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GACzC;IAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;CAAE,CAAC;AAE9D;;;;;;;;;;GAUG;AACH,qBAAa,4BAA6B,SAAQ,QAAQ;IACxD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBAEvB,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,oBAAoB;CAIrE;AAgCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAOtG;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAGvF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAO/E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAGtG;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAGxH;AAaD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,gBAAgB,CAO7H"}
@@ -17,20 +17,43 @@
17
17
  * its stderr without a word); and the miss surfaced one review round later as
18
18
  * a HIGH. A required field is what makes `tsc` enumerate the callers.
19
19
  */
20
+ import { filesystemErrnoClass, fsFaultOf } from './errors/errno-table.js';
21
+ import { FS_FAULT_CODE, FsFaultError } from './errors/fs-fault.js';
20
22
  import { VatError } from './errors/vat-error.js';
21
23
  import { transientRefusalClause } from './fs-utils.js';
24
+ import { canonicalPath } from './path-containment.js';
25
+ import { relativeEscapesRoot } from './path-core.js';
22
26
  import { safePath, toForwardSlash } from './path-utils.js';
23
27
  /**
24
28
  * Thrown by a listing under `{ refuse }` when a directory refused to be
25
29
  * listed. The message is the adopter's sentence — see {@link refusedListingMessage}.
30
+ *
31
+ * Coded `FS_FAULT` and carrying the classified fault as its `cause` (side from
32
+ * the caller's context, class from the errno), so the refusal is the schema
33
+ * table's for that side and class — never a code of its own. A refused listing
34
+ * is a filesystem call's refusal by construction, so every errno has a class:
35
+ * one the table does not list is the unlisted class, as {@link fsFaultOf}
36
+ * answers for the error it came from.
26
37
  */
27
38
  export class DirectoryListingRefusedError extends VatError {
28
39
  refusal;
29
40
  constructor(refusal, context) {
30
- super('DIRECTORY_LISTING_REFUSED', refusedListingMessage(refusal, context));
41
+ super(FS_FAULT_CODE, refusedListingMessage(refusal, context), { cause: classifiedListingFault(refusal, context.side) });
31
42
  this.refusal = refusal;
32
43
  }
33
44
  }
45
+ /** The classified fault a refused listing stands for. */
46
+ function classifiedListingFault(refusal, side) {
47
+ return new FsFaultError({
48
+ side,
49
+ faultClass: filesystemErrnoClass(refusal.code),
50
+ errno: refusal.code,
51
+ path: refusal.directory,
52
+ origin: 'content',
53
+ action: 'list a directory',
54
+ cause: undefined,
55
+ });
56
+ }
34
57
  /**
35
58
  * What a refused listing costs, said so it is true on BOTH routes.
36
59
  *
@@ -82,6 +105,60 @@ export function settleRefusal(policy, refusal) {
82
105
  throw new DirectoryListingRefusedError(refusal, policy.refuse);
83
106
  policy.degrade(refusal);
84
107
  }
108
+ /**
109
+ * Whether `path` is on what a crawl's verb WRITES: one of its `outputs`, inside one, or holding
110
+ * one (the crawl base of a project a build writes into holds its output). THE one place a
111
+ * crawl derives a side from its `outputs` — the base `stat`, a refused listing at the base or
112
+ * beneath it, on every route. Paths are compared canonically (`canonicalPath`: real path, one
113
+ * spelling), so a directory git reports under its real path (`/private/var` for `/var`) or
114
+ * another drive-letter case is still the output.
115
+ *
116
+ * @param path - A directory the crawl met
117
+ * @param outputs - The trees the calling verb writes, `[]` for a verb that only reads
118
+ */
119
+ export function onCrawlOutput(path, outputs) {
120
+ if (outputs.length === 0)
121
+ return false;
122
+ const at = canonicalOrSpelled(path);
123
+ return outputs.some((output) => {
124
+ const out = canonicalOrSpelled(output);
125
+ return !relativeEscapesRoot(safePath.relative(out, at)) || !relativeEscapesRoot(safePath.relative(at, out));
126
+ });
127
+ }
128
+ /**
129
+ * The side of a path a crawl met: the destination's when it is on the verb's `outputs`
130
+ * ({@link onCrawlOutput}), else the side of the tree the verb reads — the policy's own
131
+ * (`source` when the policy degrades).
132
+ */
133
+ export function crawlSideOf(path, outputs, policy) {
134
+ if (onCrawlOutput(path, outputs))
135
+ return 'destination';
136
+ return 'refuse' in policy ? policy.refuse.side : 'source';
137
+ }
138
+ /**
139
+ * Settle one refusal a CRAWL met: a refused directory that is on the crawl's `outputs`
140
+ * ({@link onCrawlOutput}) is the destination's; any other keeps the policy's own side.
141
+ *
142
+ * @param policy - The caller's decision
143
+ * @param refusal - The directory that could not be listed
144
+ * @param outputs - The trees the calling verb writes (the crawl's one declaration of them)
145
+ */
146
+ export function settleCrawlRefusal(policy, refusal, outputs) {
147
+ const onOutput = 'refuse' in policy && onCrawlOutput(refusal.directory, outputs);
148
+ settleRefusal(onOutput ? { refuse: { ...policy.refuse, side: 'destination' } } : policy, refusal);
149
+ }
150
+ function canonicalOrSpelled(path) {
151
+ try {
152
+ // The refused directory and the verb's outputs are spelled by the verb: each is its own edge.
153
+ return toForwardSlash(canonicalPath(path, path));
154
+ }
155
+ catch (error) {
156
+ // A directory that refused its listing may refuse resolution too: its spelling is all there is.
157
+ if (fsFaultOf(error) === undefined)
158
+ throw error;
159
+ return toForwardSlash(safePath.resolve(path));
160
+ }
161
+ }
85
162
  /**
86
163
  * Refuse an omitted policy up front, by name, before any directory is listed.
87
164
  *
@@ -98,7 +175,7 @@ export function settleRefusal(policy, refusal) {
98
175
  */
99
176
  export function requireUnreadablePolicy(policy, api) {
100
177
  if (policy === undefined) {
101
- throw new TypeError(`${api}: \`unreadable\` is required — pass { refuse: { root, remedy } } to stop on a directory the listing cannot open, ` +
178
+ throw new TypeError(`${api}: \`unreadable\` is required — pass { refuse: { root, remedy, side } } to stop on a directory the listing cannot open, ` +
102
179
  'or { degrade: (refusal) => … } to keep going and report the gap yourself.');
103
180
  }
104
181
  }
@@ -1 +1 @@
1
- {"version":3,"file":"listing-refusal.js","sourceRoot":"","sources":["../src/listing-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAyB,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA4C3D;;;GAGG;AACH,MAAM,OAAO,4BAA6B,SAAQ,QAAQ;IAC/C,OAAO,CAAmB;IAEnC,YAAY,OAAyB,EAAE,OAA6B;QAClE,KAAK,CAAC,2BAA2B,EAAE,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAC5E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,kBAAkB,GACtB,8EAA8E;IAC9E,mDAAmD,CAAC;AAEtD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAyB,EAAE,OAA6B;IAC5F,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,kBAAkB,QAAQ,GAAG,CAAC;IACvF,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS;QAC9B,CAAC,CAAC,GAAG,sBAAsB,CAAC,OAAO,CAAC,IAAI,CAAC,6EAA6E;QACtH,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACnB,OAAO,WAAW,KAAK,iBAAiB,OAAO,CAAC,IAAI,MAAM,kBAAkB,KAAK,MAAM,EAAE,CAAC;AAC5F,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,MAAwB,EAAE,OAAyB;IAC/E,IAAI,QAAQ,IAAI,MAAM;QAAE,MAAM,IAAI,4BAA4B,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACvF,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAoC,EAAE,GAAW;IACvF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,SAAS,CACjB,GAAG,GAAG,mHAAmH;YACvH,2EAA2E,CAC9E,CAAC;IACJ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"listing-refusal.js","sourceRoot":"","sources":["../src/listing-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAe,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAyB,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAqD3D;;;;;;;;;;GAUG;AACH,MAAM,OAAO,4BAA6B,SAAQ,QAAQ;IAC/C,OAAO,CAAmB;IAEnC,YAAY,OAAyB,EAAE,OAA6B;QAClE,KAAK,CAAC,aAAa,EAAE,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxH,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED,yDAAyD;AACzD,SAAS,sBAAsB,CAAC,OAAyB,EAAE,IAAY;IACrE,OAAO,IAAI,YAAY,CAAC;QACtB,IAAI;QACJ,UAAU,EAAE,oBAAoB,CAAC,OAAO,CAAC,IAAI,CAAC;QAC9C,KAAK,EAAE,OAAO,CAAC,IAAI;QACnB,IAAI,EAAE,OAAO,CAAC,SAAS;QACvB,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,kBAAkB;QAC1B,KAAK,EAAE,SAAS;KACjB,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,kBAAkB,GACtB,8EAA8E;IAC9E,mDAAmD,CAAC;AAEtD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAyB,EAAE,OAA6B;IAC5F,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,kBAAkB,QAAQ,GAAG,CAAC;IACvF,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS;QAC9B,CAAC,CAAC,GAAG,sBAAsB,CAAC,OAAO,CAAC,IAAI,CAAC,6EAA6E;QACtH,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACnB,OAAO,WAAW,KAAK,iBAAiB,OAAO,CAAC,IAAI,MAAM,kBAAkB,KAAK,MAAM,EAAE,CAAC;AAC5F,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,MAAwB,EAAE,OAAyB;IAC/E,IAAI,QAAQ,IAAI,MAAM;QAAE,MAAM,IAAI,4BAA4B,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACvF,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,OAA0B;IACpE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QAC7B,MAAM,GAAG,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QACvC,OAAO,CAAC,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9G,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAA0B,EAAE,MAAwB;IAC5F,IAAI,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC;QAAE,OAAO,aAAa,CAAC;IACvD,OAAO,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC5D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAwB,EAAE,OAAyB,EAAE,OAA0B;IAChH,MAAM,QAAQ,GAAG,QAAQ,IAAI,MAAM,IAAI,aAAa,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACjF,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACpG,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAY;IACtC,IAAI,CAAC;QACH,8FAA8F;QAC9F,OAAO,cAAc,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,gGAAgG;QAChG,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,SAAS;YAAE,MAAM,KAAK,CAAC;QAChD,OAAO,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAoC,EAAE,GAAW;IACvF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,SAAS,CACjB,GAAG,GAAG,yHAAyH;YAC7H,2EAA2E,CAC9E,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * One path segment as the strictest volume VAT writes onto compares it. Pure.
3
+ *
4
+ * Two segments with one fold are ONE entry on some volume, so a set of names holding both lands
5
+ * differently depending on where it is written — an archive's entry list and a plugin's `files:`
6
+ * destinations are both judged by it.
7
+ */
8
+ /**
9
+ * A path segment as a filesystem that folds names compares it: the stream suffix and trailing dots
10
+ * and spaces cut (NTFS), decomposed, then case-folded UP and then down — NTFS compares upper-cased
11
+ * names, so `ß` and `SS` are one name, and a dotless `ı` (U+0131) is `I` there, which lower-casing
12
+ * alone leaves apart from `i`. The cut is made on every host, so a POSIX directory really named
13
+ * `skills:notes` compares as `skills` too.
14
+ *
15
+ * STRICTER than any one volume, on purpose. The decomposition is by compatibility (NFKD), where
16
+ * the volumes that normalise (APFS, HFS+) do so canonically: `x².md` and `x2.md` fold together
17
+ * here and are two names everywhere. And the stream cut makes `10:00.log` and `10:30.log` one
18
+ * name, which they are only on NTFS. A caller that refuses on equal folds over-refuses those, and
19
+ * says which rule matched.
20
+ *
21
+ * @param segment - One path segment, never a path
22
+ * @returns The segment's fold: equal for two names that are one entry on some volume
23
+ */
24
+ export declare function foldedSegment(segment: string): string;
25
+ //# sourceMappingURL=name-fold.d.ts.map