From c38a4ce3ab0c2c60164adcb9d45e418df0c66297 Mon Sep 17 00:00:00 2001 From: Louis Thibault Date: Wed, 20 May 2026 18:20:09 -0400 Subject: [PATCH] refactor(caps): remove legacy perform-fs compatibility surface --- CHANGELOG.md | 3 +- doc/capabilities.md | 6 ++-- std/caps/src/lib.rs | 77 ------------------------------------------- std/kernel/src/lib.rs | 7 ++-- 4 files changed, 8 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 474ac641..5bae4b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,13 +10,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - **Routing capability gains write-path v1 mutation surface (CID-transform API).** Added explicit mutation methods that take a base CID and return a new root CID: `mkdir`, `writeFile`, and `remove`, plus `publish` for IPNS updates with optional compare-and-set (`expectedCurrent`) conflict checks. This keeps reads on WASI paths while making writes explicit, attenuable effects with no hidden mutable daemon root. - **`ww run` now requires a persistent identity by default.** Identity resolution no longer silently falls back to ephemeral keys when `--identity` is missing or points to a nonexistent file. Default lookup is `~/.ww/identity`; if absent, startup fails with a clear message and remediation. Operators can explicitly bypass with `--insecure-ephemeral` (named insecure on purpose), which restores prior ephemeral behavior for quick trial runs. - **Admin UDS interface removed; `ww shell` is temporarily unavailable.** Removed the daemon-side Unix-domain admin service and its local socket discovery path. `ww shell` now remains as a forward-stable CLI surface but exits with `NOT IMPLEMENTED` until the replacement remote transport/auth path lands. -- **Filesystem data-plane contract tightened: backend is now root-layer-only and `perform fs` reads are deprecated.** The shell/MCP evaluation wrapper no longer routes data-plane reads through `(perform fs ...)`; filesystem reads now go through WASI path I/O (`load`, `import`, `/ipfs/...`, `/ipns/...`). The legacy `fs` handler remains only to return a migration-grade deprecation error. Backend virtual mount resolution now rejects targeted mounts (`source:/guest/path`) and accepts root layers only; `ww run` enforces this early with a CLI preflight error that lists offending mounts. Docs updated across `doc/shell.md`, `doc/capabilities.md`, and `doc/architecture.md` to reflect the single-path model. +- **Filesystem data-plane contract tightened: backend is now root-layer-only and `perform fs` reads are removed.** The shell/MCP evaluation wrapper no longer routes data-plane reads through `(perform fs ...)`; filesystem reads now go through WASI path I/O (`load`, `import`, `/ipfs/...`, `/ipns/...`). The legacy `fs` handler has been removed. Backend virtual mount resolution now rejects targeted mounts (`source:/guest/path`) and accepts root layers only; `ww run` enforces this early with a CLI preflight error that lists offending mounts. Docs updated across `doc/shell.md`, `doc/capabilities.md`, and `doc/architecture.md` to reflect the single-path model. - **CompilationService now uses a dedicated worker pool with in-flight dedupe.** The compiler subsystem moved from single-thread compile handling to a fixed worker pool (`WW_COMPILE_WORKERS`, default derived from CPU count), keys cache entries by `(wasm_blake3, engine identity)`, and coalesces concurrent duplicate compile requests so one cold compile serves all waiters. - **Shell local-discovery policy aligned to per-user run dir.** `ww shell` local discovery now scans only `~/.ww/run/` for `.sock` entries (no `/var/run/ww` fallback), and the client fails deterministically with a disambiguation error when multiple local daemons are present instead of prompting interactively. Updated `src/discovery.rs`, `src/cli/shell.rs`, and shell/CLI docs to match this behavior and keep the local admin auth boundary consistently user-scoped. - **CLI module boundary cleanup (no behavior change).** Extracted daemon-management helpers, namespace command handlers, and doctor checks from `src/cli/main.rs` into `src/cli/daemon_cmd.rs`, `src/cli/ns_cmd.rs`, and `src/cli/doctor_cmd.rs`, leaving `Commands` as thin delegators. This reduces `main.rs` surface area and improves maintainability while preserving existing command behavior. - **Runtime load path now supports staged component precompilation via a dedicated compiler service.** Added `ProcBuilder::with_component(...)` so executor spawns can instantiate precompiled components instead of recompiling WASM on worker threads, threaded an optional compile-request channel through runtime construction, and wired daemon startup to spawn `CompilationService` and pass it into kernel/admin runtimes. This keeps behavior compatible while moving CPU-heavy compile work off the executor hot path. ### Removed +- **Legacy `perform fs` compatibility handler removed.** The deprecated `fs-handler` fallback in `std/caps` has been deleted; callers must use WASI path I/O for reads and the routing write-path API for mutations. - **`std/lib/ww/fs.glia` and `std/lib/ww/routing.glia` — pure-veneer modules.** Both were 1-line pass-throughs over the bound `fs` and `routing` capabilities (`(defn read-str [path] (perform fs :read-str path))` and four analogous `routing/*` shims). Zero internal callers across the repo. The wrappers obscured the canonical `(perform : args)` cap-dispatch idiom without abstracting anything, and the names sustained the fiction that `ww/fs` was "the filesystem module" when `fs` is the underlying capability and the only thing doing work. Also gone: the pure-Glia `ipfs-path` / `ipns-path` constructors (trivial `(str "/ipfs/" cid)` inlines) and the incomplete `cid?` predicate (missed several valid CIDv1 multicodec prefixes — `bafr`, `bagu`, `bagy`, … — and wasn't fs-related to begin with). `doc/capabilities.md` updated: the "Content access" section now shows `(perform fs :read-str path)` directly instead of advertising `fs/read*` wrappers; the env-bindings table entry switches from "Glia functions" to "capabilities" framing. The `fs` and `routing` capabilities themselves are unchanged — still bound at env startup (`std/shell/src/lib.rs:260-274`); only the redundant module veneer is gone. Both modules were introduced earlier in `[Unreleased]` and never shipped in a release, so zero external migration burden. - **`ww shell` "AI agents:" startup hint.** The line `AI agents: ipfs cat /ipns/releases.wetware.run/.agents/prompt.md` is gone from `src/cli/shell.rs`. The hint pointed at a host-shell command (`ipfs cat`) that's awkward to surface from inside a Glia REPL (the user can't paste it), and the obvious Glia-form rewrite — `(perform fs :read-str "/ipns/…")` — fails today because the WASI fs interceptor (`crates/cell/src/fs_intercept.rs:481-520`) only recognizes `ipfs//…` paths (`parse_ipfs_path` at line 72 strips the `ipfs/` prefix; there's no `ipns/` sibling). `/ipfs//…` reads through the cap *do* work — `open_ipfs` lazily materializes content from the pinset cache — so a hint pointing at a stable CID would work today; what's missing is IPNS resolution at the intercept layer (or a sibling cap method that calls Kubo `name/resolve` first, then routes through the existing pinset path). Restoring a pasteable Glia-form hint is the natural reward for that follow-up. diff --git a/doc/capabilities.md b/doc/capabilities.md index 9cebf0f3..d488786d 100644 --- a/doc/capabilities.md +++ b/doc/capabilities.md @@ -89,9 +89,9 @@ Use regular guest file I/O against filesystem paths: - direct guest reads via WASI-aware code under `/ipfs//...` and `/ipns//...` -`(perform fs ...)` is legacy and returns a migration error. Keeping a separate -`perform` filesystem read surface created dual-path semantics and is being -removed. +There is no `perform fs` read surface. Keeping a separate `perform` +filesystem API created dual-path semantics; reads now go only through +WASI path I/O. ### Content mutation (explicit capability API) diff --git a/std/caps/src/lib.rs b/std/caps/src/lib.rs index 413df29e..a8468f32 100644 --- a/std/caps/src/lib.rs +++ b/std/caps/src/lib.rs @@ -640,30 +640,6 @@ pub fn make_routing_handler(routing: routing_capnp::routing::Client) -> Val { } } -/// Legacy `fs` effect handler. -/// -/// `perform fs` data-plane reads are removed in favor of direct WASI file I/O -/// (`load`, `import`, and normal guest file reads against `/ipfs` / `/ipns`). -/// The handler remains only to produce a migration-grade error when older code -/// attempts `(perform fs ...)`. -pub fn make_fs_handler() -> Val { - Val::AsyncNativeFn { - name: "fs-handler".into(), - func: Rc::new(move |args: Vec| { - Box::pin(async move { - let (method, rest) = extract_method(&args[0])?; - let _resume = &args[1]; - let _ = rest; - Err(glia::error::cap_call( - "fs", - method, - "deprecated: `(perform fs ...)` is removed; use WASI path I/O (`load`, `import`, or direct file reads under /ipfs|/ipns)".to_string(), - )) - }) - }), - } -} - // --------------------------------------------------------------------------- // Effect handler wrapping — nests with-effect-handler forms around an expr // --------------------------------------------------------------------------- @@ -1003,57 +979,4 @@ mod tests { } } - // -- make_fs_handler (legacy deprecation behavior) -- - - /// Drive an `AsyncNativeFn` synchronously by polling its future to - /// completion on a fresh single-threaded tokio runtime. - fn drive_handler(handler: &Val, method: &str, path: Option<&str>) -> Result { - let mut method_list = vec![Val::Keyword(method.into())]; - if let Some(p) = path { - method_list.push(Val::Str(p.into())); - } - let resume = Val::NativeFn { - name: "test-resume".into(), - func: std::rc::Rc::new(|args: &[Val]| Ok(args[0].clone())), - }; - let args = vec![Val::List(method_list), resume]; - - match handler { - Val::AsyncNativeFn { func, .. } => { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("runtime"); - let local = tokio::task::LocalSet::new(); - local.block_on(&rt, func(args)) - } - other => panic!("expected AsyncNativeFn, got {other:?}"), - } - } - - #[test] - fn fs_read_returns_deprecation_error() { - let handler = make_fs_handler(); - let err = drive_handler(&handler, "read", Some("/ipfs/bafy.../foo.txt")).unwrap_err(); - assert_eq!( - glia::error::type_tag(&err), - Some(glia::error::tag::CAP_CALL), - "expected cap-call-failed tag, got: {err:?}" - ); - let msg = glia::error::message(&err).unwrap_or(""); - assert!( - msg.contains("deprecated") && msg.contains("perform fs"), - "expected migration error message, got: {msg}" - ); - } - - #[test] - fn fs_unknown_method_still_returns_deprecation_error() { - let handler = make_fs_handler(); - let err = drive_handler(&handler, "explode", Some("anything")).unwrap_err(); - assert_eq!( - glia::error::type_tag(&err), - Some(glia::error::tag::CAP_CALL) - ); - } } diff --git a/std/kernel/src/lib.rs b/std/kernel/src/lib.rs index 9f633a09..ae54d411 100644 --- a/std/kernel/src/lib.rs +++ b/std/kernel/src/lib.rs @@ -1089,10 +1089,9 @@ fn parse_initd_script(name: &str, data: &[u8]) -> Option> { /// ```glia /// (with-effect-handler host host-handler /// (with-effect-handler runtime runtime-handler -/// (with-effect-handler fs fs-handler -/// (with-effect-handler routing routing-handler -/// (with-effect-handler :load (fn [path resume] (resume (load path))) -///
))))) +/// (with-effect-handler routing routing-handler +/// (with-effect-handler :load (fn [path resume] (resume (load path))) +/// )))) /// ``` /// /// Cap handlers are looked up from the environment by name. Keyword effect