From d17bff3f5ca9cbddf25dc68fe75b4c7dda3839de Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:40:20 -0700 Subject: [PATCH 1/2] Components' host calls: no check of the declared families makepad#118 (OctoSense #450, option (c)) removed the runtime's capability check before a script's host.request; a component's octosense:host call, which the docs promise acts as the script's does, follows: it reaches the dispatcher whatever the manifest declares, and a service that needs a grant checks it itself. What stays: never wasm.* (the app's worker is running the call), never a sheet or a prompt, the call's deadline. Co-Authored-By: Claude Opus 5.5 --- crates/shell/src/wasm_service.rs | 40 ++++++------------- .../adr/0014-app-components-in-webassembly.md | 8 ++-- ...014-app-components-in-webassembly.zh-CN.md | 6 +-- docs/wasm.md | 9 +++-- docs/wasm.zh-CN.md | 5 ++- 5 files changed, 28 insertions(+), 40 deletions(-) diff --git a/crates/shell/src/wasm_service.rs b/crates/shell/src/wasm_service.rs index 83dc2263..6c77e5fb 100644 --- a/crates/shell/src/wasm_service.rs +++ b/crates/shell/src/wasm_service.rs @@ -37,7 +37,7 @@ //! is left of the quota is what a call may add (a write past it fails inside //! the component, as a full disk). -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, HashMap}; use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; @@ -99,14 +99,14 @@ static HOST_WAITING: Mutex> = Mutex::new(None); /// isolate's. static NEXT_HOST_KEY: AtomicUsize = AtomicUsize::new(1 << 50); -/// An app's host services as its components reach them: the families its -/// admitted manifest grants (a system app's own namespace too), dispatched -/// on the UI thread as its script's `host.request` would be, but never with -/// a sheet or a prompt, so only the methods a background surface may call. +/// An app's host services as its components reach them: dispatched on the +/// UI thread as its script's `host.request` would be, and like it with no +/// check of the families the manifest declares (makepad#118, OctoSense +/// #450): a service that needs a grant checks it itself. Never with a sheet +/// or a prompt, so only the methods a background surface may call. struct AppHostCalls { app: String, host_dir: PathBuf, - families: BTreeSet, } impl HostCalls for AppHostCalls { @@ -118,16 +118,6 @@ impl HostCalls for AppHostCalls { "a component cannot call wasm.*: its app's functions are already running it".into(), ); } - let own = self - .app - .strip_prefix(octosense_appstore::system::SYSTEM_ID_PREFIX) - == Some(family); - if !self.families.contains(family) && !own { - return Err(format!( - "{} was not granted the {family} service, which {service} needs", - self.app - )); - } let args: Value = serde_json::from_str(args) .map_err(|e| format!("{service}: the arguments are not JSON: {e}"))?; let (done, answer) = sync_channel(1); @@ -758,12 +748,6 @@ impl Lab { host_calls: Arc::new(AppHostCalls { app: app.to_string(), host_dir: admission.root.join(".host"), - families: admission.manifest["capabilities"] - .as_array() - .into_iter() - .flatten() - .filter_map(|c| c.as_str().map(str::to_string)) - .collect(), }), admission: admission.clone(), }; @@ -2078,7 +2062,7 @@ mod tests { /// services, dispatched on the UI thread as its script's would be (a /// thread here plays the UI's part); nothing else, and never `wasm.*`. #[test] - fn a_components_host_calls_reach_only_its_apps_granted_services() { + fn a_components_host_calls_reach_the_host_as_its_apps_script_does() { struct Echo; impl HostService for Echo { fn family(&self) -> &'static str { @@ -2116,7 +2100,9 @@ mod tests { let admission = Admission { root: root.clone(), bundle, - manifest: json!({"capabilities": ["wasm", "wasmhostecho"]}), + // No wasmhostecho capability: a host call, like the script's + // host.request, is not checked against the declared families. + manifest: json!({"capabilities": ["wasm"]}), }; let mut lab = Lab::from_bundle( "org.example.hostcalls", @@ -2146,11 +2132,9 @@ mod tests { answer, json!({"app": "org.example.hostcalls", "method": "get", "args": {"id": 1}, "may_prompt": false}) ); + // A family with no service here: the dispatcher says so. let error = call("mail.list", "{}").unwrap_err(); - assert!( - error.contains("was not granted the mail service"), - "{error}" - ); + assert!(error.contains(r#"no service answers "mail""#), "{error}"); let error = call("wasm.functions", "{}").unwrap_err(); assert!(error.contains("cannot call wasm.*"), "{error}"); let error = call("wasmhostecho.get", "not json").unwrap_err(); diff --git a/docs/adr/0014-app-components-in-webassembly.md b/docs/adr/0014-app-components-in-webassembly.md index 429968c9..9006b7a0 100644 --- a/docs/adr/0014-app-components-in-webassembly.md +++ b/docs/adr/0014-app-components-in-webassembly.md @@ -97,9 +97,11 @@ component reaches only what its app may already reach. end it, so its timeouts are clamped to the call's deadline, which is 10 s for a component that imports `wasi:http`. - **`octosense:host`, phase 3:** `request(service, args)` reaches the host - services the app is granted, dispatched on the UI thread as the app's - script's `host.request` is, but with no sheet and no prompt (only - methods a background surface may call), and never `wasm.*`. + services as the app's script's `host.request` does, dispatched on the UI + thread and, since makepad#118 (OctoSense #450), not checked against the + families the manifest declares (a service that needs a grant checks it + itself), but with no sheet and no prompt (only methods a background + surface may call), and never `wasm.*`. - **Never:** `wasi:sockets`, and any import outside these packages. A component that asks for one is refused when it loads (`LoadError::Import`) and by App Hub's gate. diff --git a/docs/adr/0014-app-components-in-webassembly.zh-CN.md b/docs/adr/0014-app-components-in-webassembly.zh-CN.md index b4153da4..f781f362 100644 --- a/docs/adr/0014-app-components-in-webassembly.zh-CN.md +++ b/docs/adr/0014-app-components-in-webassembly.zh-CN.md @@ -69,9 +69,9 @@ crates.io 上的大多数 crate 要么需要上述缺失能力中的某些(时 运行时闸门,边界是操作系统和宿主的 API 表面。把组件限制在应用的 `network.hosts` 内、后来又限制 为公共 HTTPS 主机的做法,都已按该裁定否决。请求在客体之外等待,epoch 检查无法结束它,因此 请求的超时被限制在调用的截止时间内;导入 `wasi:http` 的组件每次调用有 10 秒。 - - **`octosense:host`(第 3 阶段):** `request(service, args)` 可以调用应用已获授权的宿主服务, - 像应用脚本的 `host.request` 一样在 UI 线程上分派,但不打开面板、不询问用户(只能调用后台界面 - 可以调用的方法),并且绝不调用 `wasm.*`。 + - **`octosense:host`(第 3 阶段):** `request(service, args)` 像应用脚本的 `host.request` 一样调用 + 宿主服务,在 UI 线程上分派;自 makepad#118(OctoSense #450)起不检查清单声明的服务族(需要授权的 + 服务自己检查),但不打开面板、不询问用户(只能调用后台界面可以调用的方法),并且绝不调用 `wasm.*`。 - **从不提供:** `wasi:sockets`,以及上述包以外的任何导入。请求这些导入的组件在加载时被拒绝 (`LoadError::Import`),App Hub 的审核闸门也会拒绝。 5. **无需编写 WIT。** diff --git a/docs/wasm.md b/docs/wasm.md index 42fb9d5a..a2234e8e 100644 --- a/docs/wasm.md +++ b/docs/wasm.md @@ -95,10 +95,11 @@ two kinds apart by the file's preamble, and a bundle may carry both. connect, first-byte and between-bytes timeouts end with the call. - **Host services.** Through `octosense:host` ([`crates/wasm-host/wit/octosense-host.wit`](../crates/wasm-host/wit/octosense-host.wit)), - `request(service, args)` calls one of the host services its app is - granted, as the app's script does: the same families, dispatched on the UI - thread, but never with a sheet or a prompt, so only methods a background - surface may call. `wasm.*` is refused, since the app's worker is busy with + `request(service, args)` calls a host service as the app's script does: + dispatched on the UI thread, with no check of the families the manifest + declares (makepad#118; a service that needs a grant checks it itself), but + never with a sheet or a prompt, so only methods a background surface may + call. `wasm.*` is refused, since the app's worker is busy with the call, and the call's deadline bounds the wait. ADR 0014 records what this costs. On an M-series Mac, the 313 KiB test diff --git a/docs/wasm.zh-CN.md b/docs/wasm.zh-CN.md index 9ddffeff..597db428 100644 --- a/docs/wasm.zh-CN.md +++ b/docs/wasm.zh-CN.md @@ -73,8 +73,9 @@ Rust crate 构建。服务按文件头区分两者,一个应用包可以同时 每个请求的连接、首字节和字节间超时都随调用结束。 - **宿主服务。** 通过 `octosense:host` ([`crates/wasm-host/wit/octosense-host.wit`](../crates/wasm-host/wit/octosense-host.wit)), - 组件可以用 `request(service, args)` 调用应用已获授权的宿主服务,与应用的脚本相同:同样的服务族, - 在 UI 线程上分派,但绝不打开面板、不询问用户,因此只能调用后台界面可以调用的方法。`wasm.*` + 组件可以用 `request(service, args)` 像应用的脚本一样调用宿主服务:在 UI 线程上分派,不检查清单 + 声明的服务族(makepad#118;需要授权的服务自己检查),但绝不打开面板、不询问用户,因此只能调用 + 后台界面可以调用的方法。`wasm.*` 被拒绝,因为应用的工作线程正忙于这次调用;调用的截止时间限制等待时长。 ADR 0014 记录了开销。在 M 系列 Mac 上,313 KiB 的测试组件约 40 毫秒完成编译,从缓存加载 From 3918de03175a2cd5a308d388988e6626de2adcb4 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Fri, 9 Oct 2026 21:38:16 -0700 Subject: [PATCH 2/2] test(wasm): isolate missing-service dispatch assertion --- crates/shell/src/wasm_service.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/shell/src/wasm_service.rs b/crates/shell/src/wasm_service.rs index 6c77e5fb..14a493b7 100644 --- a/crates/shell/src/wasm_service.rs +++ b/crates/shell/src/wasm_service.rs @@ -2132,9 +2132,9 @@ mod tests { answer, json!({"app": "org.example.hostcalls", "method": "get", "args": {"id": 1}, "may_prompt": false}) ); - // A family with no service here: the dispatcher says so. - let error = call("mail.list", "{}").unwrap_err(); - assert!(error.contains(r#"no service answers "mail""#), "{error}"); + // Use a unique family: parallel shell tests may register real services. + let error = call("wasmhostmissing.get", "{}").unwrap_err(); + assert!(error.contains(r#"no service answers "wasmhostmissing""#), "{error}"); let error = call("wasm.functions", "{}").unwrap_err(); assert!(error.contains("cannot call wasm.*"), "{error}"); let error = call("wasmhostecho.get", "not json").unwrap_err();