{"id":50446730,"url":"https://github.com/mearman/cascade","last_synced_at":"2026-06-16T08:00:57.884Z","repository":{"id":360873624,"uuid":"1251438765","full_name":"Mearman/cascade","owner":"Mearman","description":"Cross-platform cloud storage filesystem client in Rust: on-demand file access, nested .cascade config with directory-walk precedence, offline pinning, policy-driven lifecycle, P2P block sync, and multi-backend support. Native platform APIs (FSKit on macOS, ProjFS on Windows, FUSE on Linux) with NFS/WebDAV fallback, no kernel extensions.","archived":false,"fork":false,"pushed_at":"2026-06-12T03:22:59.000Z","size":3431,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T05:22:42.713Z","etag":null,"topics":["cloud-storage","cross-platform","file-provider","file-sync","filesystem","fskit","fuse","google-drive","nfs","on-demand","p2p","projfs","rust","s3","sqlite","tokio","virtual-filesystem","webdav"],"latest_commit_sha":null,"homepage":"https://github.com/Mearman/cascade/releases/latest","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Mearman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-27T15:21:03.000Z","updated_at":"2026-06-12T03:22:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Mearman/cascade","commit_stats":null,"previous_names":["mearman/cascade"],"tags_count":369,"template":false,"template_full_name":null,"purl":"pkg:github/Mearman/cascade","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mearman%2Fcascade","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mearman%2Fcascade/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mearman%2Fcascade/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mearman%2Fcascade/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Mearman","download_url":"https://codeload.github.com/Mearman/cascade/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Mearman%2Fcascade/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34396429,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloud-storage","cross-platform","file-provider","file-sync","filesystem","fskit","fuse","google-drive","nfs","on-demand","p2p","projfs","rust","s3","sqlite","tokio","virtual-filesystem","webdav"],"created_at":"2026-05-31T22:00:49.942Z","updated_at":"2026-06-16T08:00:57.828Z","avatar_url":"https://github.com/Mearman.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cascade\n\n[![CI](https://img.shields.io/github/actions/workflow/status/Mearman/cascade/ci.yml?branch=main\u0026label=CI)](https://github.com/Mearman/cascade/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/Mearman/cascade?sort=semver)](https://github.com/Mearman/cascade/releases/latest)\n[![License](https://img.shields.io/github/license/Mearman/cascade)](LICENSE)\n[![GitHub](https://img.shields.io/badge/GitHub-181717?logo=github\u0026logoColor=white)](https://github.com/Mearman/cascade)\n[![Homebrew](https://img.shields.io/badge/Homebrew-FBB040?logo=homebrew\u0026logoColor=white)](https://github.com/Mearman/homebrew-cascade)\n[![Scoop](https://img.shields.io/badge/Scoop-205081?logo=data:image/svg%2Bxml%3Bbase64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTExIDJoMnY5aC0yek0xMiAyMmE3IDcgMCAwIDAgNy03SDVhNyA3IDAgMCAwIDcgN3oiIGZpbGw9IiNmZmYiLz48L3N2Zz4K\u0026logoColor=white)](https://github.com/Mearman/scoop-cascade)\n\n\u003e Cross-platform cloud storage filesystem client built in Rust. On-demand file access, nested `.cascade` config with directory-walk precedence, offline pinning, policy-driven lifecycle management, P2P block sync, and multi-backend support. Uses native platform APIs (File Provider on macOS, ProjFS on Windows, FUSE on Linux) with NFS fallback — no kernel extensions required.\n\nRust (edition 2024) · Swift (macOS File Provider extension) · SQLite state · Tokio async runtime\n\n## Why\n\nEvery cloud provider ships their own desktop client. They all share the same shape — pick a folder, sync it, hope your disk is bigger than the data. Google Drive for Desktop has a stream mode that loads files on demand, but it's macOS- and Windows-only, leaves you guessing what's actually local, and behaves badly offline. Dropbox, OneDrive, iCloud — same story, each with their own client, their own quirks, their own opinions about your filesystem layout.\n\n[rclone](https://github.com/rclone/rclone) solves the cross-cloud unification well, but its mount story is per-OS fiddly: each platform needs its own filesystem driver, sometimes root, sometimes a separate install. The config is global; if you want different rules for `Work/` than `Personal/` you reach for a wrapper script.\n\nCascade is what happens when you want a single tool, with a single config language, that:\n\n- Presents every backend — Google Drive, S3, the local filesystem — as one virtual tree under one mount point, mounted with the same shape on macOS, Linux, and Windows.\n- Streams file content on demand. Directory listings work without downloading anything; only `open()` triggers a fetch.\n- Uses the OS's native filesystem APIs — FSKit on macOS (15.4+), FUSE on Linux, WebDAV via the built-in `WebClient` on Windows — with no kernel extension, no admin escalation, no third-party driver.\n- Takes its rules from `.cascade` files scattered through the tree (gitignore-style precedence), so behaviour can vary per directory without restarting anything.\n- Mirrors the actual Drive web UI — `My Drive`, `Shared drives`, `Shared with me`, `Bin` — instead of pretending every account is one flat folder.\n\nThe bet is that one filesystem client that works the same on every laptop is more useful than five vendor clients plus a folder full of rclone scripts.\n\n## Getting started\n\n### Prerequisites\n\n- Rust toolchain pinned in [`rust-toolchain.toml`](rust-toolchain.toml) (currently 1.96.0, edition 2024). `rustup` installs it automatically on first build.\n    - **rustup (recommended)** — the project pins its Rust toolchain in `rust-toolchain.toml`. Install rustup from \u003chttps://rustup.rs/\u003e so local builds, the pre-push hook, and CI all use the same compiler.\n- macOS: Xcode Command Line Tools (for Swift File Provider and FSKit extensions). FSKit requires macOS 15.4+ (Sequoia).\n- Linux: `libfuse3` runtime libraries for the FUSE presenter (`apt install libfuse3-dev` on Debian/Ubuntu, `yum install fuse3-devel` on RHEL/Fedora). NFS fallback additionally needs root to bind a privileged port.\n- Windows: the built-in `WebClient` service for the WebDAV mount. It ships with Windows but is often set to manual start — `sc config WebClient start= auto` (in an elevated shell) makes it come up automatically. A native ProjFS presenter (`presenter-projfs`) is implemented and tried first by `cascade start` — it serves directory browsing and on-demand file reads through an engine-backed content provider — with WebDAV as the fallback if ProjFS cannot start (for example when the Client-ProjFS optional feature is disabled).\n\n### Install\n\nPre-built binaries are published on each release from [GitHub Releases](https://github.com/Mearman/cascade/releases).\n\n- **macOS and Linux (Homebrew)**:\n\n  ```bash\n  brew install Mearman/cascade/cascade\n  ```\n\n  The [`Formula/cascade.rb`](Formula/cascade.rb) bottle covers both `aarch64` and `x86_64` on macOS and Linux.\n\n- **Linux (direct download)**: grab `cascade-aarch64-linux.tar.gz` or `cascade-x86_64-linux.tar.gz` from the release, extract, and place `cascade` on your `PATH`:\n\n  ```bash\n  curl -L https://github.com/Mearman/cascade/releases/latest/download/cascade-x86_64-linux.tar.gz | tar -xz\n  install -m 0755 cascade ~/.local/bin/\n  ```\n\n- **Windows (Scoop)**:\n\n  ```powershell\n  scoop bucket add cascade https://github.com/Mearman/scoop-cascade\n  scoop install cascade\n  ```\n\n- **Windows (direct download)**: grab `cascade-x86_64-windows.zip` from the release, extract, and place `cascade.exe` on your `PATH`.\n\n- **From source (any platform)**: see [Build](#build) below.\n\n### Build\n\n```bash\ncargo build --release\n\n# Specific presenter\ncargo build --release --features presenter-nfs\ncargo build --release --features presenter-fileprovider  # macOS only\n\n# Including Swift extensions (macOS)\ncargo build --release\nxcodebuild -project swift/CascadeFileProvider.xcodeproj -scheme CascadeFileProviderHost -configuration Release -destination \"platform=macOS\" build\ncd swift/CascadeFSKit \u0026\u0026 xcodebuild\n```\n\nThe `Makefile` wraps the common workflows:\n\n```bash\nmake release   # cargo build --release\nmake build     # cargo build (debug)\nmake start     # build then run the daemon\nmake stop      # stop a running daemon\nmake dev       # cargo watch with debug logging\nmake debug     # run the release binary with RUST_LOG=debug\n```\n\n### Run\n\n```bash\ncascade backend add gdrive --name personal\ncascade start\ncascade status\ncascade pin Documents/Accounts/\ncascade stop\n```\n\n### Running as a background service\n\n`cascade service` manages the daemon as an OS background service. The default scope is per-user and requires no administrator rights: a launchd `LaunchAgent` on macOS, a systemd `--user` unit on Linux, and a logon Scheduled Task on Windows.\n\n```bash\ncascade service install    # write the service definition and register it\ncascade service start      # start the registered service\ncascade service status     # show whether the service is registered and running\ncascade service stop       # stop the service\ncascade service uninstall  # deregister the service and remove its definition\n```\n\nThe scope is selected in this order: an explicit `--user` or `--system` flag, then inference from the session (an interactive GUI desktop session picks the user scope; a headless host picks the system scope), then — only when there is both a GUI desktop and a terminal — a prompt that defaults to the user scope. The chosen scope and the reason for it are always printed. The `--system` scope installs a machine-wide service: on Linux it writes a systemd system unit (requires root); on macOS and Windows it errors clearly — a system-scoped service in session 0 cannot drive File Provider, FSKit, ProjFS, or WebDAV, which all require a user session, and this is a documented platform limitation rather than a missing feature.\n\nIf you installed via Homebrew, `brew services start cascade` works out of the box — the formula ships a `service` block that delegates to `cascade start`.\n\nThe daemon exits cleanly with a log message when no backends are configured, so a freshly-installed service does not crash-loop before `cascade backend add` has been run.\n\n## Build, test, and lint\n\n```bash\ncargo test --workspace              # all unit tests\ncargo test --test integration       # integration tests (require mock backend)\ncargo test -p backend-gdrive        # single crate\ncargo clippy --workspace            # lint\ncargo fmt --check                   # format check\n```\n\n## Architecture\n\nThe design is documented in full at [`docs/design.md`](docs/design.md). This section covers the high-level structure.\n\n```\n┌──────────────────────────────────────────────────────────┐\n│  Platform Layer (per-OS)                                 │\n│  macOS:   File Provider · FSKit (15.4+) · WebDAV · NFS   │\n│  Linux:   FUSE · NFS (root)                              │\n│  Windows: ProjFS · WebDAV via WebClient                  │\n│  Universal fallback: NFS server · WebDAV server          │\n└────────────────────┬─────────────────────────────────────┘\n                     │ VfsPresenter trait\n┌────────────────────▼─────────────────────────────────────┐\n│  Cascade Engine (Rust)                                   │\n│  VFS Tree · .cascade config walk · Cache Manager         │\n│  Backend trait · Expression Evaluator · P2P Engine (BEP) │\n└──────────────────────────────────────────────────────────┘\n```\n\n`cascade start` tries the platform-preferred presenters in order and falls back as each one fails: on macOS that's FSKit → WebDAV → NFS; on Linux it's FUSE → NFS; on Windows it's ProjFS → WebDAV (mounted via `net use *` against the built-in `WebClient` service). The Windows ProjFS presenter implements the full callback table — directory enumeration, placeholder info, file-name queries, on-demand reads, notifications, and cancellation — and serves file contents through an engine-backed content provider. WebDAV remains the fallback for when ProjFS cannot start (for example when the Client-ProjFS optional feature is disabled on the machine).\n\nCommunication between the platform layer and the engine uses a Unix domain socket with a length-prefixed JSON protocol, shared by the CLI, the macOS File Provider and FSKit extensions, and any future GUI.\n\n### Workspace structure\n\n```\ncrates/\n  engine/                 VFS tree, backend trait, cache manager, sync, state DB\n  cascade-config/         .cascade parsing (4 formats), merge, directory walk\n  expr/                   Conditional expression parser (PEG via pest) and evaluator\n  p2p/                    BEP protocol, peer discovery (LAN, gossip, announce, Mainline DHT), block store\n  cascade-announce-wire/  Announce-server wire contract: signed-candidate types, HMAC write auth, wasm-safe handler\n  backend-gdrive/         Google Drive (Drive API v3, OAuth2 device code)\n  backend-s3/             S3-compatible\n  backend-local/          Local filesystem (adopt-and-sync)\n  backend-p2p/            P2P-only content-addressed store (no cloud authority; blocks local, metadata in SQLite)\n  presenter-nfs/          NFSv3 server\n  presenter-fuse/         Linux FUSE presenter\n  presenter-webdav/       WebDAV server presenter (cross-platform)\n  presenter-fileprovider/ macOS File Provider bridge (Rust side)\n  presenter-fskit/        macOS FSKit bridge (Rust side, macOS 15.4+)\n  presenter-projfs/       Windows ProjFS presenter\n  cascade/                Binary crate (CLI entry point and daemon)\n  relay-server/           Opaque byte-pipe relay (binary): pairs two WebSocket clients by session ID for WAN NAT traversal, HMAC-gated, never inspects payload\nswift/\n  CascadeFileProvider/    macOS File Provider extension\n  CascadeFSKit/           macOS FSKit extension (15.4+)\nworkers/\n  announce/               Stateless announce-server Cloudflare Worker (workers-rs, KV soft state)\n```\n\nIntegration tests live inside each crate's `tests/` directory rather than at workspace root.\n\n### Key abstractions\n\n- **`Backend` trait** — every cloud provider and the local filesystem implement this. The engine never sees provider-specific APIs. Each backend crate exposes `create_backend(config) -\u003e Result\u003cBox\u003cdyn Backend\u003e\u003e`.\n- **`VfsTree`** — composes multiple backends into a single tree, routed by longest-prefix match. Cross-backend moves trigger download + upload + delete.\n- **`VfsPresenter` trait** — platform-agnostic interface for presenting the VFS to the OS. Compile-time selection: FSKit on macOS (with WebDAV and NFS fallbacks), FUSE on Linux (with NFS fallback), ProjFS on Windows with WebDAV via WebClient as fallback, NFS as universal fallback.\n- **`.cascade` config walk** — like `.gitignore`: files in each directory layer with child-overrides-parent precedence. Four formats (gitignore-style, TOML, YAML, JSON) all deserialise to `CascadeConfig`.\n- **Expression language** — PEG grammar evaluated against `EvalContext` (file, device, disk, network, power, time, peer). Used for conditional rules in `.cascade` files.\n- **P2P engine** — based on Syncthing's BEP v1. Sits between VFS and cache as an optimisation layer, not as a backend. Cloud remains the authority for cloud-backed folders.\n- **Node management plane** — a trusted device administers another over the authenticated peer connection. Authority is modelled as capability grants (a verb over a scope) held on the managed node; the `ManageRequest` / `ManageResponse` BEP frames carry the full command set — status read, pin/unpin, cache evict/warm, config push, policy set, backend add/remove, daemon restart/stop, and grant delegation/revocation — and dispatch into the same handlers the local CLI drives, gated by per-command authorisation and an append-only audit log. The dangerous verbs (backend, lifecycle, grant administration) are never satisfied by a node-wide grant and must be granted explicitly for a folder scope, and a delegated grant must be a subset of authority the caller can itself exercise. `cascade grant add|list|revoke|audit` administers the capabilities a node confers; `cascade remote \u003cdevice-id\u003e` drives a target reached over the discovery and connectivity stack. On top of the on-node grant list sits the signed capability-token model: `cascade token issue|revoke|list` mints and revokes tokens signed by the issuing node's real device key, which the bearer carries and presents with `--token \u003cfile\u003e` on `cascade remote`. The node verifies the signature, expiry, and revocation list, then authorises the carried grant through the same path an on-node grant takes. Delegated tokens form bounded chains — each hop can only narrow authority, never widen it — and a token's expiry is clamped to its parent's, so a delegate never outlives the authority it derived from.\n\n### State database\n\nSQLite at `~/.config/cascade/state.db`. Tables: `files`, `backends`, `pin_rules`, `lifecycle_policies`, `config_cache`, `sync_cursors`, `p2p_peers`, `p2p_block_index`, `grants`, `manage_audit`. Full schema in [`docs/design.md`](docs/design.md).\n\n## Conventions\n\n- **Rust edition 2024** with `async_trait` for the Backend and VfsPresenter traits.\n- **Error handling** with `anyhow` for applications and `thiserror` for library crate error types.\n- **Serde** for all serialisation (JSON wire protocol, TOML/YAML/JSON config, SQLite bridge types).\n- **Platform context** is injected via traits (`PlatformContext`), not pulled from global APIs. Each OS provides its own implementation behind the same contract.\n- **Backend crates** are self-contained: each exposes exactly one `create_backend` function and implements the `Backend` trait.\n- **Config merge semantics** differ by concern: ignore rules and pins accumulate, lifecycle policies are child-first first-match-wins, cache settings are nearest-wins, device config is root-only.\n- **Strict workspace lints** in `Cargo.toml`: pedantic, nursery, and cargo Clippy groups are denied, plus `unwrap_used`, `expect_used`, `indexing_slicing`, `string_slice`, and `unsafe_code`. New code must satisfy these without `#[allow]` escapes.\n\n## Gotchas and quirks\n\n- **Linux FUSE runs without root.** The FUSE presenter mounts as the calling user via `fusermount3`. NFS fallback is different — binding a privileged port for NFS still needs `sudo`, so if FUSE is unavailable (missing `libfuse3`, no `/dev/fuse`) and you don't want to escalate, the daemon will fail rather than silently downgrade.\n- **Windows mounts go via WebDAV and the WebClient service.** `cascade start` runs `net use * http://localhost:\u003cport\u003e/` which lets the OS pick the next free drive letter. If `WebClient` isn't running the mount fails immediately; start it once with `sc start WebClient` or set it to auto-start (see Prerequisites).\n- **Google Drive auth tokens are stored per platform.** macOS and Linux: `$XDG_CONFIG_HOME/cascade/gdrive-tokens/\u003caccount\u003e.json`, falling back to `~/.config/cascade/...`. Windows: `%APPDATA%\\cascade\\gdrive-tokens\\\u003caccount\u003e.json`, with the default NTFS ACLs restricting access to the current user.\n- **NFS cache mode** controls write support, typed as the `NfsCacheMode` enum (`off`/`minimal`/`full`). `off` is read-only — writes are refused with `NFS3ERR_ROFS`/`NFS4ERR_ROFS`. `minimal` (default) is write-capable with minimal disk usage; `full` caches everything eagerly. Write-capable modes implement the full NFSv3 and NFSv4 write procedure set: `WRITE`, `CREATE`, `SETATTR` (size/mtime), `MKDIR`, `REMOVE`, `RMDIR`, `RENAME`, and `COMMIT`. All write procedures route through the same backend operations (`upload`, `update`, `create_dir`, `delete`, `rename`) as the WebDAV presenter.\n- **P2P exposure is one posture, not a pile of flags.** A single `DiscoveryReach` (`lan-only`/`private`/`public`) governs how far a device reaches for peers, defaulting to `private` — LAN discovery, gossip, hole punch, and peer relay across a trusted mesh, with nothing published to any global directory. Each discovery source self-activates only when the posture permits its level *and* it has what it needs (a bound listener, a configured server). Global publication to the Mainline DHT and announce servers is opt-in via the `public` posture; the DHT's bootstrap set is always present (empty falls back to the built-in public nodes), so disabling the DHT is a posture choice, not missing config. **Rendezvous-by-presence** is a complementary live-pairing path: two peers register under the same rendezvous key at the same instant, and the `RendezvousBroker` — a distinct in-memory component hosted alongside the relay server's `SessionRegistry`, not the same registry — exchanges their candidate sets and hole-punch agreements so they can connect directly; no durable state remains once either side disconnects. It activates under the `public` posture only and requires a configured rendezvous endpoint.\n- **Google Drive rate limits** — ~10,000 requests per 100 seconds per user. The backend uses a token-bucket rate limiter and batch requests where possible.\n- **Cross-backend moves** are not atomic — they download from source, upload to destination, then delete the original. A failure partway through can leave duplicates.\n- **P2P block sizes are adaptive**: 128KB for files under 250MB, 512KB for 250MB–1GB, 1MB for files over 1GB. This is not configurable per-file.\n- **Conflict copies are never auto-deleted.** The losing version is renamed with the device name and date (e.g. `report (work-laptop 2026-05-27).conflict`). P2P-only folders use last-write-wins per-block.\n- **Shadowing in nested mounts**: if a child mount point exists in the parent backend with files, the child takes over and the parent's files are hidden (not deleted). Removing the child reveals them.\n- **Nested-mount listing and shadowing** work across all three on-demand presenters — NFS, WebDAV, and FUSE. A backend mounted inside another's subtree (`work/projects`) appears as a directory when you list the parent (`/work`), shadowing a same-named entry, and a multi-segment mount prefix is shown nested at the right level rather than flat. Reads and writes route into the nested mount by longest-prefix match.\n- **Pre-1.0 path-shape change (uniform-backend-mounts refactor).** Each configured backend now mounts at a named prefix under a neutral VFS root rather than at the bare root. A single backend named `gdrive` appears at `gdrive/Documents/…` instead of `Documents/…`. To keep the old layout, add `mount = \"/\"` to that backend's config — an at-root mount is a first-class configuration, not a special case. Pin rules, lifecycle policies, and any stored `files.path` values written before this change need updating to include the mount prefix (e.g. `Documents/**` → `gdrive/Documents/**`). The state database schema is unchanged; only the semantics of `files.path` changed from a backend-relative path to a full VFS-absolute path.\n- **Multiple `.cascade` formats in one directory** is allowed — they merge in deterministic order: gitignore-style → TOML → YAML → JSON, with last-writer-wins for scalar settings.\n- **Device identity** is derived from a self-generated TLS certificate (SHA-256 of cert, base32-encoded). All P2P connections are TLS-encrypted and authenticated by device ID.\n\n## Roadmap\n\n| Phase | Scope |\n|-------|-------|\n| v1 | NFS mount + `.cascade` (ignore only) + single backend (read-only, Google Drive) |\n| v2 | Pinning + lifecycle + cache manager |\n| v3 | Write-back + multi-backend + nested mounts + conflict resolution |\n| v4 | Conditional rules (expressions + context providers) |\n| v5 | macOS File Provider presenter (Swift extension), implemented |\n| v6 | Adopt existing directories (local backend, adopt-and-sync, adopt-in-place) |\n| v7 | P2P block sharing (LAN) |\n| v8 | Windows native ProjFS presenter, implemented (Linux FUSE delivered earlier) |\n| v9 | Full P2P (WAN discovery, NAT traversal), implemented |\n| v10 | Node management plane (capability grants, remote administration over BEP), implemented |\n| v11 | OS background service (`cascade service install|start|stop|status|uninstall`): per-user LaunchAgent on macOS, systemd `--user` unit on Linux, logon Scheduled Task on Windows, implemented |\n\nFull timeline estimates, dependency list, and reference implementations in [`docs/design.md`](docs/design.md).\n\n## References\n\n- **Design specification** — [`docs/design.md`](docs/design.md): core types, Backend trait definition, VFS tree implementation, `.cascade` parser details, expression grammar, NFS/FUSE/File Provider presenter internals, state database schema, wire protocol, Google Drive backend details.\n- **Deployment guide** — [`docs/deployment.md`](docs/deployment.md): deploying the announce Worker and relay server for WAN peer discovery and NAT traversal.\n- **Reference implementations** — [rclone](https://github.com/rclone/rclone) (NFS, VFS caching, Google Drive), [Syncthing](https://github.com/syncthing/syncthing) (BEP, peer discovery, NAT traversal), [go-nfs](https://github.com/willscott/go-nfs) (NFSv3/XDR), [WinFSP](https://github.com/winfsp/winfsp) (Windows virtual filesystem).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmearman%2Fcascade","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmearman%2Fcascade","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmearman%2Fcascade/lists"}