# `avril:path` Lexical path manipulation with no filesystem access: joining, normalization, parsing, and `file:` URL conversion. The algorithms follow Node's `path` semantics for POSIX and Windows, including drive roots, drive-relative forms, UNC paths, extended `\\?\` / `\\.\` namespaces, case-insensitive drive and component comparison for `win32.relative`, and the `parse`/`format` dot and extension rules. There is deliberately no glob support and no filesystem lookup of any kind; `realPath` stays in `avril:std`, where symlinks and permissions live. ```ts import * as path from "avril:path"; import * as posix from "avril:path/posix"; import * as win32 from "avril:path/win32"; path.join("/a", "b", "../c"); // "/a/c" on POSIX hosts win32.join("C:\\a", "b", "..\\c"); // "C:\\a\\c" on any host posix.relative("/a/b/c", "/a/d/e"); // "../../d/e" path.toFileUrl("/a b/c#d").href; // "file:///a%20b/c%23d" path.fromFileUrl(new URL("file:///a%20b/c")); // "/a b/c" ``` `avril:path` exports the native flavor for the target OS directly (`join`, `resolve`, `normalize`, `relative`, `dirname`, `basename`, `extname`, `isAbsolute`, `parse`, `format`, `toNamespacedPath`, `fromFileUrl`, `toFileUrl`, `sep`, `delimiter`) plus frozen `posix` and `win32` namespaces with the same surface. `avril:path/posix` and `avril:path/win32` export the same names pinned to one flavor on any host, so explicit Windows paths work lexically on Unix and vice versa. There is no default export; `import * as path` is idiomatic. `posix`/`win32` namespace objects carry no nested `posix`/`win32` fields. ## Authority Pure operations (`join`, `normalize`, `dirname`, `basename`, `extname`, `isAbsolute`, `parse`, `format`, and the POSIX `toNamespacedPath` passthrough) never touch the working directory and run under `avril run -0` with no grants. `resolve`, `relative`, and `win32.toNamespacedPath` (which resolves its input first) consult the working directory only when relative, root-relative, or drive-relative input actually needs it: inputs that are already fully qualified need no read grant, and importing the module itself never requires any authority. Each lookup rechecks live capability state, so a `pledge()` narrowing read access is honored by the next call; there is no cached directory. A missing grant throws the normal `PermissionDenied`, and `toFileUrl`/`fromFileUrl` need no permissions at all. ## Windows notes Windows resolution never reads per-drive hidden environment entries and needs no environment permission: when the requested drive differs from the working directory's drive (or the directory has no drive letter, as on POSIX hosts), resolution falls back to that drive's root instead of consulting another drive's remembered directory. Explicit `posix` calls on Windows use the POSIX view of the working directory (separators converted, drive indicator truncated), matching Node. Extended-namespace paths (`\\?\`, `\\.\`, and their forward-slash spellings) work in every lexical operation but are rejected by `toFileUrl` and `fromFileUrl` with a `TypeError`. UNC servers are validated strictly (control characters, spaces, and `#?%@/:[]` are rejected rather than left for the URL parser to strip or reinterpret, except inside bracketed IPv6 literals like `\\[::1]\share`, which are accepted); ASCII case folds for a deterministic representation, `localhost` is refused as a server (WHATWG would collapse it and lose the share — including Unicode aliases that normalize to it, which are rechecked on the constructed URL), and other non-ASCII names pass through IDNA to their punycode form. ## File URLs `toFileUrl(path)` requires an absolute path: `/`-rooted for POSIX, or a fully qualified drive (`C:\…`) or UNC (`\\server\share\…`) path for Windows. The Windows input is normalized with the lexical `win32` algorithm first, so `.`/`..` can never cross a drive or share root (unlike a bare URL parse, which does not know the share boundary); explicit trailing slashes are preserved. Drive-relative (`C:foo`), root-only (`\foo`), and relative inputs throw `TypeError` — as does the bare drive `C:`, which Node accepts but which names no directory here — along with NUL bytes and lone surrogates. Conversion is purely lexical and never consults the working directory. The percent-encoding table matches Node's `pathToFileURL` exactly (verified differentially over the full ASCII range, including `~`, `|`, `^`, `[`, `]`); ordinary separators and coherent trailing slashes survive, and `.`/`..` normalize per URL semantics. `fromFileUrl(url)` accepts a genuine `URL` instance or an absolute `file:` URL string; forged objects and other schemes throw `TypeError`. Raw strings are rejected up front for lone surrogates, NUL bytes, and tab/newline characters (the URL parser would otherwise normalize those silently); a `URL` instance is already parsed, so a surrogate it normalized to U+FFFD on construction stays one. Non-local POSIX hosts are rejected (`localhost` is local); any other Windows host becomes a UNC path, with `localhost` meaning the local machine (`file://server/` with no share, and drive-relative `file:///C:` / `file:///C:foo`, are rejected as incomplete). Encoded separators (`%2F` everywhere, `%5C` for Windows), malformed escapes, invalid UTF-8, NUL bytes, and surrogate escapes throw instead of decoding quietly. Unlike Node, a nonempty query or fragment is rejected rather than silently dropped.