path-lib 1.0.0

Cross-platform application and OS directory resolution for Rust, with user and global scopes, hardened sub-path joining, and guarded filesystem operations. Zero dependencies.
# v1.0.0 — Stable API

`path-lib` reaches 1.0. The public API, the directory tables, and the guarantees of the filesystem operations are frozen for the 1.x line under [`STABILITY-1.0.md`](../STABILITY-1.0.md). Every later 1.x release is backwards compatible with this one.

## What `path-lib` Is

Application and OS directory resolution for Linux, macOS, and Windows. `App` resolves the data, config, cache, logs, state, runtime, temp, and documents directories of one application for the current user, the whole machine, or one chosen folder. Sub-paths built on those directories are validated before they exist, and the filesystem operations refuse to overwrite, follow links, or delete critical paths. Zero runtime dependencies.

```rust
use path_lib::{App, Kind};

let app = App::new("HiveDB")?.with_env("HIVEDB")?;

let wal = app.create(Kind::Data, "db/wal")?;
path_lib::write_atomic(app.config_at("hivedb.toml")?, "port = 7000\n")?;
# Ok::<(), path_lib::Error>(())
```

```toml
[dependencies]
path-lib = "1.0"
```

## New Since 0.1.0

### Directory overrides

- **`App::with_dir(kind, dir)`** replaces one kind's directory, for example to put data on a mounted volume. The path must be absolute and is cleaned. A directory that another kind already uses is rejected with `Reason::SharedDir`, so the rule that no two kinds share a directory still holds after an override.
- **`App::with_env(prefix)`** reads `<PREFIX>_<KIND>_DIR` for every kind (`HIVEDB_DATA_DIR`, `HIVEDB_LOGS_DIR`, ...) and applies each set, non-empty value through `with_dir`. A bad value fails with an error that names the variable as `NAME=value`. This is the usual way to relocate a service's directories in a container or a service manager without code changes.
- **`App::dirs()`** iterates every kind with its directory, and **`Kind::env_name()`** gives the variable suffix `with_env` uses.

### Portable detection

**`App::auto(name)`** chooses a portable layout next to the executable when a `.portable` file (`PORTABLE_MARKER`) sits beside it, and per-user directories otherwise. Ship one binary in an installer and in a zip; the zip carries the marker. The check happens once, at construction.

### Atomic file replacement

**`write_atomic(path, contents)`** writes a hidden temporary sibling, flushes it, and renames it over `path`, so readers see either the old file or the complete new one, even across a crash or power loss. On Unix the parent directory is flushed too, and an existing file's permission bits are kept, so a `0600` secrets file stays `0600`. A symbolic link is written through and kept. Directories, broken links, and paths without a file name are refused. On any failure the temporary file is removed and the original is untouched.

`write_atomic` is the one operation in the crate whose purpose is to overwrite. Every other operation still refuses to.

### Home expansion

**`expand_home(input)`** turns `~` and `~/rest` (also `~\rest` on Windows) into paths under the home directory and returns everything else unchanged, so it is safe to apply to every path read from configuration. `~name` is rejected with `Reason::OtherUser`.

### Changes to existing items

- **`Kind` and `Scope` are `#[non_exhaustive]`.** A kind or scope can be added in a minor release. An exhaustive `match` written against 0.1.0 needs a wildcard arm; iterating `Kind::ALL` is unaffected.
- **New `Reason` variants:** `NotAbsolute`, `SharedDir`, `OtherUser`. `Reason` was already `#[non_exhaustive]`.
- **`move_to` error documentation corrected.** When the cross-volume fallback copies successfully but deleting the source fails, the error's `op` is `"remove moved source"`. The destination is complete; the source may be partly deleted, because a directory's contents can be removed before the directory itself fails. Treat the destination as the surviving version. The 0.1.0 documentation said both copies remain, which is not guaranteed.

## The Stable Surface

Every item re-exported from the crate root is part of the 1.x contract.

- **Application directories:** `App` (constructors `new`, `global`, `portable`, `auto`; overrides `with_dir`, `with_env`; accessors, `*_at` sub-paths, `dirs`; `ensure`, `ensure_all`, `create`, `list`, `remove`, `find`), `Kind`, `Scope`, `PORTABLE_MARKER`.
- **Path helpers:** `join`, `sanitize`, `clean`, and the limits `MAX_DEPTH`, `MAX_INPUT_BYTES`, `MAX_SEGMENT_BYTES`.
- **OS folders:** `home`, `documents`, `desktop`, `downloads`, `pictures`, `music`, `videos`, `root`, `temp`, `exe`, `exe_dir`, `expand_home`, `os`, `Os`.
- **Filesystem operations:** `make_dir`, `make_file`, `exists`, `list`, `remove`, `copy`, `move_to`, `find`, `write_atomic`, `MAX_WALK_DEPTH`.
- **Errors:** `Error`, `Reason`, `Result`, `PREVIEW_CHARS`.

The per-platform directory tables are part of the contract: moving a default directory requires a major version. The full list of frozen items and guarantees, and what may still change in minor releases, is in [`STABILITY-1.0.md`](../STABILITY-1.0.md).

## Verification

- **Tests.** 184 tests on Windows and 189 on Linux, where extra Unix-only tests cover unreadable directories, special files, permission preservation, and symbolic links. Property tests run 4096 cases each. All pass on stable and on MSRV 1.85.0.
- **Coverage.** Line coverage measured with `cargo-llvm-cov` is 96.0% on Windows and 97.2% on Linux. The unexecuted lines are branches for the other operating system and OS failures that cannot be forced safely, such as `current_exe` failing.
- **Hard-to-reach paths.** Injected rename functions and walk-depth limits exercise the cross-volume move fallback, a failed final rename of a staged copy, a failed rename in `write_atomic`, and the depth limit.
- **Lints and supply chain.** `clippy::pedantic` is clean for `x86_64-pc-windows-msvc`, `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`, `x86_64-apple-darwin`, and `x86_64-unknown-freebsd`. `cargo deny` and `cargo audit` are clean. There are no runtime dependencies.

The property test `prop_join_slash_style_is_irrelevant` could generate Windows device names such as `con` and then fail. It now asserts that both slash styles produce the same outcome, success or the same rejection. The library code was not affected.

## Performance

| Operation | 0.1.0 | 1.0.0 |
|---|---|---|
| `home()` after first call | 0.94 ns | 0.94 ns |
| `app.data()` | 0.23 ns | 0.23 ns |
| `app.data_at("db/main")` | 78 ns | 77.2 ns |
| `join(base, "db")` | 57 ns | 56.3 ns |
| `App::new("HiveDB")` | 567 ns | 581 ns |

The 1.0.0 numbers match 0.1.0 within noise. `write_atomic` is not benchmarked: its cost is dominated by the flushes to disk (the file, and on Unix its directory), which depend on the storage device rather than the crate. Full methodology and results are in [`docs/PERFORMANCE.md`](../PERFORMANCE.md).

## Compatibility

- **MSRV:** Rust 1.85, frozen for 1.x
- **Edition:** 2024
- **Platforms:** Linux, macOS, Windows (FreeBSD and other BSDs follow the Linux rules)
- **Public API:** additive over 0.1.0, except that `Kind` and `Scope` are now `#[non_exhaustive]`.

## Upgrading From 0.1

Bump the version.

```toml
[dependencies]
path-lib = "1.0"
```

The only code change that can be needed is a wildcard arm in a `match` over `Kind` or `Scope`:

```rust
use path_lib::Kind;

fn retention_days(kind: Kind) -> u32 {
    match kind {
        Kind::Logs => 30,
        Kind::Cache | Kind::Temp => 7,
        _ => 0,
    }
}
```

## Out of Scope

These were considered for 1.0 and left out on purpose:

- **Async filesystem operations.** They would need a runtime dependency. The operations are short and synchronous; async code can run them on a blocking thread.
- **File watching.** It needs OS notification APIs and, in practice, a dependency.
- **Recursive `**` glob patterns.** `find` matches single names with `*` and `?` anywhere under a root, which covers the common case.
- **ACL and permission management.** Beyond the `0700`/`0755` modes `App` uses and the permission bits `write_atomic` preserves, permissions are left to the caller.
- **Expanding `$VARS` inside paths.** A configuration value could then pull arbitrary environment state into a path. `expand_home` handles `~` and nothing else.