From 64692f43e87308e237c2dccc5dafa266f2894d77 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Fri, 2 Oct 2026 20:12:23 -0700 Subject: [PATCH 01/15] feat(studio): add scoped on-device L0 rendering and device probes --- AGENTS.md | 7 + README.md | 2 + README.zh-CN.md | 2 + crates/app-peers/src/broker.rs | 4 + crates/app-peers/src/host_tools.rs | 7 + crates/app-peers/tests/broker.rs | 5 +- crates/shell/src/glance.rs | 28 + crates/shell/src/glance_card.rs | 15 +- crates/shell/src/host_tools/mod.rs | 9 +- crates/shell/src/host_tools/relay.rs | 13 +- crates/shell/src/host_tools/studio.rs | 584 ++++++++++++++++++ crates/shell/src/host_tools/tests.rs | 41 ++ crates/shell/src/lib.rs | 60 +- crates/shell/src/studio/mod.rs | 721 +++++++++++++++++++++++ crates/shell/src/system_chat/session.rs | 13 + crates/shell/src/system_chat/tests.rs | 22 +- docs/adr/0006-app-studio-on-the-phone.md | 50 +- docs/architecture-walkthrough.md | 14 + docs/architecture-walkthrough.zh-CN.md | 14 + phone/README.md | 34 +- phone/README.zh-CN.md | 12 +- rom/README.md | 2 + rom/README.zh-CN.md | 2 + rom/docs/home-build.md | 24 +- rom/scripts/build-home.py | 28 +- rom/scripts/stage-home.py | 4 + rom/tests/test_home_build.py | 57 ++ tools/studio-device-probe.py | 370 ++++++++++++ 28 files changed, 2079 insertions(+), 65 deletions(-) create mode 100644 crates/shell/src/host_tools/studio.rs create mode 100644 crates/shell/src/studio/mod.rs create mode 100644 tools/studio-device-probe.py diff --git a/AGENTS.md b/AGENTS.md index c8548263b..2761a0ec8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,7 @@ | Which apps have an agent, their tools, and the relay that checks and routes every agent tool call | `crates/shell/src/apps.rs` (`agent_apps`), `crates/shell/src/host_tools/` (`script_apps.rs`, `relay.rs`, `files.rs`) | from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell host_tools`; `phone.yml` | | Approvals, first-use consent, standing rules, the audit | `crates/shell/src/approvals/` | from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell approvals`; `phone.yml` | | Glance cards (`glance.publish`), the desktop's glance panel and card window, a card's in-card chat | `crates/shell/src/glance*.rs`; the shared `sys.chat` host side in `crates/l0-chat/` | `cargo test --locked -p octosense-l0-chat`; from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell glance`; `apps.yml`, `phone.yml` | +| App Studio's developer-only L0 preview (`studio.render`) | `crates/shell/src/studio/` (UI/GPU), `crates/shell/src/host_tools/studio.rs` (workspace and executor), `docs/adr/0006-app-studio-on-the-phone.md` | from `phone/`: `cargo test --locked --features mobile-apps,dev-mode -p octosense-shell`; separate-package device checks are required for GPU acceptance | | The system toolbox's workflow templates (library, runner, forks, evaluation, `mod.research`) | `crates/toolbox/` (templates in `crates/toolbox/templates//`); offered to app agents by `crates/ai-host/src/toolbox_peers.rs` (feature `toolbox-peers`) | `cargo test --locked -p octosense-toolbox`, its README, `apps.yml` | | A system app | `apps//bundle/` | App Hub's `card-host --bundle apps//bundle --system`; then in a shell | | A system app's agent: its tools and the cards they publish | `apps//bundle/tools.json` and the manifest's `agent` block; the tools run on the app's host service | its host service's tests (below); then in a shell, with the system agent asking the app's agent | @@ -31,6 +32,12 @@ Start every session with `python3 tools/setup.py` (it prepares `.sources/`, and changes nothing that is already right). +For local shell tests that link Rinx, set `OCTOSENSE_HOME` and `RINX_DATA_DIR` +to separate, empty test directories and `MAKEPAD_HIDE_WINDOWS=1`. Some hosted +module tests initialize Rinx; without the explicit data directory they can +restore the machine's existing account. Keep these directories outside the +checkout and never copy personal account state into them. + On a machine that already has clones of Makepad, OctoScript or Octoscript-Makepad, never let setup clone them again: name the clones as a hub (`~/.config/octosense/sources.json`, `--hub DIR` or `OCTOSENSE_SOURCES_HUB`; see the README's Set up) so every `.sources/` entry is a `git worktree` of the one clone, and run `python3 tools/setup.py --remove-worktrees` before deleting a checkout of this repository. ## Local CI diff --git a/README.md b/README.md index 13a8a5618..843d3272d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ It was OctoSense-Desktop; OctoSense-ROM (retired; merged into this repository) a New to the codebase? Start with [From an app window to an agent turn](docs/architecture-walkthrough.md). It follows executable entry points, native and script hosting, app data access, human/system conversations, tool routing and the actual Tokio tasks. The [product walkthrough](desktop/docs/code-walkthrough.md) adds desktop, Home, ROM and system-app run recipes. +[App Studio on the phone](docs/adr/0006-app-studio-on-the-phone.md) is an accepted plan with milestone 1 in progress. Home can now be packaged with explicit developer options and a separate test identity ([phone build options](phone/README.md#build-and-run)); the complete render/check/refine/install loop remains under development. Device acceptance is recorded separately from unit and build checks. + ## How it fits together One shell process per device, one octos kernel per shell, and every agent is a session in that kernel. App-agent access goes through the shell broker. The shell holds the host connection and the host token, relays every agent tool call to the app that owns the tool, and routes approvals through developer mode, the person's standing rules or confirmation sheets. The opt-in AppCard prototype accesses the shared kernel service directly, bypassing the app-peer broker, as described below. The full picture, with the code paths and what is on `main` versus planned: [docs/architecture.md](docs/architecture.md); the decisions: [ADR 0004](docs/adr/0004-native-apps-hosting-and-peers.md). diff --git a/README.zh-CN.md b/README.zh-CN.md index 558498bfe..5813bdb8b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -18,6 +18,8 @@ 初次阅读源码?从[从应用窗口到 Agent 回合](docs/architecture-walkthrough.zh-CN.md)开始,沿着入口、原生/脚本托管、数据访问、人类/系统对话、工具路由以及实际 Tokio 任务阅读源码。[产品导读](desktop/docs/code-walkthrough.zh-CN.md)补充桌面、Home、ROM 和系统应用的运行方式。 +[手机上的 App Studio](docs/adr/0006-app-studio-on-the-phone.md) 已通过设计决策,里程碑 1 正在实现。Home 现可显式编译开发者选项并使用独立测试包名([手机构建选项](phone/README.zh-CN.md#构建与运行));完整的渲染、检查、改进与安装循环仍在开发中。设备验收与单元测试、构建检查分别记录。 + ## 整体如何运作 每台设备一个 Shell 进程,每个 Shell 一个 octos 内核,每个 Agent 都是这个内核中的一个会话。应用 Agent 通过 Shell broker 访问内核。Shell 持有宿主连接和宿主 token,把每个 Agent 工具调用转交给拥有该工具的应用,并通过开发者模式、用户预设的常设规则或确认面板处理审批。下文的可选 AppCard 原型绕过应用 peer broker,直接访问共享内核服务。完整说明,包括代码路径以及哪些已在 `main` 上、哪些还在规划中:[docs/architecture.zh-CN.md](docs/architecture.zh-CN.md);相关决策:[ADR 0004(英文)](docs/adr/0004-native-apps-hosting-and-peers.md)。 diff --git a/crates/app-peers/src/broker.rs b/crates/app-peers/src/broker.rs index 2d925945b..15ffb5da1 100644 --- a/crates/app-peers/src/broker.rs +++ b/crates/app-peers/src/broker.rs @@ -82,6 +82,7 @@ use std::collections::{BTreeSet, HashMap, VecDeque}; use std::future::Future; +use std::path::PathBuf; use std::pin::Pin; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, Weak}; @@ -661,6 +662,7 @@ fn remember(list: &mut VecDeque, item: String) { #[derive(Clone)] struct PeerInfo { + workspace: Option, slug: String, session: String, /// The host token that controls this peer (UPCR-2026-034). @@ -1686,6 +1688,7 @@ impl Inner { } } let peer = PeerInfo { + workspace: result["cwd"].as_str().map(PathBuf::from), slug, session: session.clone(), token, @@ -1897,6 +1900,7 @@ impl Inner { return refuse("workspace_refused", &format!("the account's workspace was refused: {why}")); } call.calling_app = self.cfg.app_id.clone(); + call.peer_workspace = peer.workspace.clone(); call.account = Some(account); let occurrence = call.occurrence(); let repeat = { diff --git a/crates/app-peers/src/host_tools.rs b/crates/app-peers/src/host_tools.rs index afeb9d433..89c8518ff 100644 --- a/crates/app-peers/src/host_tools.rs +++ b/crates/app-peers/src/host_tools.rs @@ -261,6 +261,9 @@ pub struct HostToolCall { // --- stamped by the host (the broker, or the system chat) --- /// The app whose agent calls (`system` for the system agent). pub calling_app: String, + /// Kernel-confirmed peer workspace, stamped by the broker, never parsed + /// from tool arguments or wire caller fields. Context folders are below it. + pub peer_workspace: Option, /// The calling peer's account (`None`: signed out, or the system agent). pub account: Option, /// The client of the calling request context (a Rinx mini app), from @@ -302,6 +305,7 @@ impl HostToolCall { turn_id: s("turn_id").or_else(|| caller.get("turn_id").and_then(Value::as_str).map(str::to_owned)).unwrap_or_default(), caller_kind, calling_app: String::new(), + peer_workspace: None, account: None, client: None, origin: if caller_kind == CallerKind::System { CallOrigin::System } else { CallOrigin::PeerOwn }, @@ -1104,6 +1108,9 @@ mod tests { "args": {"room": "!r", "text": "hi"}, "risk": "act", "confirm_required": true, "timeout_ms": 5000, "tools_version": 3 }); let call = HostToolCall::parse(¶ms).unwrap(); + let mut forged = params.clone(); + forged["peer_workspace"] = json!("/other/app"); + assert!(HostToolCall::parse(&forged).unwrap().peer_workspace.is_none(), "only the broker may stamp the workspace"); assert_eq!(call.caller_kind, CallerKind::AppPeer); assert!(call.confirm_required); assert_eq!(call.context_id.as_deref(), Some("ab-mini")); diff --git a/crates/app-peers/tests/broker.rs b/crates/app-peers/tests/broker.rs index be8cdc987..3d4fcb62a 100644 --- a/crates/app-peers/tests/broker.rs +++ b/crates/app-peers/tests/broker.rs @@ -775,12 +775,15 @@ fn a_tool_call_is_stamped_run_once_answered_on_its_link_and_never_after_a_cancel let slug = peer_slug(&script); let context_id = calls_of(&script, "peer/context/open")[0].1["context_id"].as_str().unwrap().to_owned(); - notify(&script, "peer/tool/call", tool_call_params(&slug, "c1", "t1", Some(&context_id))); + let mut incoming = tool_call_params(&slug, "c1", "t1", Some(&context_id)); + incoming["peer_workspace"] = json!("/forged/workspace"); + notify(&script, "peer/tool/call", incoming); wait_for("the host to get the call", || host.calls.lock().unwrap().len() == 1); let (call, reply) = host.calls.lock().unwrap()[0].clone(); assert_eq!(call.account.as_deref(), Some("@alice:x"), "the account is the host's, never the app's"); assert_eq!(call.client.as_deref(), Some("mini.news#1"), "the client comes from the context table"); assert_eq!(call.calling_app, "rinx"); + assert_eq!(call.peer_workspace.as_deref(), Some(std::path::Path::new("/kernel/ws")), "workspace comes from peer/prepare, never the tool call"); assert_eq!(call.origin, CallOrigin::Context); assert!(reply.acknowledge()); assert!(reply.finish(ToolOutcome::Ok(json!({"sent": true})))); diff --git a/crates/shell/src/glance.rs b/crates/shell/src/glance.rs index 822300061..689ba53e8 100644 --- a/crates/shell/src/glance.rs +++ b/crates/shell/src/glance.rs @@ -206,6 +206,34 @@ pub fn check_level(source: &str) -> Result<(), String> { Ok(()) } +/// Prepare an unpublished studio card without touching the glance store. +/// Chat snapshots currently create account folders; refuse them until the +/// studio has a strictly read-only chat snapshot API. +pub(crate) fn prepare_studio(app: &str, source: &str, data: &Value, dark: bool) -> Result { + if source.len() > SOURCE_MAX || serde_json::to_vec(data).map_err(|e| e.to_string())?.len() > DATA_MAX { + return Err("card source or data exceeds the glance budget".into()); + } + if !data.is_object() { return Err("data must be an object".into()); } + check_level(source)?; + if octoscript_ui_l0::check_ui_l0(source).level != octoscript_ui_l0::Level::L0 { + return Err("studio milestone 1 accepts L0 only".into()); + } + crate::glance_chat::check_publisher(source, app)?; + if crate::glance_chat::reads_chat(source) { + return Err("studio_chat_unsupported: read-only chat snapshots are not available yet".into()); + } + let mut data = data.clone(); + resolve_digests(&Caller::Native(app.into()), source, &mut data, + crate::glance_digest::digest_root().as_deref(), octosense_l0_chat::now_ms())?; + let body = crate::glance_card::lower_in_mode(source, &data, dark)?; + // Images may finish loading after a visually quiet frame. Until their + // pending-resource state is observable, never call such a render settled. + if body.contains("http_resource(") || body.contains("Image {") || body.contains("Image{") || body.contains("crate_resource(\"self:") { + return Err("studio_images_unsupported: milestone 1 renders resource-free L0 cards".into()); + } + Ok(body) +} + /// The card's `sys.digest` sources, resolved for `caller` into `data`: the /// value under the source's name and its lifecycle under `$status`, both /// replacing anything the publisher sent. Returns the bound run ids and the diff --git a/crates/shell/src/glance_card.rs b/crates/shell/src/glance_card.rs index 82947f204..7293d64c0 100644 --- a/crates/shell/src/glance_card.rs +++ b/crates/shell/src/glance_card.rs @@ -124,21 +124,26 @@ const MOODS: &[(&str, &str)] = &[ /// general translation re-lowered chips the Material way and dropped their /// text, and drew the shipping card's tiles light under white text). pub fn lower(source: &str, data: &serde_json::Value) -> Result { - lower_report(source, octoscript_ui_l0::realize(source, data, Default::default()), true) + lower_in_mode(source, data, dark()) +} + +/// Studio selects a palette without changing the live shell's mode. +pub fn lower_in_mode(source: &str, data: &serde_json::Value, dark: bool) -> Result { + lower_report(source, octoscript_ui_l0::realize(source, data, Default::default()), true, dark) } /// [`lower`], realized against a card's local state (its `InstanceStore`), /// for the card window. pub fn lower_with_state(source: &str, data: &serde_json::Value, store: &octoscript_ui_l0::InstanceStore) -> Result { - lower_report(source, octoscript_ui_l0::realize_with_state(source, data, store, Default::default()), true) + lower_report(source, octoscript_ui_l0::realize_with_state(source, data, store, Default::default()), true, dark()) } -fn lower_report(source: &str, report: octoscript_ui_l0::RealizeReport, l0_ui: bool) -> Result { +fn lower_report(source: &str, report: octoscript_ui_l0::RealizeReport, l0_ui: bool, dark: bool) -> Result { let root = report.complete_root()?; if octoscript_ui_l0::kit_pack::contains(root) { return Err("native kit components are not offered on a glance tile".into()); } - let mood = octoscript_ui_l0::card_theme(source).unwrap_or_else(|| if dark() { "dark" } else { "light" }.into()); + let mood = octoscript_ui_l0::card_theme(source).unwrap_or_else(|| if dark { "dark" } else { "light" }.into()); let delta = MOODS.iter().find(|(name, _)| *name == mood).map(|(_, d)| *d).ok_or_else(|| format!("theme {mood:?} is not offered on a glance tile"))?; for (axis, value) in octoscript_ui_l0::card_theme_axes(source) { if !matches!(value.as_str(), "neutral" | "regular" | "none" | "soft" | "sans") { @@ -842,7 +847,7 @@ pub const CAN_RENDER: bool = cfg!(any(feature = "app-hub", native_mobile)); /// Give every Splash isolate the Card runner's vocabulary, once: the same /// registration the `card` module makes before it runs an app. -fn ensure_vocabulary(cx: &mut Cx) { +pub(crate) fn ensure_vocabulary(cx: &mut Cx) { thread_local! { static DONE: std::cell::Cell = const { std::cell::Cell::new(false) }; } diff --git a/crates/shell/src/host_tools/mod.rs b/crates/shell/src/host_tools/mod.rs index c982358bf..dc3237fd4 100644 --- a/crates/shell/src/host_tools/mod.rs +++ b/crates/shell/src/host_tools/mod.rs @@ -37,6 +37,7 @@ pub mod dev_run; pub mod files; pub mod relay; pub mod schema; +pub mod studio; #[cfg(feature = "toolbox-peers")] pub mod toolbox; #[cfg(any(feature = "app-hub", native_mobile))] @@ -360,6 +361,7 @@ impl ToolHost for ShellToolHost { /// with it and are withdrawn the moment it ends (the relay also refuses a /// late call). The peers asked. pub fn developer_mode_changed() -> usize { + crate::system_chat::sync_host_tools(); #[cfg(kernel)] { crate::ai_host::app_peers::broker::reregister_tools_where(|_| true) @@ -376,16 +378,20 @@ pub fn developer_mode_changed() -> usize { pub struct HostExecutor { dev_run: dev_run::DevRunExecutor, workspace: fn(&str, &str) -> Option, + studio: studio::StudioExecutor, } impl HostExecutor { pub fn new(workspace: fn(&str, &str) -> Option) -> HostExecutor { - HostExecutor { dev_run: dev_run::DevRunExecutor::new(workspace), workspace } + HostExecutor { dev_run: dev_run::DevRunExecutor::new(workspace), workspace, studio: studio::StudioExecutor::default() } } } impl ToolExecutor for HostExecutor { fn execute(&self, call: HostToolCall, reply: ToolReply) { + if call.name == studio::RENDER { + return self.studio.execute(call, reply); + } if call.name == relay::DEV_RUN { return self.dev_run.execute(call, reply); } @@ -405,6 +411,7 @@ impl ToolExecutor for HostExecutor { fn cancel(&self, call_id: &str) { self.dev_run.cancel(call_id); + self.studio.cancel(call_id); } } diff --git a/crates/shell/src/host_tools/relay.rs b/crates/shell/src/host_tools/relay.rs index 24371ff5e..3121fb40a 100644 --- a/crates/shell/src/host_tools/relay.rs +++ b/crates/shell/src/host_tools/relay.rs @@ -74,7 +74,7 @@ pub const HOST_EXECUTOR: &str = "@shell"; /// Whether `tool` is one the shell runs itself, as the calling app's own /// tool, whatever its declaration names (never routed to the app). pub fn is_host_run(tool: &str) -> bool { - tool == DEV_RUN || super::files::TOOLS.contains(&tool) + tool == DEV_RUN || tool == super::studio::RENDER || super::files::TOOLS.contains(&tool) } /// A tool the shell runs, as `owner` declares it (its schemas). @@ -82,6 +82,9 @@ fn host_run_declaration(owner: &str, tool: &str) -> Option { if tool == DEV_RUN { return Some(super::dev_run::declaration(owner)); } + if tool == super::studio::RENDER { + return Some(super::studio::declaration(owner)); + } super::files::declarations(owner).into_iter().find(|d| d["name"] == tool) } /// The system toolbox (ADR 0002 §6), the owning app of the toolbox tools @@ -380,6 +383,9 @@ impl Catalog { if dev_all && app != SYSTEM { out.push(super::dev_run::declaration(app)); } + if dev_all && super::studio::SUPPORTED { + out.push(super::studio::declaration(app)); + } out } } @@ -640,6 +646,11 @@ impl Relay { // 1. Authorize by (owning app, tool) and caller. let host_run = is_host_run(&tool); let (caller, granted) = match call.caller_kind { + _ if tool == super::studio::RENDER => ( + if call.caller_kind == CallerKind::System { Caller::SystemAgent } else { Caller::OwnAgent { client: call.client.clone() } }, + super::studio::SUPPORTED && calling == owner && env.grants_all(&calling) + && (call.caller_kind == CallerKind::AppPeer || calling == SYSTEM), + ), // The shell's own: only the app's own agent, on its own peer; // `dev.run` only while developer mode covers the app. _ if host_run => ( diff --git a/crates/shell/src/host_tools/studio.rs b/crates/shell/src/host_tools/studio.rs new file mode 100644 index 000000000..661928024 --- /dev/null +++ b/crates/shell/src/host_tools/studio.rs @@ -0,0 +1,584 @@ +//! Developer-only L0 glance rendering. The relay authorizes and audits the +//! call; this executor opens files relative to a trusted workspace descriptor. +//! No source/output path can select a different app, account or context. +use super::relay::{app_of_peer, SYSTEM}; +use crate::ai_host::app_peers::host_tools::{ + CallerKind, HostToolCall, ToolExecutor, ToolOutcome, ToolReply, +}; +use serde_json::{json, Value}; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +pub const RENDER: &str = "studio.render"; +pub const SUPPORTED: bool = cfg!(unix); +pub const SOURCE_MAX: usize = 16 * 1024; +pub const DATA_MAX: usize = 32 * 1024; +const MAX_WAIT: Duration = Duration::from_secs(25); +const MAX_IN_FLIGHT: usize = 2; +static IN_FLIGHT: AtomicUsize = AtomicUsize::new(0); +static SYSTEM_WORKSPACE: Mutex> = Mutex::new(None); + +/// Called only with the kernel's session/open response by the system driver. +pub fn system_workspace_opened(path: Option<&str>) { + *SYSTEM_WORKSPACE.lock().unwrap_or_else(|e| e.into_inner()) = path + .filter(|p| Path::new(p).is_absolute()) + .and_then(|p| std::fs::canonicalize(p).ok()); +} + +pub fn declaration(app: &str) -> Value { + json!({ + "name": RENDER, "app": app, + "description": "Developer mode only. Render an L0 glance card at this device's actual glance width while Home is foreground. Read source_path and optional data_path from your own conversation workspace. Returns a PNG path for view_image; no bundle install or app execution. Rendering may take up to 20 seconds; the host call has its normal 30-second deadline.", + "input_schema": {"type":"object", "properties": { + "source_path":{"type":"string", "minLength":1,"maxLength":1024}, + "data_path":{"type":"string", "minLength":1,"maxLength":1024}, + "dark":{"type":"boolean"} + }, "required":["source_path"],"additionalProperties":false}, + "output_schema": {"type":"object", "properties": { + "path":{"type":"string"}, "width":{"type":"integer","minimum":1}, + "height":{"type":"integer","minimum":1}, "settled":{"type":"boolean"} + }, "required":["path","width","height","settled"],"additionalProperties":false}, + "risk":"read", "background":false,"shareable":false + }) +} + +fn workspace(call: &HostToolCall) -> Result { + if call.caller_kind == CallerKind::System { + if call.calling_app != SYSTEM + || call.peer.is_some() + || call.context_id.is_some() + || call.session_id != crate::system_chat::session::SYSTEM_SESSION + { + return Err("not the shell's system conversation".into()); + } + return SYSTEM_WORKSPACE + .lock() + .unwrap_or_else(|e| e.into_inner()) + .clone() + .ok_or_else(|| "the kernel has not confirmed the system workspace".into()); + } + app_available(call)?; + // A resumed peer can retain a kernel-owned cwd rather than the account + // directory offered for new peers. Use only the broker's confirmed cwd; + // agent_workspace() is a proposal for new peers, not their actual cwd. + call.peer_workspace + .clone() + .filter(|p| p.is_absolute()) + .ok_or_else(|| "the broker has not confirmed this peer workspace".into()) +} + +fn app_available(call: &HostToolCall) -> Result<(), String> { + let account = call + .account + .as_deref() + .ok_or("the calling app has no account")?; + if super::suspended(&call.calling_app, Some(account)) { + return Err("the calling app account is suspended".into()); + } + if let Some(why) = crate::app_storage::host() + .and_then(|storage| super::workspace_refused_in(storage, &call.calling_app, account)) + { + return Err(why); + } + Ok(()) +} + +fn authorized(app: &str, tag: &crate::dev_mode::DevTag) -> bool { + crate::dev_mode::tag_valid(tag) && crate::dev_mode::grants_all(app) +} + +#[derive(Default)] +pub struct StudioExecutor { + running: Arc>>>, +} +impl ToolExecutor for StudioExecutor { + fn execute(&self, call: HostToolCall, reply: ToolReply) { + let root = match workspace(&call) { + Ok(root) => root, + Err(why) => { + reply.finish(ToolOutcome::error("no_workspace", why)); + return; + } + }; + self.execute_scoped(call, reply, root); + } + fn cancel(&self, id: &str) { + if let Some(flag) = self + .running + .lock() + .unwrap_or_else(|e| e.into_inner()) + .get(id) + { + flag.store(true, Ordering::SeqCst); + } + crate::studio::cancel(id); + } +} +impl StudioExecutor { + fn execute_scoped(&self, call: HostToolCall, reply: ToolReply, root: PathBuf) { + let app = app_of_peer(&call.calling_app).to_string(); + let Some(tag) = crate::dev_mode::tag().filter(|t| authorized(&app, t)) else { + reply.finish(ToolOutcome::error( + "not_granted", + "studio.render needs developer mode for the calling agent", + )); + return; + }; + if call.app != app || call.name != RENDER { + reply.finish(ToolOutcome::error( + "not_granted", + "studio.render belongs to the calling agent", + )); + return; + } + if let Err(why) = super::schema::check(&declaration(&app)["input_schema"], &call.args) { + reply.finish(ToolOutcome::error("invalid_args", why)); + return; + } + crate::dev_mode::audit_tool_call(&app, RENDER, &call.args.to_string(), &call.calling_app); + if IN_FLIGHT.fetch_add(1, Ordering::SeqCst) >= MAX_IN_FLIGHT { + IN_FLIGHT.fetch_sub(1, Ordering::SeqCst); + reply.finish(ToolOutcome::error( + "busy", + "two studio requests are already in flight", + )); + return; + } + let flag = Arc::new(AtomicBool::new(false)); + { + let mut running = self.running.lock().unwrap_or_else(|e| e.into_inner()); + if running.contains_key(&call.call_id) { + IN_FLIGHT.fetch_sub(1, Ordering::SeqCst); + reply.finish(ToolOutcome::error( + "busy", + "this studio call is already running", + )); + return; + } + running.insert(call.call_id.clone(), flag.clone()); + } + let running = self.running.clone(); + std::thread::spawn(move || { + let outcome = render_call(&call, &root, &app, &tag, &flag, &reply); + reply.finish(outcome); + running + .lock() + .unwrap_or_else(|e| e.into_inner()) + .remove(&call.call_id); + IN_FLIGHT.fetch_sub(1, Ordering::SeqCst); + }); + } +} + +fn render_call( + call: &HostToolCall, + root: &Path, + app: &str, + tag: &crate::dev_mode::DevTag, + cancel: &AtomicBool, + reply: &ToolReply, +) -> ToolOutcome { + #[cfg(not(unix))] + { + let _ = (call, root, app, tag, cancel, reply); + ToolOutcome::error("unsupported", "studio rendering requires a Unix workspace") + } + #[cfg(unix)] + { + let run = || -> Result { + let scope = scoped::Workspace::open(root, call.context_id.as_deref())?; + let source = scope.read(call.args["source_path"].as_str().unwrap_or(""), SOURCE_MAX)?; + let data = match call.args["data_path"].as_str() { + Some(path) => serde_json::from_str(&scope.read(path, DATA_MAX)?) + .map_err(|e| format!("invalid data JSON: {e}"))?, + None => json!({}), + }; + let output = scope.output()?; + if !authorized(app, tag) || cancel.load(Ordering::SeqCst) || !reply.is_open() { + return Err("cancelled or developer mode revoked".into()); + } + let (tx, rx) = std::sync::mpsc::channel(); + crate::studio::submit(crate::studio::RenderJob { + id: call.call_id.clone(), + app: app.to_string(), + source, + data, + dark: call.args["dark"].as_bool().unwrap_or(false), + width: None, + output: output.file.try_clone().map_err(|e| e.to_string())?, + reply: tx, + dev_tag: tag.clone(), + })?; + if call.call_id.starts_with("studio-test-") { + makepad_widgets::log!("studio-test-started: {}", call.call_id); + } + let timeout = + Duration::from_millis(call.timeout_ms.saturating_sub(500).max(1)).min(MAX_WAIT); + let deadline = Instant::now() + timeout; + let result = loop { + if cancel.load(Ordering::SeqCst) || !reply.is_open() || !authorized(app, tag) { + crate::studio::cancel(&call.call_id); + break Err("cancelled or developer mode revoked".to_string()); + } + if Instant::now() >= deadline { + crate::studio::cancel(&call.call_id); + break Err("studio render timed out".to_string()); + } + match rx.recv_timeout(Duration::from_millis(25)) { + Ok(value) => break value, + Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {} + Err(_) => break Err("studio renderer stopped".into()), + } + }?; + if !authorized(app, tag) || cancel.load(Ordering::SeqCst) || !reply.is_open() { + return Err("cancelled or developer mode revoked".into()); + } + if call.caller_kind == CallerKind::AppPeer { + app_available(call)?; + } + scope.verify(root, call.context_id.as_deref())?; + // Pinned octos resolves a peer-context session to binding.cwd, + // /contexts/ (app_binding::resolve_session_app_binding). + // Its view_image is rooted there, not at the parent peer workspace. + let path = output.keep(); + Ok( + json!({"path":path,"width":result.width,"height":result.height,"settled":result.settled}), + ) + }; + match run() { + Ok(data) => ToolOutcome::Ok(data), + Err(why) => ToolOutcome::error("studio_render_failed", why), + } + } +} + +/// Explicit developer test action only. Its workspace comes from a local +/// launch fixture, never from model tool arguments. No provider is required. +pub fn test_action(spec_path: &str) -> Result<(), String> { + let home = crate::octosense::paths::home(); + makepad_widgets::log!("studio-test-profile: home={} canonical={} build={:?}", + home.display(), std::fs::canonicalize(&home).unwrap_or(home.clone()).display(), + crate::dev_mode::BuildKind::current()); + if !crate::dev_mode::grants_all(SYSTEM) { + return Err("studio test action requires developer mode for system".into()); + } + use std::io::Read; + let mut text = String::new(); + std::fs::File::open(spec_path) + .map_err(|e| e.to_string())? + .take(8193) + .read_to_string(&mut text) + .map_err(|e| e.to_string())?; + if text.len() > 8192 { + return Err("studio test fixture is too large".into()); + } + let mut args: Value = serde_json::from_str(&text).map_err(|e| e.to_string())?; + let root = PathBuf::from( + args["workspace"] + .as_str() + .ok_or("fixture needs workspace")?, + ); + if !root.is_absolute() { + return Err("fixture workspace must be absolute".into()); + } + args.as_object_mut() + .ok_or("fixture must be an object")? + .remove("workspace"); + let id = format!("studio-test-{}", uuid::Uuid::new_v4()); + let mut call = HostToolCall::parse(&json!({"call_id":id,"name":RENDER,"app":SYSTEM,"session_id":crate::system_chat::session::SYSTEM_SESSION, + "caller":{"kind":"system"},"args":args,"timeout_ms":30_000,"risk":"read"})).map_err(|e| e.to_string())?; + call.calling_app = SYSTEM.into(); + #[cfg(unix)] + let result_file = scoped::Workspace::open(&root, None)?.result_file()?; + let reply = ToolReply::new(id, move |fields| { + #[cfg(unix)] + { + use std::io::Write; + let data = fields.to_string(); + if data.len() <= 8192 { + let mut file = &result_file; + if let Err(error) = file + .write_all(data.as_bytes()) + .and_then(|_| file.sync_all()) + { + makepad_widgets::log!("studio-test-result-file-error: {}", error); + } + } + } + makepad_widgets::log!("studio-test-result: {}", fields) + }); + StudioExecutor::default().execute_scoped(call, reply, root); + Ok(()) +} + +#[cfg(unix)] +mod scoped { + use super::*; + use std::ffi::{CString, OsStr}; + use std::fs::File; + use std::io::Read; + use std::os::fd::{AsRawFd, FromRawFd, OwnedFd}; + use std::os::unix::ffi::OsStrExt; + use std::os::unix::fs::MetadataExt; + use std::path::Component; + + pub struct Workspace { + dir: Arc, + identity: (u64, u64), + } + fn cstr(s: &OsStr) -> Result { + CString::new(s.as_bytes()).map_err(|_| "NUL in path".into()) + } + fn openat(dir: i32, name: &OsStr, flags: i32) -> Result { + let c = cstr(name)?; + let fd = unsafe { + libc::openat( + dir, + c.as_ptr(), + flags | libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK, + 0o600, + ) + }; + if fd < 0 { + return Err(std::io::Error::last_os_error().to_string()); + } + Ok(unsafe { OwnedFd::from_raw_fd(fd) }) + } + fn parts(path: &str) -> Result, String> { + let parts: Result, _> = Path::new(path) + .components() + .map(|p| match p { + Component::Normal(n) => Ok(n), + _ => Err("path must stay relative to your own workspace".to_string()), + }) + .collect(); + let parts = parts?; + if parts.is_empty() { + return Err("empty path".into()); + } + if parts[0].to_string_lossy().eq_ignore_ascii_case("contexts") { + return Err("other conversation folders are not inputs".into()); + } + Ok(parts) + } + impl Workspace { + pub fn open(root: &Path, context: Option<&str>) -> Result { + let mut fd = openat( + libc::AT_FDCWD, + root.as_os_str(), + libc::O_RDONLY | libc::O_DIRECTORY, + )?; + if let Some(context) = context { + let component = parts(context)?; + if component.len() != 1 { + return Err("invalid trusted context id".into()); + } + fd = openat( + fd.as_raw_fd(), + OsStr::new("contexts"), + libc::O_RDONLY | libc::O_DIRECTORY, + )?; + fd = openat( + fd.as_raw_fd(), + component[0], + libc::O_RDONLY | libc::O_DIRECTORY, + )?; + } + let file = File::from(fd); + let m = file.metadata().map_err(|e| e.to_string())?; + Ok(Self { + identity: (m.dev(), m.ino()), + dir: Arc::new(file.into()), + }) + } + pub fn verify(&self, root: &Path, context: Option<&str>) -> Result<(), String> { + if Self::open(root, context)?.identity != self.identity { + return Err("workspace changed during render".into()); + } + Ok(()) + } + pub fn read(&self, path: &str, cap: usize) -> Result { + let names = parts(path)?; + let mut dir = self.dir.clone(); + for name in &names[..names.len() - 1] { + dir = Arc::new(openat( + dir.as_raw_fd(), + name, + libc::O_RDONLY | libc::O_DIRECTORY, + )?); + } + let mut file = File::from(openat( + dir.as_raw_fd(), + names[names.len() - 1], + libc::O_RDONLY, + )?); + let meta = file.metadata().map_err(|e| e.to_string())?; + if !meta.is_file() || meta.len() > cap as u64 { + return Err("input must be a bounded regular file".into()); + } + let mut out = String::new(); + (&mut file) + .take(cap as u64 + 1) + .read_to_string(&mut out) + .map_err(|e| e.to_string())?; + if out.len() > cap { + return Err("input exceeds render limit".into()); + } + Ok(out) + } + pub fn result_file(&self) -> Result { + openat( + self.dir.as_raw_fd(), + OsStr::new("studio-result.json"), + libc::O_WRONLY | libc::O_CREAT | libc::O_EXCL, + ) + .map(File::from) + } + pub fn output(&self) -> Result { + let name = format!(".studio-{}.png", uuid::Uuid::new_v4()); + let fd = openat( + self.dir.as_raw_fd(), + OsStr::new(&name), + libc::O_WRONLY | libc::O_CREAT | libc::O_EXCL, + )?; + Ok(Output { + file: File::from(fd), + dir: self.dir.clone(), + name, + keep: false, + }) + } + } + pub struct Output { + pub file: File, + dir: Arc, + name: String, + keep: bool, + } + impl Output { + pub fn keep(mut self) -> String { + self.keep = true; + self.name.clone() + } + } + impl Drop for Output { + fn drop(&mut self) { + if !self.keep { + if let Ok(name) = CString::new(self.name.clone()) { + unsafe { + libc::unlinkat(self.dir.as_raw_fd(), name.as_ptr(), 0); + } + } + } + } + } +} + +#[cfg(all(test, unix))] +mod tests { + use super::*; + use std::os::unix::fs::symlink; + + #[test] + fn context_inputs_and_outputs_stay_with_the_caller() { + let temp = crate::app_storage::tests::Scratch::new("studio"); + let root = temp.0.as_path(); + std::fs::create_dir_all(root.join("contexts/a")).unwrap(); + std::fs::create_dir_all(root.join("contexts/b")).unwrap(); + std::fs::write(root.join("contexts/a/card.l0"), "own").unwrap(); + std::fs::write(root.join("contexts/b/card.l0"), "other").unwrap(); + let scope = scoped::Workspace::open(root, Some("a")).unwrap(); + assert_eq!(scope.read("card.l0", SOURCE_MAX).unwrap(), "own"); + for path in ["../b/card.l0", "/etc/passwd", "../../card.l0"] { + assert!(scope.read(path, SOURCE_MAX).is_err(), "{path}"); + } + assert!(scoped::Workspace::open(root, Some("../b")).is_err()); + let output = scope.output().unwrap(); + let name = output.keep(); + assert!(root.join("contexts/a").join(&name).is_file()); + assert!(!root.join(&name).exists()); + assert!(!root.join("contexts/b").join(&name).exists()); + let peer = scoped::Workspace::open(root, None).unwrap(); + assert!(peer.read("contexts/b/card.l0", SOURCE_MAX).is_err()); + assert!(peer.read("Contexts/b/card.l0", SOURCE_MAX).is_err()); + } + + #[test] + fn inputs_reject_links_devices_and_oversized_data() { + let temp = crate::app_storage::tests::Scratch::new("studio"); + let outside = crate::app_storage::tests::Scratch::new("studio"); + std::fs::write(outside.0.as_path().join("private"), "private").unwrap(); + symlink(outside.0.as_path(), temp.0.as_path().join("jump")).unwrap(); + symlink( + outside.0.as_path().join("private"), + temp.0.as_path().join("file-link"), + ) + .unwrap(); + symlink("/dev/zero", temp.0.as_path().join("device")).unwrap(); + std::fs::write(temp.0.as_path().join("large"), vec![b'x'; DATA_MAX + 1]).unwrap(); + let scope = scoped::Workspace::open(temp.0.as_path(), None).unwrap(); + for path in ["jump/private", "file-link", "device", "large"] { + assert!(scope.read(path, DATA_MAX).is_err(), "{path}"); + } + std::fs::create_dir_all(temp.0.as_path().join("contexts")).unwrap(); + symlink(outside.0.as_path(), temp.0.as_path().join("contexts/other")).unwrap(); + assert!(scoped::Workspace::open(temp.0.as_path(), Some("other")).is_err()); + } + + #[test] + fn failed_outputs_are_unlinked_and_workspace_replacement_is_detected() { + let temp = crate::app_storage::tests::Scratch::new("studio"); + let root = temp.0.as_path().join("workspace"); + std::fs::create_dir(&root).unwrap(); + let scope = scoped::Workspace::open(&root, None).unwrap(); + { + let _output = scope.output().unwrap(); + } + assert_eq!(std::fs::read_dir(&root).unwrap().count(), 0); + std::fs::rename(&root, temp.0.as_path().join("old")).unwrap(); + std::fs::create_dir(&root).unwrap(); + assert!(scope.verify(&root, None).is_err()); + } + + #[test] + fn schema_does_not_accept_workspace_output_or_bundle_paths() { + let schema = declaration("system")["input_schema"].clone(); + assert!(super::super::schema::check( + &schema, + &json!({"source_path":"main.card","dark":true}) + ) + .is_ok()); + for extra in [ + "workspace", + "app", + "context_id", + "output", + "bundle", + "width", + ] { + let mut args = json!({"source_path":"main.card"}); + args[extra] = json!("untrusted"); + assert!( + super::super::schema::check(&schema, &args).is_err(), + "{extra}" + ); + } + } + + #[test] + fn cancellation_marks_the_running_call() { + let executor = StudioExecutor::default(); + let flag = Arc::new(AtomicBool::new(false)); + let id = format!("cancel-test-{}", uuid::Uuid::new_v4()); + executor + .running + .lock() + .unwrap() + .insert(id.clone(), flag.clone()); + executor.cancel(&id); + assert!(flag.load(Ordering::SeqCst)); + } +} diff --git a/crates/shell/src/host_tools/tests.rs b/crates/shell/src/host_tools/tests.rs index bf98685bc..caeff24bb 100644 --- a/crates/shell/src/host_tools/tests.rs +++ b/crates/shell/src/host_tools/tests.rs @@ -556,6 +556,7 @@ fn a_peer_is_offered_exactly_its_granted_toolbox_tools_marked_with_their_owner_a assert!(offered_names(&relay, "calendar", false, true).is_empty()); let mut every = shareable_native_tools(); every.push(super::relay::DEV_RUN.to_string()); + if super::studio::SUPPORTED { every.push(super::studio::RENDER.to_string()); } every.sort(); assert_eq!(offered_names(&relay, "calendar", true, true), every); } @@ -1171,3 +1172,43 @@ fn a_cancelled_confirm_app_call_is_withdrawn_from_the_apps_sheet() { assert!(!w.router.is_pending(&id), "withdrawn with the call"); assert!(exec.0.lock().unwrap().is_empty()); } + +/// Studio uses the host executor for only its covered owner, including the +/// system session; an arbitrary cross-app declaration cannot grant it. +#[test] +fn studio_render_is_scoped_revocable_and_schema_checked() { + let mut relay = Relay::default(); + let host = Arc::new(Exec::default()); + relay.set_executor(super::relay::HOST_EXECUTOR, Some(host.clone())); + let mut w = World::new(FixedDevMode::all()); + w.dev_all = true; + let make = |id: &str, caller: &str, owner: &str, system: bool| { + let mut c = call(id, super::studio::RENDER, caller); + c.app = owner.into(); + c.args = json!({"source_path":"draft.card"}); + if system { c.caller_kind = CallerKind::System; } + c + }; + for (id, caller, system) in [("s1", "os.news", false), ("s2", super::SYSTEM, true)] { + let (r, _) = reply(id); + relay.handle(Event::Call { call: make(id, caller, caller, system), reply: r }, &mut w); + } + assert_eq!(host.0.lock().unwrap().len(), if super::studio::SUPPORTED { 2 } else { 0 }); + if !super::studio::SUPPORTED { return; } + let (r, sent) = reply("cross"); + relay.handle(Event::Call { call: make("cross", "os.news", "os.mail", false), reply: r }, &mut w); + assert_eq!(sent.lock().unwrap()[0]["error"]["kind"], "not_granted"); + let mut bad = make("bad", "os.news", "os.news", false); + bad.args["workspace"] = json!("/other-app"); + let (r, sent) = reply("bad"); + relay.handle(Event::Call { call: bad, reply: r }, &mut w); + assert_eq!(sent.lock().unwrap()[0]["error"]["kind"], "invalid_args"); + w.dev_all = false; + let (r, sent) = reply("revoked"); + relay.handle(Event::Call { call: make("revoked", super::SYSTEM, super::SYSTEM, true), reply: r }, &mut w); + assert_eq!(sent.lock().unwrap()[0]["error"]["kind"], "not_granted"); + relay.handle(Event::Cancel { call_id:"s1".into(),reason:"stopped".into() }, &mut w); + assert_eq!(host.1.lock().unwrap().as_slice(), &["s1".to_string()]); + assert!(!relay.catalog.offered("os.news", false, true).iter().any(|d| d["name"] == super::studio::RENDER)); + assert!(relay.catalog.offered("os.news", true, true).iter().any(|d| d["name"] == super::studio::RENDER)); +} diff --git a/crates/shell/src/lib.rs b/crates/shell/src/lib.rs index 1ae2a82b7..e87ef39da 100644 --- a/crates/shell/src/lib.rs +++ b/crates/shell/src/lib.rs @@ -73,6 +73,8 @@ pub mod wm_reply; pub mod ext; pub mod glance; pub mod glance_card; +pub mod studio; +use studio::Renderer as StudioRenderer; pub mod glance_chat; pub mod glance_digest; #[cfg(any(feature = "app-hub", native_mobile))] @@ -505,6 +507,7 @@ pub struct App { /// at without a screen (the GPU readback does not need one). #[rust] pub test_capture: Option<(Timer, std::path::PathBuf)>, + #[rust] pub studio: StudioRenderer, #[rust] pub test_capture_ticket: Option, #[rust] pub test_recording: bool, /// `--test-action page:`: the home page to jump to, once the phone @@ -5128,22 +5131,15 @@ impl App { self.handle_event(cx, &event); } - /// The test actions' timers: a `capture:` tick writes the next frame; a - /// due `ask-appcard:` sends its text to the appcard instance's executor - /// exactly as the assistant's `ask` call would. - fn fire_test_timers(&mut self, cx: &mut Cx, te: &TimerEvent) { - if let Some((timer, path)) = self.test_capture.clone() { - if timer.is_timer(te).is_some() { - #[cfg(not(target_os = "android"))] - { - let tmp = path.with_extension("part.png"); - cx.capture_next_frame_to_file(tmp); - let _ = std::fs::rename(path.with_extension("part.png"), path); - } - #[cfg(target_os = "android")] - { - for result in cx.try_take_texture_readbacks() { - if Some(result.ticket) != self.test_capture_ticket { continue; } + /// One consumer drains the platform queue; a test capture cannot discard + /// Studio's ticket (or vice versa). Cancelled unowned tickets are drained + /// too, releasing their platform readback reservation. + fn route_texture_readbacks(&mut self, cx: &mut Cx) { + for result in cx.try_take_texture_readbacks() { + let Some(result) = self.studio.readback(cx, result) else {continue}; + #[cfg(target_os = "android")] + if Some(result.ticket) == self.test_capture_ticket { + let Some((_, path)) = self.test_capture.clone() else {continue}; self.test_capture_ticket = None; let output = if self.test_recording { let timestamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_millis(); @@ -5166,7 +5162,26 @@ impl App { if std::fs::write(&tmp,png).is_ok() { let _ = std::fs::rename(tmp,output); } } }) { Ok(task) => task.detach(), Err(error) => log!("wm: capture worker unavailable: {error}") } - } + } + #[cfg(not(target_os = "android"))] + let _ = result; + } + } + + /// The test actions' timers: a `capture:` tick writes the next frame; a + /// due `ask-appcard:` sends its text to the appcard instance's executor + /// exactly as the assistant's `ask` call would. + fn fire_test_timers(&mut self, cx: &mut Cx, te: &TimerEvent) { + if let Some((timer, _path)) = self.test_capture.clone() { + if timer.is_timer(te).is_some() { + #[cfg(not(target_os = "android"))] + { + let tmp = _path.with_extension("part.png"); + cx.capture_next_frame_to_file(tmp); + let _ = std::fs::rename(_path.with_extension("part.png"), _path); + } + #[cfg(target_os = "android")] + { if self.test_capture_ticket.is_none() { let focus = self.state_mut().layout.focused_client(); let texture = focus.and_then(|client|self.desk(cx).borrow::().and_then(|desk|desk.phone_client_texture(client))); @@ -5268,6 +5283,12 @@ impl App { i += 2; continue; } + #[cfg(unix)] + if let Some(path) = name.strip_prefix("studio-render:") { + if let Err(error) = host_tools::studio::test_action(path) { log!("studio test: {error}"); } + i += 2; + continue; + } // capture:: write the presented frame to // every 5 s — a scripted run's screen, with no display // (or a locked one) needed. @@ -6311,6 +6332,8 @@ impl App { pub fn shell_handle_event(&mut self, cx: &mut Cx, event: &Event) { self.webview_render.handle_event(cx, event); self.shell_handle_event_inner(cx, event); + // Studio is independent of modal/phone routes that may consume an event. + if let Event::Draw(draw) = event { self.studio.draw(cx, draw); } // Whatever module panicked during this event — in its tile's event // or draw, or in a call the shell made — is contained by now; show // it closed and free it before the next event (module_host.rs). @@ -6327,6 +6350,9 @@ impl App { } fn shell_handle_event_inner(&mut self, cx: &mut Cx, event: &Event) { + let studio_surface = studio::surface_geometry(cx); + self.studio.event(cx, event, studio_surface); + self.route_texture_readbacks(cx); // Quitting, or closing the shell's window, asks the hosted instances // first (makepad#65). A termination signal is never refused. match event { diff --git a/crates/shell/src/studio/mod.rs b/crates/shell/src/studio/mod.rs new file mode 100644 index 000000000..211d159c9 --- /dev/null +++ b/crates/shell/src/studio/mod.rs @@ -0,0 +1,721 @@ +//! Foreground-only, unpublished L0 previews. No app policy or live app storage +//! is inherited: each render receives a disposable, zero-quota isolate. +use crate::dev_mode::DevTag; +use makepad_widgets::*; +use serde_json::Value; +use std::{ + collections::{HashMap, VecDeque}, + fs::File, + io::Write, + path::PathBuf, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::Sender, + Arc, Mutex, OnceLock, + }, + time::{Duration, Instant}, +}; + +const DEADLINE: Duration = Duration::from_secs(20); +const MAX_JOBS: usize = 4; +const MAX_PNG: usize = 5 * 1024 * 1024; + +pub struct RenderJob { + pub id: String, + pub app: String, + pub source: String, + pub data: Value, + pub dark: bool, + /// None uses the current surface's glance width. Reserved for host tests. + pub width: Option, + pub output: File, + pub reply: Sender>, + pub dev_tag: DevTag, +} +#[derive(Debug)] +pub struct RenderResult { + pub width: u32, + pub height: u32, + pub settled: bool, +} +struct Pending { + job: RenderJob, + cancel: Arc, + started: Instant, +} +#[derive(Default)] +struct Queue { + jobs: VecDeque, + tokens: HashMap>, +} +fn queue() -> &'static Mutex { + static Q: OnceLock> = OnceLock::new(); + Q.get_or_init(Default::default) +} +fn authorized(job: &RenderJob) -> bool { + crate::dev_mode::tag_valid(&job.dev_tag) && crate::dev_mode::grants_all(&job.app) +} +pub fn submit(job: RenderJob) -> Result<(), String> { + if !authorized(&job) { + return Err("studio requires a current developer-mode grant".into()); + } + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + if q.tokens.len() >= MAX_JOBS || q.tokens.contains_key(&job.id) { + return Err("studio_busy".into()); + } + let cancel = Arc::new(AtomicBool::new(false)); + q.tokens.insert(job.id.clone(), cancel.clone()); + q.jobs.push_back(Pending { + job, + cancel, + started: Instant::now(), + }); + SignalToUI::set_ui_signal(); + Ok(()) +} +pub fn cancel(id: &str) { + if let Some(cancel) = queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .tokens + .get(id) + { + cancel.store(true, Ordering::Release); + } + SignalToUI::set_ui_signal(); +} +fn retire(id: &str) { + queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .tokens + .remove(id); + SignalToUI::set_ui_signal(); +} +fn validity(p: &Pending) -> Result<(), String> { + if p.cancel.load(Ordering::Acquire) { + return Err("studio_cancelled".into()); + } + if !authorized(&p.job) { + return Err("studio_grant_expired".into()); + } + if p.started.elapsed() >= DEADLINE { + return Err("studio_timeout".into()); + } + Ok(()) +} + +/// Geometry from the platform's created root window, in layout points. The +/// widget lookup and shell's cached DPI can still be empty at a test action. +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct SurfaceGeometry { + width: f64, + height: f64, + dpi: f64, +} +impl SurfaceGeometry { + fn from_window(created: bool, geom: &WindowGeom) -> Option { + let width = geom.inner_size.x; + let height = geom.inner_size.y; + let dpi = geom.dpi_factor; + (created + && width.is_finite() + && height.is_finite() + && dpi.is_finite() + && width >= 112.0 + && height > 0.0 + && (0.5..=4.0).contains(&dpi)) + .then_some(Self { width, height, dpi }) + } + fn card_width(self) -> f64 { + self.width - 40.0 + } +} +pub(crate) fn surface_geometry(cx: &Cx) -> Option { + // The shell owns the first window; hosted module windows must never supply + // its geometry. Use the current generation rather than an assumed id_zero. + let id = cx.windows.id_iter().next()?; + let window = &cx.windows[id]; + SurfaceGeometry::from_window(window.is_created, &window.window_geom) +} + +struct Active { + pending: Pending, + frame: WidgetRef, + splash: SplashRef, + pass: DrawPass, + list: DrawList2d, + texture: Texture, + jail: PathBuf, + width: f64, + height: f64, + dpi: f64, + surface: SurfaceGeometry, + ticket: Option, + previous: Option>, + stable: usize, + draws: usize, + repaint: bool, +} +pub struct Renderer { + active: Option, + foreground: bool, + timer: Option, +} +impl Default for Renderer { + fn default() -> Self { + Self { + active: None, + foreground: true, + timer: None, + } + } +} +impl Renderer { + /// Called before the shell may consume an event. Background/cancellation + /// revokes publication immediately, even if a GPU lease is still draining. + pub(crate) fn event(&mut self, cx: &mut Cx, event: &Event, surface: Option) { + match event { + Event::Pause | Event::Background => self.foreground = false, + Event::Resume | Event::Foreground => self.foreground = true, + _ => {} + } + if let Some(a) = self.active.as_ref() { + let status = if !self.foreground { + Err("not_foreground".into()) + } else if surface != Some(a.surface) { + Err("studio_surface_changed".into()) + } else { + validity(&a.pending) + }; + if let Err(e) = status { + self.fail(cx, e); + } + } + if !self.foreground { + // Includes jobs whose PNG worker already owns the output file. + for token in queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .tokens + .values() + { + token.store(true, Ordering::Release); + } + } + if self.active.is_none() { + let pending = queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .jobs + .pop_front(); + if let Some(pending) = pending { + let error = if !self.foreground { + Some("not_foreground".into()) + } else { + validity(&pending).err() + }; + if let Some(e) = error { + let _ = pending.job.reply.send(Err(e)); + retire(&pending.job.id); + } else if let Some(surface) = surface { + match Active::new(cx, pending, surface) { + Ok(a) => self.active = Some(a), + Err((p, e)) => { + let _ = p.job.reply.send(Err(e)); + retire(&p.job.id); + } + } + } else { + // Wait for the native surface; do not turn zero geometry + // into a minimum-width successful image. The original + // enqueue deadline still applies while waiting. + queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .jobs + .push_front(pending); + } + } + } + let waiting = !queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .jobs + .is_empty(); + if (self.active.is_some() || waiting) && self.timer.is_none() { + self.timer = Some(cx.start_interval(0.08)); + } + if self.active.is_none() && !waiting { + if let Some(timer) = self.timer.take() { + cx.stop_timer(timer); + } + } + if let Some(a) = self.active.as_mut() { + // No input reaches the preview, and no host-service pump runs. + if matches!(event, Event::Signal | Event::Timer(_) | Event::NextFrame(_)) { + a.frame.handle_event(cx, event, &mut Scope::empty()); + } + if let Event::Timer(te) = event { + if self + .timer + .as_ref() + .is_some_and(|t| t.is_timer(te).is_some()) + && a.ticket.is_none() + { + a.repaint = true; + cx.redraw_all(); + } + } + } + } + /// A separate root pass never becomes a live tile or enters its store. + pub fn draw(&mut self, cx: &mut Cx, event: &DrawEvent) { + let Some(a) = self + .active + .as_mut() + .filter(|a| a.repaint && a.ticket.is_none()) + else { + return; + }; + a.repaint = false; + let size = dvec2(a.width, a.height); + a.pass.set_size(cx, size); + { + let mut draw = CxDraw::new(cx, event); + let mut cx = Cx2d::new(&mut draw); + cx.begin_pass(&a.pass, Some(a.dpi)); + a.list.begin_always(&mut cx); + cx.begin_root_turtle(size, Layout::flow_overlay()); + let walk = Walk::abs_rect(Rect { + pos: dvec2(0.0, 0.0), + size, + }); + let vm_id = a + .splash + .borrow() + .and_then(|s| cx.script_ref_vm_id(&s.view.source)); + match vm_id { + Some(id) => widget_async::with_isolate(&mut cx, id, |cx| { + a.frame.draw_walk_all(cx, &mut Scope::empty(), walk) + }), + None => a.frame.draw_walk_all(&mut cx, &mut Scope::empty(), walk), + } + cx.end_pass_sized_turtle(); + a.list.end(&mut cx); + cx.end_pass(&a.pass); + } + a.draws += 1; + let measured = a.splash.area().rect(cx).size.y.clamp( + crate::glance_card::TILE_MIN_HEIGHT, + crate::glance_card::TILE_MAX_HEIGHT, + ); + if (a.height - measured).abs() > 0.5 { + a.height = measured; + a.previous = None; + a.stable = 0; + return; + } + // Shader readiness is checked after at least one platform paint. A + // not-yet-compiled shader makes us request another frame, not a PNG. + if a.draws < 2 || !shaders_ready(cx, a.list.id(), 0) { + return; + } + match a + .texture + .read_back(cx, ReadbackRequest { next_render: true }) + { + Ok(ticket) => a.ticket = Some(ticket), + Err(ReadbackError::Backpressure) => {} + Err(e) => self.fail(cx, format!("studio_readback: {e}")), + } + } + /// Return unowned tickets to the shell's shared readback router. + pub fn readback(&mut self, cx: &mut Cx, result: TextureReadback) -> Option { + if !self + .active + .as_ref() + .is_some_and(|a| a.ticket == Some(result.ticket)) + { + return Some(result); + } + let a = self.active.as_mut().unwrap(); + a.ticket = None; + let bytes = match rgba(&result, a.pending.job.dark) { + Ok(b) => b, + Err(e) => { + self.fail(cx, e); + return None; + } + }; + if a.previous.as_ref() == Some(&bytes) { + a.stable += 1; + } else { + a.stable = 0; + } + a.previous = Some(bytes); + if a.stable < 2 { + return None; + } + let a = self.active.take().unwrap(); + let bytes = a.previous.clone().unwrap(); + a.splash.set_text(cx, ""); + let _ = std::fs::remove_dir_all(&a.jail); + let reply = a.pending.job.reply.clone(); + let id = a.pending.job.id.clone(); + // Compression never stalls the UI. Grants, cancellation and the same + // original deadline are checked again before publishing the file. + let task = cx.task_pool().submit(Lane::Heavy, move || { + let mut p = a.pending; + let outcome = (|| { + validity(&p)?; + let png = Cx::encode_rgba_as_png(result.width as u32, result.height as u32, &bytes) + .map_err(|e| format!("studio_png: {e:?}"))?; + if png.len() > MAX_PNG { + return Err("studio_png_too_large".into()); + } + validity(&p)?; + p.job.output.write_all(&png).map_err(|e| e.to_string())?; + p.job.output.flush().map_err(|e| e.to_string())?; + validity(&p)?; + Ok(RenderResult { + width: result.width as u32, + height: result.height as u32, + settled: true, + }) + })(); + let _ = p.job.reply.send(outcome); + retire(&p.job.id); + }); + match task { + Ok(task) => task.detach(), + Err(e) => { + let _ = reply.send(Err(format!("studio_worker: {e}"))); + retire(&id); + } + } + None + } + fn fail(&mut self, cx: &mut Cx, reason: String) { + if let Some(a) = self.active.take() { + if let Some(ticket) = a.ticket { + cx.cancel_texture_readback(ticket); + } + a.splash.set_text(cx, ""); + let _ = std::fs::remove_dir_all(&a.jail); + let _ = a.pending.job.reply.send(Err(reason)); + retire(&a.pending.job.id); + } + } +} +impl Active { + fn new( + cx: &mut Cx, + pending: Pending, + surface: SurfaceGeometry, + ) -> Result { + let body = match crate::glance::prepare_studio( + &pending.job.app, + &pending.job.source, + &pending.job.data, + pending.job.dark, + ) { + Ok(b) => b, + Err(e) => return Err((pending, e)), + }; + if !crate::glance_card::CAN_RENDER { + return Err(( + pending, + "studio renderer needs the App Hub vocabulary".into(), + )); + } + let width = pending.job.width.unwrap_or(surface.card_width()); + let dpi = surface.dpi; + if !width.is_finite() + || !(72.0..=2048.0).contains(&width) + || !dpi.is_finite() + || !(0.5..=4.0).contains(&dpi) + { + return Err((pending, "invalid studio surface geometry".into())); + } + log!("studio-test-geometry: surface_width={} surface_height={} dpi={} card_width={} pixel_width={}", + surface.width, surface.height, dpi, width, (width * dpi).round()); + let base = cx + .get_data_dir() + .map(PathBuf::from) + .unwrap_or_else(std::env::temp_dir); + let jail = base.join(format!(".studio-isolate-{}", uuid::Uuid::new_v4())); + if let Err(e) = std::fs::create_dir(&jail) { + return Err((pending, e.to_string())); + } + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if let Err(e) = std::fs::set_permissions(&jail, std::fs::Permissions::from_mode(0o700)) + { + let _ = std::fs::remove_dir(&jail); + return Err((pending, e.to_string())); + } + } + crate::glance_card::ensure_vocabulary(cx); + let frame = cx.with_vm(|vm| { + let value = script_eval!(vm,{use mod.widgets.* GlanceTileFrame {}}); + WidgetRef::script_from_value(vm, value) + }); + let splash = frame.splash(cx, ids!(card)); + if let Some(mut s) = splash.borrow_mut() { + s.set_allow_net(false); + } + splash.set_sandbox_dir(cx, Some(jail.clone())); + splash.set_storage_quota(cx, Some(0)); + splash.set_host_caps(cx, Vec::new()); + splash.set_host_prompts(cx, false); + splash.set_policy(cx, Some(Vec::new()), Some(5_000_000)); + splash.set_memory_bytes(cx, Some(16 * 1024 * 1024)); + splash.set_text(cx, &body); + let mut children = 0; + if let Some(s) = splash.borrow() { + s.children(&mut |_, _| children += 1); + } + let running = splash + .borrow_mut() + .and_then(|mut s| s.isolate_heap_key(cx)) + .is_some_and(makepad_widgets::splash_policy::may_run); + if children == 0 || !running { + splash.set_text(cx, ""); + let _ = std::fs::remove_dir_all(&jail); + return Err((pending, "studio_eval_failed: preview has no root content or exhausted its instruction budget".into())); + } + let pass = DrawPass::new_with_name(cx, "studio_l0_preview"); + let texture = Texture::new_with_format( + cx, + TextureFormat::RenderBGRAu8 { + size: TextureSize::Auto, + initial: true, + }, + ); + pass.set_color_texture( + cx, + &texture, + DrawPassClearColor::ClearWith(vec4(0.0, 0.0, 0.0, 0.0)), + ); + let list = DrawList2d::new(cx); + cx.redraw_all(); + Ok(Self { + pending, + frame, + splash, + pass, + list, + texture, + jail, + width, + height: crate::glance_card::TILE_DEFAULT_HEIGHT, + dpi, + surface, + ticket: None, + previous: None, + stable: 0, + draws: 0, + repaint: true, + }) + } +} +fn shaders_ready(cx: &Cx, id: DrawListId, depth: usize) -> bool { + if depth > 64 || cx.draw_shaders_pending() { + return false; + } + let items = &cx.draw_lists[id].draw_items; + (0..items.len()).all(|i| { + let kind = &items[i].kind; + if let Some(id) = kind.sub_list() { + return shaders_ready(cx, id, depth + 1); + } + if let Some(call) = kind.draw_call() { + #[cfg(any(target_os = "android", target_os = "linux", target_os = "windows"))] + { + return cx.is_draw_shader_window_ready(call.draw_shader_id); + } + #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "windows")))] + { + let _ = call; + } + } + true + }) +} +/// Platform readbacks are premultiplied. Flatten before PNG encoding rather +/// than interpreting premultiplied bytes as straight-alpha PNG samples. +fn rgba(result: &TextureReadback, dark: bool) -> Result, String> { + let bytes = result + .data + .as_ref() + .map_err(|e| format!("studio_readback: {e}"))?; + let row = result + .width + .checked_mul(4) + .ok_or("invalid readback dimensions")?; + let total = result + .stride + .checked_mul(result.height) + .ok_or("invalid readback dimensions")?; + if result.stride < row || bytes.len() < total { + return Err("invalid readback stride".into()); + } + let mut out = Vec::with_capacity(row * result.height); + let backdrop = if dark { 24u16 } else { 255u16 }; + for y in 0..result.height { + let y = match result.origin { + ReadbackOrigin::TopLeft => y, + ReadbackOrigin::BottomLeft => result.height - 1 - y, + }; + for p in bytes[y * result.stride..y * result.stride + row].chunks_exact(4) { + let rgb = match result.channel_order { + ReadbackChannelOrder::Rgba => [p[0], p[1], p[2]], + ReadbackChannelOrder::Bgra => [p[2], p[1], p[0]], + }; + for c in rgb { + out.push( + (u16::from(c) + (backdrop * (255 - u16::from(p[3])) + 127) / 255).min(255) + as u8, + ); + } + out.push(255); + } + } + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + fn pixels(order: ReadbackChannelOrder, origin: ReadbackOrigin, data: &[u8]) -> TextureReadback { + TextureReadback { + ticket: ReadbackTicket(1), + allocation_generation: 1, + producer_serial: 1, + width: 1, + height: 2, + stride: 8, + channel_order: order, + origin, + data: Ok(Arc::from(data)), + } + } + #[test] + fn png_input_flattens_premultiplied_rows_and_respects_padding_and_origin() { + let result = pixels( + ReadbackChannelOrder::Bgra, + ReadbackOrigin::BottomLeft, + &[0, 0, 128, 128, 9, 9, 9, 9, 30, 20, 10, 255, 9, 9, 9, 9], + ); + assert_eq!( + rgba(&result, false).unwrap(), + [10, 20, 30, 255, 255, 127, 127, 255] + ); + assert_eq!( + rgba(&result, true).unwrap(), + [10, 20, 30, 255, 140, 12, 12, 255] + ); + } + #[test] + fn bad_readback_payloads_fail_before_encoding() { + assert!(rgba( + &pixels(ReadbackChannelOrder::Rgba, ReadbackOrigin::TopLeft, &[0; 8]), + false + ) + .is_err()); + let mut result = pixels( + ReadbackChannelOrder::Rgba, + ReadbackOrigin::TopLeft, + &[0; 16], + ); + result.data = Err(ReadbackError::Cancelled); + assert!(rgba(&result, false).unwrap_err().contains("Cancelled")); + } + #[test] + fn preview_is_l0_only_and_explicit_modes_do_not_depend_on_global_dark() { + let (source, data) = crate::glance::demo_digest(); + let light = crate::glance::prepare_studio("os.news", &source, &data, false).unwrap(); + let dark = crate::glance::prepare_studio("os.news", &source, &data, true).unwrap(); + assert_ne!(light, dark); + assert_eq!( + light, + crate::glance::prepare_studio("os.news", &source, &data, false).unwrap() + ); + assert!(crate::glance::prepare_studio("os.news", "View{}", &data, false).is_err()); + assert!(crate::glance::prepare_studio("os.news", &source, &Value::Null, false).is_err()); + } + #[test] + fn digest_values_are_host_owned_and_chat_snapshots_fail_closed() { + let source = include_str!("../../resources/glance/news-brief.card"); + let data = serde_json::json!({"brief":{"summary":"FORGED","points":[],"sources":[]},"$status":{"brief":"ready"}}); + let body = crate::glance::prepare_studio("os.news", source, &data, false).unwrap(); + assert!(!body.contains("FORGED")); + assert!( + crate::glance::prepare_studio("os.mail", source, &data, false) + .unwrap_err() + .contains("own app") + ); + let (_, _, source, data) = crate::glance::demo_mail().remove(0); + assert!( + crate::glance::prepare_studio("os.mail", &source, &data, false) + .unwrap_err() + .contains("studio_chat_unsupported") + ); + } + #[test] + fn asynchronous_images_are_refused_instead_of_reported_as_settled() { + let (source, mut data) = crate::glance::demo_digest(); + let source = source.replace( + "TextTitle(text: status.message, width: .fill)", + "Photo(src: status.message)", + ); + data["status"]["message"] = serde_json::json!("https://example.invalid/image.png"); + let error = crate::glance::prepare_studio("os.news", &source, &data, false).unwrap_err(); + assert!(error.contains("studio_images_unsupported"), "{error}"); + } + + #[test] + fn unowned_readback_is_preserved_for_other_shell_consumers() { + let mut cx = Cx::new(Box::new(|_, _| {})); + let mut renderer = Renderer::default(); + let original = pixels( + ReadbackChannelOrder::Rgba, + ReadbackOrigin::TopLeft, + &[0; 16], + ); + assert_eq!( + renderer.readback(&mut cx, original).unwrap().ticket, + ReadbackTicket(1) + ); + } + #[test] + fn geometry_waits_for_created_surface_and_uses_its_actual_scale() { + let mut geom = WindowGeom { + inner_size: dvec2(0.0, 0.0), + dpi_factor: 1.0, + ..Default::default() + }; + assert!(SurfaceGeometry::from_window(true, &geom).is_none()); + geom.inner_size = dvec2(432.0, 912.0); + geom.dpi_factor = 2.5; + assert!(SurfaceGeometry::from_window(false, &geom).is_none()); + let surface = SurfaceGeometry::from_window(true, &geom).unwrap(); + assert_eq!(surface.card_width(), 392.0); + assert_eq!(surface.card_width() * surface.dpi, 980.0); + for invalid in [0.0, f64::NAN, f64::INFINITY] { + geom.dpi_factor = invalid; + assert!(SurfaceGeometry::from_window(true, &geom).is_none()); + } + } + #[test] + fn background_events_close_admission_and_resume_reopens_it() { + let mut cx = Cx::new(Box::new(|_, _| {})); + let mut renderer = Renderer::default(); + renderer.event(&mut cx, &Event::Background, None); + assert!(!renderer.foreground); + renderer.event(&mut cx, &Event::Foreground, None); + assert!(renderer.foreground); + } +} diff --git a/crates/shell/src/system_chat/session.rs b/crates/shell/src/system_chat/session.rs index 196ac14d5..40a614d5c 100644 --- a/crates/shell/src/system_chat/session.rs +++ b/crates/shell/src/system_chat/session.rs @@ -90,6 +90,8 @@ pub trait SystemHost: Send { /// The host tools to register on the system session now (empty: none /// granted, or the grant was withdrawn). fn declarations(&self) -> Vec; + /// Workspace confirmed by this connection, never a model-supplied path. + fn workspace_opened(&self, _workspace: Option<&str>) {} } /// The shell's: Setup's grants and the app peers' host state. @@ -107,8 +109,17 @@ impl SystemHost for ShellSystemHost { // Which apps have an agent, and asking the person to allow one // (ADR 0004 §4; answered by the chat itself, `crate::agents`). decls.extend(crate::agents::declarations()); + // octos apply_turn_host_tool_rosters applies the kernel allowlist + // BEFORE adding this connection-owned host set. No baseline kernel + // tool grant is needed, and external turns cannot inherit this set. + if crate::host_tools::studio::SUPPORTED && crate::dev_mode::grants_all(crate::host_tools::SYSTEM) { + decls.push(crate::host_tools::studio::declaration(crate::host_tools::SYSTEM)); + } decls } + fn workspace_opened(&self, workspace: Option<&str>) { + crate::host_tools::studio::system_workspace_opened(workspace); + } } /// What the UI asks of the driver. @@ -438,6 +449,7 @@ impl Driver { } fn drop_link(&mut self) { + self.system_host.workspace_opened(None); self.link = None; self.pending.clear(); self.opened = false; @@ -586,6 +598,7 @@ impl Driver { self.retry_at = Some(Instant::now() + Duration::from_secs(3)); } None => { + self.system_host.workspace_opened(result.and_then(|r| r["opened"]["workspace_root"].as_str())); self.opened = true; self.model.set_phase(Phase::Ready); self.sync_tools(); diff --git a/crates/shell/src/system_chat/tests.rs b/crates/shell/src/system_chat/tests.rs index 4e1a178f1..bfd5df3da 100644 --- a/crates/shell/src/system_chat/tests.rs +++ b/crates/shell/src/system_chat/tests.rs @@ -27,6 +27,7 @@ struct Script { unavailable: Option, /// `session/open` fails with this message. open_error: Option, + opened_workspace: Option, } #[derive(Clone, Default)] @@ -57,7 +58,7 @@ impl Link for FakeLink { let reply = match method.as_str() { "session/open" => match &s.open_error { Some(e) => json!({"jsonrpc": "2.0", "id": id, "error": {"code": -32000, "message": e}}), - None => json!({"jsonrpc": "2.0", "id": id, "result": {"opened": {"session_id": SYSTEM_SESSION}}}), + None => json!({"jsonrpc": "2.0", "id": id, "result": {"opened": {"session_id": SYSTEM_SESSION, "workspace_root": s.opened_workspace}}}), }, "session/hydrate" => json!({"jsonrpc": "2.0", "id": id, "result": {"messages": s.history.clone()}}), _ => json!({"jsonrpc": "2.0", "id": id, "result": {}}), @@ -1070,3 +1071,22 @@ fn the_system_agent_gets_the_read_tools_of_the_native_apps_that_run_here_never_t assert_eq!(calculator.system_tools, ["calculator.eval"]); assert_eq!(crate::native_apps::find("notes").unwrap().system_tools, ["notes.search", "notes.read"]); } + +#[test] +fn kernel_confirmed_system_workspace_is_delivered_and_cleared_on_disconnect() { + struct Workspaces(Arc>>>); + impl SystemHost for Workspaces { + fn declarations(&self) -> Vec { vec![] } + fn workspace_opened(&self, path: Option<&str>) { self.0.lock().unwrap().push(path.map(str::to_owned)); } + } + let fake = Fake::default(); + fake.s().opened_workspace = Some("/kernel/system-workspace".into()); + let seen = Arc::new(Mutex::new(Vec::new())); + let mut driver = Driver::with_system_host(Box::new(FakeConnector(fake.clone())), Box::new(Workspaces(seen.clone()))); + driver.command(Command::Open); + settle(&mut driver); + assert_eq!(seen.lock().unwrap().last().cloned(), Some(Some("/kernel/system-workspace".into()))); + driver.command(Command::Close); + settle(&mut driver); + assert_eq!(seen.lock().unwrap().last().cloned(), Some(None)); +} diff --git a/docs/adr/0006-app-studio-on-the-phone.md b/docs/adr/0006-app-studio-on-the-phone.md index 72b21fbd2..fbef59f5b 100644 --- a/docs/adr/0006-app-studio-on-the-phone.md +++ b/docs/adr/0006-app-studio-on-the-phone.md @@ -1,10 +1,35 @@ # ADR 0006: App Studio on the phone - **Date:** 2026-10-02 -- **Status:** Accepted (2026-10-03). Implementation has not started; milestone 1 comes first. +- **Status:** Accepted (2026-10-03). Milestone 1 is in progress; device acceptance is still required. Milestones 2–5 remain unimplemented. - **Scope:** How an agent on the phone turns an image (a generated design or a screenshot of an existing app) into an OctoSense app or glance card, looks at its own result and improves it, entirely on the phone, at first in developer mode. Covers the inputs, the in-process renderer, the checks, the rules that can change without a build, the tools agents get, and what of the OctoScript App Design Flow moves to the phone. There is no compile in the loop and no Mac. - **Relates to:** [ADR 0002](0002-event-driven-app-agents.md) (§6 the `card_render` and `card_critique_payload` toolbox tools; §7 a card is rendered, critiqued and revised before it is published; milestone M6); [ADR 0004](0004-native-apps-hosting-and-peers.md) (app agents, host tools, approvals, §13 developer mode); [ADR 0005](0005-app-contract.md) (the app contract and bundles); [Home ADR 0004](home/0004-system-apps-are-contained-script-apps.md) (contained script apps); [Home ADR 0005](home/0005-settings-octoscript-controller.md) and [Home ADR 0006](home/0006-builtin-settings.md) (Settings and its developer options); App Hub's [`card-studio`](https://github.com/OctoSense-org/OctoSense-App-Hub/tree/main/crates/card-studio) crate and [skill](https://github.com/OctoSense-org/OctoSense-App-Hub/tree/main/skills/card-studio); the [OctoScript App Design Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) (`flows/image-to-card`, `flows/image-lib`); octos issue #1149, closed, which added the `image_generation` stub (its backend needs a new issue). +## Implementation status + +Milestone 1 is in progress. Its first implementation is a developer-only +`studio.render` tool for L0 glance previews on Unix hosts, including Android. +The system conversation uses the workspace confirmed by the kernel; an app +agent uses its own account/context workspace. The model supplies relative +`source_path`, optional `data_path`, and optional `dark`; it cannot select a +workspace, output destination or rendering width. Source is limited to 16 KiB +and data JSON to 32 KiB. The result is `{path, width, height, settled}`, with a +relative PNG path for a separate `view_image` call and dimensions in pixels. + +This slice uses the real glance width, theme and height bounds, with disposable +storage, no network or host-service grants, and foreground/cancellation checks. +Bundle screens, images and interactive chat are not supported by this first +renderer. The render deadline is 20 seconds, inside the host wait and kernel +call deadline. It does not implement checks, comparison, the editable studio +runner, bundle installation, image generation or ROM capture sessions. + +The Android build wrapper accepts `--dev-mode`, separately from the +`--development` signing choice; `--package-name` selects a standalone Home +test identity. Python tests cover build plans and receipt guards. Renderer and +routing validation, followed by OnePlus 6 acceptance and model image delivery, +are still required before milestone 1 is complete. The [device probe](../../tools/studio-device-probe.py) exercises denial, light/dark PNGs and background cancellation in an already installed, debuggable studio test package; it does not test model image delivery. The remaining sections +describe the full accepted design, including work for milestones 2–5. + ## Context Making an OctoSense app or card from a picture works today only on a desktop: @@ -35,15 +60,15 @@ What the phone already has: - start an app (`startActivity`, `startTask`); - inject input (`tap`, `swipe`, `typeText`, `pressKey`), which it refuses while the keyguard is showing. `captureScreen` and `startActivity` do not check the keyguard. - **No image generation yet.** octos registers an `image_generation` tool but binds no backend, so every call fails with `no_backend_bound`. The tool is on no client's tool list: not octos's external clients', not the system agent's, not the developer app agents'. -- **No developer mode on a phone today.** A development build (`cfg(dev_mode)`) honours it from Settings; a release build only with the `--dev-grant-all` launch flag, which a phone cannot pass. The Home APK is built `--release` without the feature. In a home with real accounts developer mode ends after 8 hours; only a home marked as a developer profile keeps it ([`dev_mode.rs`](../../crates/shell/src/dev_mode.rs)). A phone has one home. +- **Phone developer mode.** A development build (`cfg(dev_mode)`) honours it from Settings; an ordinary release build only with the `--dev-grant-all` launch flag, which a phone cannot pass. The Home APK still defaults to `--release` without the feature; the milestone 1 `--dev-mode` build option includes the existing feature while retaining release optimization. In a home with real accounts developer mode ends after 8 hours; only a home marked as a developer profile keeps it ([`dev_mode.rs`](../../crates/shell/src/dev_mode.rs)). A phone has one home. -For many people the phone is the only computer they have. An agent that can make and refine its own apps there should not need a Mac or a server. +For many people the phone is the only computer they have. An agent that can make and refine its own apps there should not need a desktop or a build/render server. ## Decision ### 1. An App Studio that runs on the phone, developer mode first -The whole loop runs on the device: +The authoring workspace and rendering loop live on the device: 1. **Take an image.** 2. **Draft** an app or card. @@ -53,7 +78,7 @@ The whole loop runs on the device: 6. **Critique** it with the agent's own vision. 7. **Edit** it, then render again. -There is no compile in the loop and no Mac or server anywhere. The first release is gated to developer mode (section 8). Normal mode follows once approvals cover screen capture and installs. +There is no compile in the loop and no desktop or build/render server is required. Generation and vision still call the model providers configured by the person. The first release is gated to developer mode (section 8). Normal mode follows once approvals cover screen capture and installs. ### 2. The input is an image; there is no Sketch path on the phone @@ -62,7 +87,8 @@ An image comes from one of two places. - **Generated designs.** The person, or the agent, asks for a design. Generation goes through a provider the person configured in AI providers, and the PNG lands in the studio project's folder. octos's `image_generation` tool gets this backend instead of a separate OctoSense tool, and joins the tool lists that need it (octos's external clients, the system agent, developer app agents), so every client sees one tool. The person types the image provider's key on AI providers' host sheet, and it is kept like every provider key, where the kernel reads it: in the profile's `env_vars` on a phone (app-private, mode 0600), behind a `keychain:` marker on a desktop. octos's backend resolves it from there as it resolves an LLM key, and no app ever sees it. - **Screenshots of existing apps, to clone them.** - On every phone, the person picks screenshots with the system document picker, as AI providers' QR import does (one PNG or JPEG at a time today; picking several is new), or shares images to OctoSense (Home's share target accepts only text today; image shares are new). - - On the OctoSense ROM, in developer mode and after the person approves a capture session, the studio drives the agent service. It opens the app (`startActivity`), walks its screens (`tap`, `swipe`) and captures each one (`captureScreen`). A session is bound to one app and ends when another app comes to the front. It only navigates, never types (`typeText` is not used), refuses to start or continue while the phone is locked, and shows a stop control the person can use at any time. + - On the OctoSense ROM, in developer mode and after the person approves a capture session, the studio drives the agent service. It opens the app (`startActivity`) and captures its screens (`captureScreen`); automated input has the additional guard below. A session is bound to one app and ends when another app comes to the front. It refuses to start or continue while the phone is locked and shows a stop control the person can use at any time. Coordinates alone cannot distinguish navigation from sending, purchasing or changing a setting. Autonomous `tap`/`swipe` stays deferred until a trusted host guard can identify the target action and require the person’s approval for actions that change external state; omitting `typeText` is insufficient. Until that guard exists, the person navigates the target app and the approved session only captures its screens. + - The ROM capture transport must also change before these sessions ship: full PNGs must travel through a file descriptor or another bounded transfer, rather than an unbounded Binder byte-array reply. The host checks dimensions and byte limits, copies the image into the project folder, and closes or cancels the transfer on timeout or session end. This is future ROM/client work, not a milestone 1 dependency. - On stock Android, a capture session through MediaProjection, with Android's own consent prompt, comes later. The Sketch kit, its gates included, stays a desktop tool and is not ported. @@ -87,7 +113,7 @@ It then: 2. reads the pixels back, one readback at a time (makepad's 32 MiB readback budget is shared by every pending readback), composes the premultiplied RGBA onto the surface's backdrop and writes an opaque PNG; 3. builds the widget-tree capture the checks read, in the capture format `card-studio` reads (section 5). -There is no remote instrument, no `card-host` process and no window grab. Studio renders never go through the glance store, so its publish rate and card limits do not apply. Script cards render under a no-side-effect policy: a `host.request` that would change something is refused during a studio render. +There is no remote instrument, no `card-host` process and no window grab. Studio renders never go through the glance store, so its publish rate and card limits do not apply. Every preview uses a no-side-effect policy established before source evaluation: a fresh disposable storage jail (or a bounded snapshot copied into it), no live app/account files, and disabled external network access. Bundle artwork is staged by the host through a bounded local asset path; service data comes from explicit preview fixtures. Mutating `host.request` calls are refused, but that gate alone is insufficient: Splash’s direct `fs.write`/`append`/`remove` and network APIs must remain confined or disabled too. Preview state is discarded on completion or cancellation. The target’s layout and lowering are reused; its live storage and service authority are not. ### 5. Checks and comparison are Rust, shared by phone and desktop @@ -118,7 +144,7 @@ The system toolbox's runner becomes the studio runner. - **What moves from the design flow to Octoscript** (rules and text, not pixels): - the role-first mapping policy (`semantics`, `core/policy`); - review records; - - the code generation of image-to-card (`compile`, `extract`, `register`), without its desktop assumptions: fonts read from a `splash-makepad` checkout, artwork fetched from a local server, and the single 406×776 artboard; + - the image-to-card code generation (`flows/image-lib/compile.py`, `flows/image-lib/register.py` and `flows/image-to-card/extract.py`), without its desktop assumptions: fonts read from a `splash-makepad` checkout, artwork fetched from a local server, and the single 406×776 artboard; - repair plans. - **What moves to Rust** (in `card-studio`): - pixel work, including `extract`'s crop and `core/policy`'s crop comparison; @@ -155,10 +181,10 @@ An octos change adds a media field to `peer/tool/result`, mapped onto octos's in ### 8. Developer mode for the studio -- **The developer build.** Home is built with `--features dev-mode`, through a new `--dev-mode` option of [`build-home.py`](../../rom/scripts/build-home.py). Its existing `--development` option only picks the signing keystore. On the ROM the developer build is platform-signed, because the agent service accepts only platform-signed OctoSense packages. +- **The developer build.** Home is built with `--features dev-mode`, through the `--dev-mode` option of [`build-home.py`](../../rom/scripts/build-home.py). Its existing `--development` option only picks the signing keystore; ordinary release builds keep developer options disabled. Standalone tests use a separate `--package-name`, such as `dev.makepad.octosense.studio`, without renaming or replacing the installed Bridge. Build receipts record both choices and ROM staging rejects developer-enabled receipts. On the ROM the developer build is platform-signed, because the agent service accepts only platform-signed OctoSense packages. - **The person's own home keeps its 8-hour limit.** The studio never marks a home that holds real accounts as a developer profile. A persistent developer profile on the phone needs a second home with test accounts, as ADR 0004 §13 intends; how the phone hosts one is open. - **One new grant.** Developer mode gives app agents the `studio.*` tools. -- **One exception to ADR 0004 §13.** A capture session's approval is a new approval kind that developer mode never answers by itself. The person answers it once per session, because the session sees other apps' screens, and the answer goes to the developer-mode audit log. +- **Person-only approvals amend ADR 0004 §13.** Developer mode never answers a capture-session approval by itself. The person answers once per session because the session sees other apps' screens. If the future trusted input guard permits an autonomous action that changes external state, its action-specific approval must also come from the person; the capture-session approval does not authorize that action. Both answers go to the developer-mode audit log. Until that guard and approval route exist, the person operates the target app. ### 9. Privacy, safety and other people's work @@ -169,7 +195,7 @@ An octos change adds a media field to `peer/tool/result`, mapped onto octos's in ### 10. Limits on the device -- **The screen must be on.** Makepad paints only while Home has a drawable surface, and without one a readback does not fail; it waits. The renderer watches for Home going to the background, cancels the readback and fails the render with `not_foreground`. Rendering in the background needs makepad changes, a surfaceless or pbuffer EGL context and its drawable-surface checks; that stays an open question. +- **Home must have a drawable surface.** Makepad paints only while Home has a drawable surface, and without one a readback does not fail; it waits. The renderer watches for Home going to the background, cancels the readback and fails the render with `not_foreground`. Rendering in the background needs makepad changes, a surfaceless or pbuffer EGL context and its drawable-surface checks; that stays an open question. - **Size and memory.** A glance tile reads back from under 1 MB to about 8 MB of RGBA, depending on its height and the screen's density. A full app screen reads back 10–18 MB, which reserves 19–25 MiB of the 32 MiB readback budget once rows are padded, so renders run one at a time. PNGs for `view_image` stay under 5 MiB and are scaled down if needed. - **Vulkan.** On Android, readback is implemented for GLES only. A Vulkan build of Home would need Vulkan readback first. @@ -201,7 +227,7 @@ An octos change adds a media field to `peer/tool/result`, mapped onto octos's in - The design flow's Python shrinks to the Sketch kit and desktop-only tools as the image-to-card parts move to Octoscript and Rust. - The shell gains a renderer that can show any card offscreen. It is a new attack surface for script cards, which is why studio renders run without side effects. - Rules and templates change on the phone without a build, under the toolbox's digest and budget rules. -- Developer mode gains the `studio.*` tools and one approval it never answers by itself. +- Developer mode gains the `studio.*` tools, with person-only approvals for capture sessions and any future guarded input that changes external state. - ADR 0002 is amended (its amendment of 2026-10-03): its §6 toolbox tools `card_render` and `card_critique_payload` become the `studio.*` host tools, and its §7 rule that the phone evaluates only while charging does not apply to renders the person starts. ## Open questions diff --git a/docs/architecture-walkthrough.md b/docs/architecture-walkthrough.md index 0e97e8e6f..6b6249c91 100644 --- a/docs/architecture-walkthrough.md +++ b/docs/architecture-walkthrough.md @@ -279,6 +279,20 @@ Read `mpsc` as a many-sender mailbox, `oneshot` as one correlated answer, and `w The broker's synchronous `bind`/`host_request` wrappers wait for channel replies: do not call such blocking interfaces from a rendering callback. Async model I/O can overlap across sessions; synchronous filesystem work still needs the existing worker boundary. One peer per app and account is an identity rule. The number of tasks and worker threads follows the runtimes and active operations above. +### A foreground render crosses three execution boundaries + +The first App Studio implementation adds `studio.render`, a shell-owned tool declared in [host_tools/studio.rs](../crates/shell/src/host_tools/studio.rs). The relay offers it only while developer mode covers the caller. It is not shareable, does not install a bundle, and does not add tools to a script manifest. A system call uses the workspace returned when its kernel session opens; an app call uses its broker-bound workspace, narrowed to `contexts//` for a human conversation. The caller supplies a relative `source_path`, optional `data_path`, and optional `dark` boolean. It cannot supply another app identity or an output directory. + +Follow one call from [the executor](../crates/shell/src/host_tools/studio.rs) to [the renderer](../crates/shell/src/studio/mod.rs): + +1. A host worker opens bounded source/data files through the workspace directory descriptor. Symlinks and `..` cannot escape that directory. It creates a unique output file there, queues a `RenderJob`, and waits for a correlated reply. +2. Makepad's UI loop checks the current developer grant, prepares the L0 card, and draws a separate offscreen pass. It uses the live glance lowering with an explicit light/dark argument, the window width minus 40 points, and measured height clamped to 72–440 points. It never publishes a tile or changes the shell's global theme. +3. The shell's shared readback router dispatches GPU results by ticket, preserving the existing test-capture route. After three matching pixel samples and ready shaders, a heavy worker flattens premultiplied pixels onto an opaque backdrop and encodes the PNG. The reply returns a workspace-relative path, pixel dimensions and `settled`; the agent can then call `view_image`. + +The preview isolate has no network, host capabilities or prompts. Its disposable storage directory has a zero-byte quota, its instruction budget is five million, and its heap limit is 16 MiB. Source/data limits match glance admission (16/32 KiB). Own-app digest references are resolved from host data; chat sources and image resources currently return explicit unsupported errors. Contained script-app screens are a later milestone. + +A render has a 20-second deadline including queueing and encoding. Backgrounding Home, cancellation or an expired developer grant prevents a successful reply, including while PNG encoding is in progress. A canceled GPU lease still has to drain before the platform releases its memory. The output is capped at 5 MiB; failed calls remove it. This is UI/GPU work followed by a worker-pool job—not a new agent peer or one Tokio task per render. Device evidence and the remaining model-image check belong to [ADR 0006](adr/0006-app-studio-on-the-phone.md). + ## 11. Tests and remaining implementation [Broker tests](../crates/app-peers/tests/broker.rs) provide executable protocol examples: `a_persons_message_runs_while_the_system_agents_input_runs`, `a_lane_stop_leaves_the_other_lane_running`, `a_kernel_without_shared_history_is_refused_for_the_conversation`, and `removing_an_account_purges_its_recorded_peer_and_drops_the_record`. [Shell relay scenario tests](../crates/shell/src/host_tools/scenario_tests.rs) cover caller/tool/approval boundaries. Start with these when changing a conversation or executor path. diff --git a/docs/architecture-walkthrough.zh-CN.md b/docs/architecture-walkthrough.zh-CN.md index 5344511c2..4a56367ca 100644 --- a/docs/architecture-walkthrough.zh-CN.md +++ b/docs/architecture-walkthrough.zh-CN.md @@ -273,6 +273,20 @@ flowchart LR Broker 同步 `bind` / `host_request` 会等待通道回复,不应在绘制回调中调用。不同 session 的异步模型 I/O 可重叠,同步文件工作仍走已有的工作线程边界。每应用/账号一个 peer 是身份规则;task 和线程数量由上表的 runtime 与活跃操作决定。 +### 前台渲染跨越三个执行边界 + +App Studio 的首个实现增加了 Shell 所有的 `studio.render`,声明位于 [host_tools/studio.rs](../crates/shell/src/host_tools/studio.rs)。只有开发者模式覆盖当前调用方时,relay 才提供此工具。它不可共享,不安装应用包,也不向脚本清单添加工具。系统调用使用内核打开 session 时返回的 workspace;应用调用使用 broker 绑定的 workspace,用户对话进一步限定到 `contexts//`。调用方只传相对 `source_path`、可选 `data_path` 和可选 `dark` 布尔值,不能指定别的应用身份或输出目录。 + +从[执行器](../crates/shell/src/host_tools/studio.rs)跟随一次调用进入[渲染器](../crates/shell/src/studio/mod.rs): + +1. 宿主工作线程通过 workspace 目录描述符打开大小受限的源文件与数据文件。符号链接和 `..` 不能越出目录。它在该目录创建唯一输出文件,将 `RenderJob` 入队,等待对应回复。 +2. Makepad UI 循环检查当前开发者授权,准备 L0 卡片,绘制独立的离屏 pass。它复用真实 glance lowering,显式传入明暗模式;宽度为窗口宽减 40 点,测得的高度限制在 72–440 点。它不会发布卡片,也不会改变 Shell 的全局主题。 +3. Shell 共享的读回路由按 ticket 分发 GPU 结果,保留已有测试截图的路线。着色器就绪且连续三次像素样本相同后,重型工作线程把预乘像素合成到不透明背景上,再编码 PNG。回复返回 workspace 相对路径、像素尺寸和 `settled`,Agent 随后可调用 `view_image`。 + +预览 isolate 没有网络、宿主能力或弹窗权限。临时存储目录配额为零,指令预算为五百万,堆上限为 16 MiB。源码/数据限制与 glance 准入一致,为 16/32 KiB。自身应用的 digest 引用由宿主数据解析;聊天来源和图像资源当前明确返回不支持。隔离脚本应用的完整屏幕属于后续里程碑。 + +渲染的 20 秒期限包含排队和编码。Home 退到后台、取消或开发者授权失效都会阻止成功回复,PNG 编码阶段也不例外。已取消的 GPU 租约仍需排空,平台才释放其内存。输出上限为 5 MiB,失败调用会删除输出。这是 UI/GPU 工作接上工作线程池任务,不是新 Agent peer,也不是每次渲染对应一个 Tokio task。设备证据及尚待验证的模型图像链路见 [ADR 0006](adr/0006-app-studio-on-the-phone.md)。 + ## 11. 测试与尚待实现的部分 [Broker 测试](../crates/app-peers/tests/broker.rs)提供可执行协议示例:`a_persons_message_runs_while_the_system_agents_input_runs`、`a_lane_stop_leaves_the_other_lane_running`、`a_kernel_without_shared_history_is_refused_for_the_conversation`、`removing_an_account_purges_its_recorded_peer_and_drops_the_record`。[Shell relay 场景测试](../crates/shell/src/host_tools/scenario_tests.rs)覆盖调用方、工具与审批边界;修改对话或执行路径时从这些测试开始。 diff --git a/phone/README.md b/phone/README.md index 2a3d5b546..d65718fdb 100644 --- a/phone/README.md +++ b/phone/README.md @@ -84,21 +84,25 @@ signer, kept outside the checkout. Use the pinned packager, not upstream's: it carries this app's Java activity ([docs/build-tool.md](docs/build-tool.md)). Signing, receipts and the ROM variant: [rom/docs/home-build.md](../rom/docs/home-build.md). -**Package name.** Home's application ID is `dev.makepad.octosense`, in both -the standalone and the ROM variant. A phone running the OctoSense ROM already -has that ID, signed with the platform key, so a development build cannot -replace it. To install a test build beside it, call the packager with another -package name, from `phone/`: - -```sh -../.sources/makepad/target/release/cargo-makepad makepad android \ - --sdk-path=/path/to/makepad-android \ - --package-name=dev.makepad.octosense.scriptapps \ - build -p octosense-home --release -``` - -`run` in place of `build` also installs and starts it; address it with its own -name, for example `adb shell am start -n dev.makepad.octosense.scriptapps/.MakepadApp`. +**Developer options and a test package.** `--development` selects signing +only. Add `--dev-mode` to compile Settings' developer options; this preserves +`--release` optimization and does not turn developer mode on. Normal builds +omit the feature. The build receipt records `development`, `dev_mode` and +`home_package`; ROM staging refuses developer-enabled artifacts. + +Home normally uses `dev.makepad.octosense`. To build a test Home beside the +installed app, add `--package-name dev.makepad.octosense.studio` to the +standalone command above. The default output becomes +`rom/out/home/standalone-dev-mode/dev.makepad.octosense.studio/` when both options +are used. Install only that test Home APK; the accompanying Bridge retains its +existing package ID and must not replace the device's Bridge. The separate +Home does not receive privileged ROM services without satisfying their existing +package/signature checks. + +The new build-plan and receipt paths are unit tested and dry-run checked. +APK and device validation remain separate: [App Studio milestone 1](../docs/adr/0006-app-studio-on-the-phone.md) +is in progress; the full phone authoring loop is not yet available. +The [studio device probe](../tools/studio-device-probe.py) checks denial, light/dark PNG output and background cancellation in that separate package. It needs a `--dev-mode` APK built with `MAKEPAD_FORCE_DEBUGGABLE=1` so `run-as` can provision test fixtures. The probe restores developer settings and returns to Home; it neither installs APKs nor tests model image delivery. Resolve startup permission prompts before running the probe; rendering does not need location access, which can remain denied. Device execution remains **unverified** until its evidence is recorded. **OpenHarmony:** `python3 rom/scripts/build-home-ohos.py --deveco-home ... --packager ... --signing-config ...` builds a normal OpenHarmony app with an diff --git a/phone/README.zh-CN.md b/phone/README.zh-CN.md index 71c251418..c6f21fc80 100644 --- a/phone/README.zh-CN.md +++ b/phone/README.zh-CN.md @@ -48,16 +48,12 @@ rom/scripts/build-home.sh --variant standalone --development \ 需要一个 `cargo-makepad` 的 Android SDK/NDK 目录(`cargo-makepad makepad android --sdk-path= install-toolchain`)、带 platform 35 和 build-tools 35.0.0 的 Android SDK、完整的 JDK 17+ 以及 Gradle 8.11.1;脚本不会安装其中任何一项。加上 `--dry-run` 可打印构建计划;发布版请把 `--development` 换成指向现有签名者的 `--sign-key` 和 `--sign-cert`,签名文件放在检出目录之外。请使用锁定版本的打包工具,而不是上游的:它带有本应用的 Java activity([docs/build-tool.md(英文)](docs/build-tool.md))。签名、回执和 ROM 变体见 [rom/docs/home-build.md(英文)](../rom/docs/home-build.md)。 -**包名。** Home 的应用 ID 是 `dev.makepad.octosense`,独立版和 ROM 版相同。运行 OctoSense ROM 的手机上已经有这个 ID,并由平台密钥签名,因此开发版无法替换它。要在旁边安装测试版,请在 `phone/` 中以另一个包名直接调用打包工具: +**开发者选项与测试包。** `--development` 只选择签名方式。另加 `--dev-mode` 才会编译设置中的开发者选项;仍使用 `--release` 优化,也不会自动开启开发者模式。默认构建不包含该 feature。构建回执记录 `development`、`dev_mode` 和 `home_package`,ROM 暂存会拒绝包含开发者模式的产物。 -```sh -../.sources/makepad/target/release/cargo-makepad makepad android \ - --sdk-path=/path/to/makepad-android \ - --package-name=dev.makepad.octosense.scriptapps \ - build -p octosense-home --release -``` +Home 默认使用 `dev.makepad.octosense`。要在已安装的应用旁构建测试 Home,请给上面的独立版命令加上 `--package-name dev.makepad.octosense.studio`。同时使用两个选项时,默认输出为 `rom/out/home/standalone-dev-mode/dev.makepad.octosense.studio/`。只安装测试 Home APK;配套 Bridge 保持原包名,不能替换设备上已安装的 Bridge。独立测试 Home 仍须满足现有包名与签名检查,才可访问 ROM 的特权服务。 -把 `build` 换成 `run` 会同时安装并启动它;用它自己的名字访问,例如 `adb shell am start -n dev.makepad.octosense.scriptapps/.MakepadApp`。 +新增构建计划和回执路径已通过单元测试与 dry-run 检查;APK 和设备验证另行进行。[App Studio 里程碑 1](../docs/adr/0006-app-studio-on-the-phone.md) 正在实现,完整的手机应用创作循环尚不可用。 +[Studio 设备探针](../tools/studio-device-probe.py) 在该独立测试包中检查权限拒绝、浅色/深色 PNG 输出和切入后台后的取消。它需要使用 `MAKEPAD_FORCE_DEBUGGABLE=1` 构建的 `--dev-mode` APK,让 `run-as` 能写入测试夹具。探针会恢复开发者设置并返回主屏;它不安装 APK,也不测试模型能否收到图片。运行前须先处理首次启动的权限提示;渲染不需要定位权限,可以保持拒绝。在记录设备证据前,这些设备检查仍属**未验证**。 **OpenHarmony:** `python3 rom/scripts/build-home-ohos.py --deveco-home ... --packager ... --signing-config ...` 使用现有的 DevEco 签名配置构建普通的 OpenHarmony 应用([rom/docs/home-build.md(英文)](../rom/docs/home-build.md#openharmony-home))。**iOS 模拟器:** 在 `phone/` 中运行 `../.sources/makepad/target/release/cargo-makepad makepad apple ios --org=dev.makepad --app=octosense run-sim -p octosense-home --features mobile-only`。两者都不在 CI 中构建。 diff --git a/rom/README.md b/rom/README.md index 83f36fcc1..374bf6928 100644 --- a/rom/README.md +++ b/rom/README.md @@ -66,6 +66,8 @@ python3 rom/scripts/stage-home.py # verify the receipt, copy the rom/scripts/stage-forks.sh /path/to/lineage-tree # apply vendor/octosense and stage the Quickstep and SystemUI forks ``` +**App Studio development builds.** Home’s build wrapper accepts `--dev-mode` independently of the `--development` signing option. Standalone tests can use `--package-name dev.makepad.octosense.studio`; only Home is renamed, so keep the installed Bridge. Developer-enabled receipts cannot be staged or published as ROM releases. See the [build contract](docs/home-build.md#android-builds) and [in-progress milestone](../docs/adr/0006-app-studio-on-the-phone.md). Build-plan tests do not establish device operation. + Then, on the host, `scripts/run-rom-rootfs.sh` enters the chroot (under `OCTOSENSE_BUILD_ROOT`, default `/home/ubuntu/octosense-adr0001`) and runs `build-rom.sh preflight`, `bacon` (the full signed build) or `module `. diff --git a/rom/README.zh-CN.md b/rom/README.zh-CN.md index 97d84a897..b884d68af 100644 --- a/rom/README.zh-CN.md +++ b/rom/README.zh-CN.md @@ -44,6 +44,8 @@ python3 rom/scripts/stage-home.py # verify the receipt, copy the rom/scripts/stage-forks.sh /path/to/lineage-tree # apply vendor/octosense and stage the Quickstep and SystemUI forks ``` +**App Studio 开发构建。** Home 构建脚本支持 `--dev-mode`,与只控制签名的 `--development` 独立。独立测试可使用 `--package-name dev.makepad.octosense.studio`;只改变 Home 包名,保留设备上的 Bridge。包含开发者模式的回执不能进入 ROM 暂存或发布。参见[构建约定](docs/home-build.md#android-builds)与[进行中的里程碑](../docs/adr/0006-app-studio-on-the-phone.md)。构建计划测试不代表设备运行已验证。 + 然后在主机上,`scripts/run-rom-rootfs.sh` 进入 chroot(位于 `OCTOSENSE_BUILD_ROOT` 下,默认 `/home/ubuntu/octosense-adr0001`),并运行 `build-rom.sh preflight`、`bacon`(完整签名构建)或 `module `。它要求 `build-rom.sh` 位于构建根目录下的 `exports/build-rom.sh`,并由 systemd 启动;该 unit 和主机配置不在本仓库中。`scripts/make-keys.sh` 一次性生成签名密钥;`scripts/release.sh ` 从 `~/.config/octosense/build.env` 中指定的主机取回构建完成的镜像。签名、构建回执和 ROM 版本:[docs/home-build.md(英文)](docs/home-build.md)。 刷写方式: diff --git a/rom/docs/home-build.md b/rom/docs/home-build.md index 3a22f1187..52a0d6461 100644 --- a/rom/docs/home-build.md +++ b/rom/docs/home-build.md @@ -121,8 +121,28 @@ Each APK build exports `OctoSenseHome.apk`, `OctoSenseBridge.apk` and `build.jso under `out/home/standalone/` or `out/home/rom/`. Home and Bridge are signed together and their certificate digests must match. The receipt records source revision/dirty state, runtime/patch/native-app inputs, APK hashes and certificate -digests. Existing application IDs, signature guards and platform imports are -unchanged. The build does not create or migrate signing keys. +digests. The default application IDs, signature guards and platform imports are +unchanged; standalone Home can use the test identity described below. The build does not create or migrate signing keys. + +**Developer options and a separate test Home.** `--development` selects only +the signing keystore. `--dev-mode` separately compiles the existing Home +`dev-mode` feature, enabling Settings to offer developer options; it does not +activate them or change release optimization. Default builds omit that feature. +Both values are recorded in `build.json`. Developer-enabled builds default to +`out/home/-dev-mode/`, and ROM staging/publication refuses them. + +For testing beside the installed Home, add +`--package-name dev.makepad.octosense.studio` to a standalone build. The wrapper +forwards this identity to the packager and verifies the resulting APK identity. +The default output then has an additional `dev.makepad.octosense.studio/` +subdirectory. Only Home is renamed: the Bridge APK keeps its existing ID and +must not replace the phone's installed Bridge. A test Home does not acquire +ROM services merely by changing its package name; their signature and package +guards remain in force. A ROM build cannot override its Home identity. + +These options have unit and dry-run coverage. An APK build and assigned-device +validation are separate checks; [ADR 0006](../../docs/adr/0006-app-studio-on-the-phone.md) +tracks the in-progress studio milestone and its remaining device acceptance. **The octos kernel.** Every APK carries Home's octos kernel as `lib/arm64-v8a/liboctos.so` (an Android app may exec only from its native diff --git a/rom/scripts/build-home.py b/rom/scripts/build-home.py index fdaea63c0..d83a143c7 100644 --- a/rom/scripts/build-home.py +++ b/rom/scripts/build-home.py @@ -24,6 +24,7 @@ # framework checkouts (.sources/, tools/setup.py) are at its root. REPO = ROOT.parent HOME = REPO / "phone" +HOME_PACKAGE = "dev.makepad.octosense" def _load_kernel_tool(): @@ -59,6 +60,8 @@ def arguments(argv=None): p.add_argument("--sign-key", type=Path, help="Existing signer PKCS#8 key; never generated by this command") p.add_argument("--sign-cert", type=Path, help="Existing signer X.509 certificate") p.add_argument("--development", action="store_true", help="Standalone only: sign both APKs with Makepad's development keystore") + p.add_argument("--dev-mode", action="store_true", help="Compile Home with developer options; independent of signing and disabled unless explicitly enabled") + p.add_argument("--package-name", default=HOME_PACKAGE, help="Standalone Home package ID for side-by-side testing; does not rename the Bridge APK") p.add_argument("--version-code", default="auto") p.add_argument("--output", type=Path) p.add_argument("--offline", action="store_true") @@ -73,13 +76,20 @@ def arguments(argv=None): p.error("--development is only for standalone builds without release keys") if not args.development and not args.sign_key: p.error("Supply the existing signer with --sign-key/--sign-cert, or use --development for standalone testing") + if not re.fullmatch(r"[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)+", args.package_name): + p.error("--package-name must be a dotted Android application ID") + if args.variant == "rom" and args.package_name != HOME_PACKAGE: + p.error("--package-name overrides are only for standalone Home test builds") if args.version_code != "auto" and (not args.version_code.isdigit() or not 0 < int(args.version_code) < 2100000000): p.error("--version-code must be auto or a positive Android version code") for name in ("sdk", "android_sdk", "gradle_home", "java_home", "packager", "sign_key", "sign_cert", "output", "octos_kernel"): if getattr(args, name): setattr(args, name, Path(getattr(args, name)).resolve()) args.java_home = args.java_home or args.sdk / "openjdk" - args.output = args.output or ROOT / "out/home" / args.variant + output = ROOT / "out/home" / (args.variant + ("-dev-mode" if args.dev_mode else "")) + if args.package_name != HOME_PACKAGE: + output /= args.package_name + args.output = args.output or output return args @@ -103,6 +113,10 @@ def build_plan(args): gradle = [str(HOME / "android/gradlew"), "--no-daemon", ":contracts:exportHomeContracts", ":system-bridge:assembleRelease"] packager = args.packager or sources / "makepad/target/release/cargo-makepad" android = [str(packager), "makepad", "android", f"--sdk-path={args.sdk}", "--abi=aarch64", f"--version-code={args.version_code}", "--no-sign", "build", "-p", "octosense-home", "--release", "--locked"] + if args.dev_mode: + android += ["--features", "dev-mode"] + if args.package_name != HOME_PACKAGE: + android.insert(android.index("build"), f"--package-name={args.package_name}") if args.offline: cargo.append("--offline") gradle.append("--offline") @@ -152,12 +166,21 @@ def certificate_digest(apksigner, apk, env): return match[1] +def home_version(badging, expected_package): + """Refuse an APK with the wrong identity before recording the build.""" + package = re.search(r"^package: name='([^']+)'.*versionCode='([0-9]+)'", badging, re.MULTILINE) + if not package or package[1] != expected_package: + raise RuntimeError(f"Home APK identity does not match requested package {expected_package}") + return int(package[2]) + + def main(argv=None): args = arguments(argv) plan = build_plan(args) kernel = kernel_plan(args)[1] if args.dry_run: print(json.dumps({"variant": args.variant, "development": args.development, + "dev_mode": args.dev_mode, "home_package": args.package_name, "output": str(args.output), "octos_kernel": str(kernel) if kernel else None, "android_env": {"MAKEPAD_ANDROID_EXTRA_LIBS": extra_libs(kernel)}, @@ -229,8 +252,9 @@ def main(argv=None): raise RuntimeError("Home and Bridge must have the same signing identity") aapt = args.android_sdk / "build-tools/35.0.0/aapt2" badging = subprocess.check_output([str(aapt), "dump", "badging", str(args.output / "OctoSenseHome.apk")], env=env, text=True) - version = re.search(r"versionCode='([0-9]+)'", badging).group(1) + version = home_version(badging, args.package_name) receipt = {"schema_version": 1, "variant": args.variant, "development": args.development, + "dev_mode": args.dev_mode, "home_package": args.package_name, "home_version_code": int(version), "source_revision": subprocess.check_output(["git", "-C", str(REPO), "rev-parse", "HEAD"], text=True).strip(), "source_dirty": bool(subprocess.check_output(["git", "-C", str(REPO), "status", "--porcelain"], text=True)), diff --git a/rom/scripts/stage-home.py b/rom/scripts/stage-home.py index ec66e1a57..8e88545ae 100644 --- a/rom/scripts/stage-home.py +++ b/rom/scripts/stage-home.py @@ -34,6 +34,10 @@ def verify(build): receipt = json.loads((build / "build.json").read_text()) if receipt.get("schema_version") != 1 or receipt.get("variant") != "rom" or receipt.get("development") is not False: raise ValueError("ROM staging requires a ROM build signed with the existing platform certificate") + if receipt.get("dev_mode", False) is not False: + raise ValueError("ROM staging refuses Home builds with developer mode compiled in") + if receipt.get("home_package", "dev.makepad.octosense") != "dev.makepad.octosense": + raise ValueError("ROM staging refuses a Home test package") certificates = set() for name in (HOME_APK, BRIDGE_APK): item = receipt["artifacts"][name] diff --git a/rom/tests/test_home_build.py b/rom/tests/test_home_build.py index a8e668f32..77901e295 100644 --- a/rom/tests/test_home_build.py +++ b/rom/tests/test_home_build.py @@ -54,6 +54,51 @@ def test_outputs_are_separate_and_builds_do_not_deploy(self): self.assertFalse(any("adb" in c or "fastboot" in c for c in commands)) self.assertFalse(any("OctoSense-mobile" in arg for c in commands for arg in c)) + def test_developer_options_are_separate_from_signing_and_keep_release_optimization(self): + for variant, signer in (("standalone", ["--development"]), + ("rom", ["--sign-key", "/keys/platform.pk8", "--sign-cert", "/keys/platform.x509.pem"])): + with self.subTest(variant=variant): + ordinary = self.args("--variant", variant, *signer) + developer = self.args("--variant", variant, *signer, "--dev-mode") + normal_plan, dev_plan = build.build_plan(ordinary), build.build_plan(developer) + self.assertNotIn("dev-mode", normal_plan[-1][1]) + self.assertEqual(dev_plan[:-1], normal_plan[:-1], "kernel, Bridge and packager are unchanged") + self.assertEqual(dev_plan[-1][1], normal_plan[-1][1] + ["--features", "dev-mode"]) + self.assertIn("--release", dev_plan[-1][1]) + self.assertNotEqual(ordinary.output, developer.output) + + def test_test_package_only_changes_home_and_is_recorded_in_dry_run(self): + package = "dev.makepad.octosense.studio" + argv = ["--variant", "standalone", "--development", "--dev-mode", "--package-name", package, + "--sdk", "/sdk with spaces", "--android-sdk", "/android", "--no-octos-kernel", "--dry-run"] + with contextlib.redirect_stdout(io.StringIO()) as output: + build.main(argv) + plan = json.loads(output.getvalue()) + self.assertTrue(plan["development"]) + self.assertTrue(plan["dev_mode"]) + self.assertEqual(plan["home_package"], package) + self.assertFalse(plan["installs_or_flashes"]) + self.assertTrue(plan["output"].endswith("standalone-dev-mode/" + package)) + commands = [step["argv"] for step in plan["steps"]] + self.assertIn("--package-name=" + package, commands[-1]) + self.assertLess(commands[-1].index("--package-name=" + package), commands[-1].index("build")) + self.assertFalse(any("--package-name=" + package in c for c in commands[:-1])) + + def test_bad_package_names_and_rom_overrides_are_refused(self): + for package in ("bare", "dev.test/escape", "dev.test..app", "dev.test.$value", "dev.1app"): + with self.subTest(package=package), contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit): + self.args("--variant", "standalone", "--development", "--package-name", package) + with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit): + self.args("--variant", "rom", "--sign-key", "/keys/platform.pk8", "--sign-cert", "/keys/platform.x509.pem", + "--package-name", "dev.makepad.octosense.studio") + + def test_receipt_requires_the_requested_apk_identity(self): + badging = "package: name='dev.makepad.octosense.studio' versionCode='42' versionName='1'\n" + self.assertEqual(build.home_version(badging, "dev.makepad.octosense.studio"), 42) + for text in (badging, "no package in this APK"): + with self.assertRaises(RuntimeError): + build.home_version(text, "dev.makepad.octosense") + def test_existing_packager_skips_tool_compilation(self): args = self.args("--variant", "standalone", "--development", "--packager", "/tools/cargo-makepad", "--no-octos-kernel") plan = build.build_plan(args) @@ -143,6 +188,18 @@ def test_rejects_standalone_on_the_rom_channel(self): with self.assertRaises(ValueError): stage.verify(self.directory) + def test_rom_channel_rejects_developer_options_even_with_platform_signing(self): + self.receipt["dev_mode"] = True + self.write_receipt() + with self.assertRaisesRegex(ValueError, "developer mode"): + stage.verify(self.directory) + + def test_rom_channel_rejects_a_home_test_identity(self): + self.receipt["home_package"] = "dev.makepad.octosense.studio" + self.write_receipt() + with self.assertRaisesRegex(ValueError, "test package"): + stage.verify(self.directory) + def test_rejects_an_apk_replaced_after_signing(self): self.write_receipt() (self.directory / "OctoSenseHome.apk").write_bytes(b"another APK") diff --git a/tools/studio-device-probe.py b/tools/studio-device-probe.py new file mode 100644 index 000000000..cae9aaab1 --- /dev/null +++ b/tools/studio-device-probe.py @@ -0,0 +1,370 @@ +#!/usr/bin/env python3 +"""Exercise the developer-only studio renderer in an already installed test APK. + +Requires a --dev-mode build with MAKEPAD_FORCE_DEBUGGABLE=1. Never installs, +flashes, clears app data, changes the default Home, or accesses another package. +The supplied device must be unlocked and app startup permission prompts resolved. +Rendering needs no location grant; location can stay denied. This probes the real executor and GPU; +it does not test model calls, image delivery to a model, or ROM capture. +""" +import argparse +import hashlib +import json +from pathlib import Path, PurePosixPath +import re +import shlex +import struct +import subprocess +import sys +import threading +import time +import uuid + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_PACKAGE = "dev.makepad.octosense.studio" +FIXTURE = ROOT / "apps/calendar/host-service/resources/agenda.card" +PNG_LIMIT = 5 * 1024 * 1024 + + +class ProbeError(RuntimeError): + pass + + +def test_package(value): + # Keep this utility incapable of selecting Home, Bridge, or an unrelated app. + if not re.fullmatch(r"dev\.makepad\.octosense\.studio(?:\.[A-Za-z][A-Za-z0-9_]*)*", value): + raise argparse.ArgumentTypeError("use dev.makepad.octosense.studio or a child test package") + return value + + +def png_metadata(data): + if len(data) < 33 or len(data) > PNG_LIMIT or data[:8] != b"\x89PNG\r\n\x1a\n": + raise ProbeError("renderer output is not a bounded PNG") + if data[12:16] != b"IHDR": + raise ProbeError("PNG has no leading IHDR") + width, height = struct.unpack(">II", data[16:24]) + if not width or not height: + raise ProbeError("PNG has empty dimensions") + return {"width": width, "height": height, "bytes": len(data), + "sha256": hashlib.sha256(data).hexdigest()} + + +def output_name(value): + if not isinstance(value, str) or not re.fullmatch(r"\.studio-[0-9a-f-]+\.png", value): + raise ProbeError("renderer returned an unexpected output path") + return value + + +def display_metrics(size_text, density_text): + """The probe targets a portrait phone window; respect adb display overrides.""" + def effective(text, pattern): + values = dict(re.findall(pattern, text)) + value = values.get("Override", values.get("Physical")) + if value is None: + raise ProbeError("wm did not report usable display metrics") + return value + size = effective(size_text, r"(Physical|Override) size:\s*(\d+x\d+)") + density = int(effective(density_text, r"(Physical|Override) density:\s*(\d+)")) + width, height = map(int, size.split("x")) + scale = density / 160.0 + if not 0.5 <= scale <= 4 or width > height or width / scale - 40 < 240: + raise ProbeError("probe requires a portrait phone display with at least 240 logical pixels of card width") + return {"screen_width_px": width, "screen_height_px": height, "density_dpi": density, + "layout_scale": scale, "expected_card_width_px": width - 40 * scale, + "min_card_height_px": 72 * scale, "max_card_height_px": 440 * scale, + "width_tolerance_px": 4, "assumption": "portrait full-window layout; no Makepad DPI override"} + + +def check_png_geometry(png, metrics): + if abs(png["width"] - metrics["expected_card_width_px"]) > metrics["width_tolerance_px"]: + raise ProbeError(f"PNG width {png['width']} differs from display-derived glance width " + f"{metrics['expected_card_width_px']} (tolerance 4px)") + if not metrics["min_card_height_px"] - 1 <= png["height"] <= metrics["max_card_height_px"] + 1: + raise ProbeError("PNG height is outside the 72..440 logical-pixel glance bounds at display density") + + +def runtime_profile(lines, package): + """Use the app mount namespace's canonical path, never adb's substitute.""" + matches = [re.search(r"studio-test-profile: home=(\S+) canonical=(\S+) build=(\S+)", line) + for line in lines if "studio-test-profile:" in line] + if len(matches) != 1 or matches[0] is None: + raise ProbeError("expected one complete studio-test-profile diagnostic") + home, canonical, build = matches[0].groups() + allowed = {f"/data/data/{package}/files/.octosense", f"/data/user/0/{package}/files/.octosense"} + if home not in allowed or canonical not in allowed: + raise ProbeError("runtime profile path is outside this test package") + if build != "Development": + raise ProbeError("studio probe requires a Development build (compile with --dev-mode)") + return {"home": home, "canonical": canonical, "build": build} + + +class Device: + def __init__(self, adb, serial, package): + self.prefix = [adb, "-s", serial] + self.package = package + + def call(self, *args, data=None, check=True, timeout=15): + result = subprocess.run(self.prefix + list(args), input=data, capture_output=True, timeout=timeout) + if check and result.returncode: + raise ProbeError(f"adb {args[0]} failed: {result.stderr.decode(errors='replace').strip()}") + return result + + def shell(self, *args, **kwargs): + return self.call("shell", shlex.join(args), **kwargs) + + def private(self, script, **kwargs): + # Shell v2 preserves stdin EOF, binary stdout, stderr, and remote exit + # status. exec-out loses the status and can leave stdin writers waiting. + return self.call("shell", "-T", shlex.join(["run-as", self.package, "sh", "-c", script]), **kwargs) + + def read(self, path, limit=8192): + quoted = shlex.quote(path) + result = self.private(f"if [ ! -e {quoted} ]; then exit 44; fi; head -c {limit + 1} {quoted}", check=False) + if result.returncode == 44: + return None + if result.returncode: + raise ProbeError("private read failed: " + result.stderr.decode(errors="replace").strip()) + if len(result.stdout) > limit: + raise ProbeError(f"private result exceeds {limit} bytes") + return result.stdout + + def write(self, path, data): + self.private(f"umask 077; cat > {shlex.quote(path)}", data=data) + + def stop(self): + self.shell("am", "force-stop", self.package) + + def launch(self, spec): + config = json.dumps({"test_actions": [f"studio-render:{spec}"]}, separators=(",", ":")) + result = self.shell("am", "start", "-n", f"{self.package}/.MakepadApp", + "--es", "makepad.APP_CONFIG", config) + if b"Error:" in result.stdout or b"Error:" in result.stderr: + raise ProbeError(result.stdout.decode(errors="replace")) + deadline = time.monotonic() + 10 + while time.monotonic() < deadline: + result = self.shell("pidof", self.package, check=False) + pids = result.stdout.decode().strip().split() + if len(pids) == 1 and pids[0].isdigit(): + return pids[0] + time.sleep(0.05) + raise ProbeError("test activity did not start") + + +class AppLog: + """Stream this process's logs; never clear the device-wide log buffer.""" + def __init__(self, device, pid, background): + self.lines = [] + self.background_sent = False + self.background_error = None + self.denied = threading.Event() + self.process = subprocess.Popen(device.prefix + ["logcat", "--pid=" + pid, "-v", "raw"], + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) + def collect(): + for line in self.process.stdout: + # Store only probe diagnostics, not unrelated app/provider logs. + if "studio-test-" in line or "studio-render-geometry:" in line or "studio test:" in line: + self.lines.append(line.rstrip()) + if "studio test action requires developer mode" in line: + self.denied.set() + if background and not self.background_sent and "studio-test-started:" in line: + self.background_sent = True + try: + device.shell("input", "keyevent", "KEYCODE_HOME") + except Exception as error: + self.background_error = str(error) + self.thread = threading.Thread(target=collect, daemon=True) + self.thread.start() + + def close(self): + self.process.terminate() + try: + self.process.wait(timeout=3) + except subprocess.TimeoutExpired: + self.process.kill() + self.process.wait() + self.thread.join(timeout=16) + self.process.stdout.close() + + +def run_case(device, root, name, destination, timeout, metrics): + directory = f"{root}/{name}" + device.stop() + device.private(f"mkdir -m 700 -p {shlex.quote(directory)}") + device.write(directory + "/card.l0", FIXTURE.read_bytes()) + data = {"day": {"title": "Studio device probe", "as_of": "FIXTURE", + "pick1_title": "1. Top row", "pick1_body": "09:00", + "pick2_title": "2. Middle row", "pick2_body": "12:00", + "pick3_title": "3. Bottom row", "pick3_body": "17:00", + "summary": "End of fixture — no account data"}} + device.write(directory + "/data.json", json.dumps(data).encode()) + spec = {"workspace": directory, "source_path": "card.l0", "data_path": "data.json", "dark": name == "dark"} + device.write(directory + "/spec.json", json.dumps(spec).encode()) + pid = device.launch(directory + "/spec.json") + logs = AppLog(device, pid, name == "background") + record = {"case": name, "status": "failed", "pid": pid, "workspace": directory} + try: + deadline = time.monotonic() + timeout + result = None + while time.monotonic() < deadline: + raw = device.read(directory + "/studio-result.json") + if raw: + try: + result = json.loads(raw) + break + except json.JSONDecodeError: + pass # Writer may be between write and fsync. + if name == "denied" and logs.denied.is_set(): + break + time.sleep(0.05) + if name == "denied": + if not logs.denied.is_set() or device.read(directory + "/studio-result.json") is not None: + raise ProbeError("expected developer-mode denial without a result file") + record.update(status="passed", denial="developer mode required") + else: + if not isinstance(result, dict): + raise ProbeError("no complete studio-result.json before timeout") + record["reply"] = result + if name == "background": + record["home_sent_after_submit"] = logs.background_sent + if logs.background_error: + raise ProbeError(logs.background_error) + if result.get("ok") is True: + record["status"] = "inconclusive" + record["reason"] = "render completed before background cancellation; rerun required" + else: + error = result.get("error", {}) + if not logs.background_sent or error.get("kind") != "studio_render_failed" or not any( + word in error.get("message", "") for word in ("not_foreground", "studio_cancelled", "cancelled") + ): + raise ProbeError("expected cancellation after renderer submission") + record["status"] = "passed" + else: + if result.get("ok") is not True: + raise ProbeError(f"renderer failed: {result.get('error')}") + rendered = result.get("data", {}) + if rendered.get("settled") is not True: + raise ProbeError("renderer did not settle") + png = device.read(directory + "/" + output_name(rendered.get("path")), PNG_LIMIT) + if png is None: + raise ProbeError("renderer PNG is missing") + metadata = png_metadata(png) + if (metadata["width"], metadata["height"]) != (rendered.get("width"), rendered.get("height")): + raise ProbeError("reply dimensions differ from PNG header") + (destination / f"{name}.png").write_bytes(png) + record.update(png=metadata, expected_geometry=metrics) + check_png_geometry(metadata, metrics) + record["status"] = "passed" + if name in ("denied", "background") and record["status"] == "passed": + files = device.private(f"find {shlex.quote(directory)} -name '*.png' -print").stdout + if files.strip(): + raise ProbeError("denied/cancelled render left a PNG behind") + except Exception as error: + record.update(status="failed", reason=str(error)) + finally: + logs.close() + try: + record["profile"] = runtime_profile(logs.lines, device.package) + except ProbeError as error: + record.update(status="failed", reason=str(error)) + (destination / f"{name}.log").write_text("\n".join(logs.lines) + "\n") + (destination / f"{name}.json").write_text(json.dumps(record, indent=2) + "\n") + return record + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--serial", required=True, help="explicit adb serial of the authorized unlocked test device") + parser.add_argument("--package", type=test_package, default=DEFAULT_PACKAGE) + parser.add_argument("--adb", default="adb") + parser.add_argument("--output", type=Path, required=True, help="new local evidence directory") + parser.add_argument("--timeout", type=float, default=35, help="seconds per case (minimum 30)") + parser.add_argument("--wake", action="store_true", help="wake screen and dismiss a noncredential keyguard; never unlocks credentials") + parser.add_argument("--dry-run", action="store_true", help="print plan without contacting adb") + args = parser.parse_args() + if args.timeout < 30: + parser.error("--timeout must be at least 30 seconds") + plan = {"serial": args.serial, "package": args.package, "cases": ["denied", "light", "dark", "background"], + "fixture": str(FIXTURE.relative_to(ROOT)), "output": str(args.output), + "installs_or_flashes": False, "model_image_delivery": "not tested"} + if args.dry_run: + print(json.dumps(plan, indent=2)) + return 0 + args.output.mkdir(parents=True, exist_ok=False) + evidence = {**plan, "started_unix": int(time.time()), "cases": [], "status": "failed"} + device = Device(args.adb, args.serial, args.package) + saved = {} + modified = False + try: + if device.call("get-state").stdout.strip() != b"device": + raise ProbeError("selected device is unavailable") + # Verify run-as before changing anything. Optimized APKs need the explicit + # MAKEPAD_FORCE_DEBUGGABLE packaging opt-in, independent of dev-mode. + app_root = device.private("pwd").stdout.decode().strip() + if not PurePosixPath(app_root).is_absolute(): + raise ProbeError("run-as did not return an absolute app directory") + device.stop() + device.private("umask 077; mkdir -p files/.octosense files/studio-fixture") + home = device.private("cd files/.octosense && pwd -P").stdout.decode().strip() + for name in ("developer-profile", "dev-mode.json"): + path = f"{home}/{name}" + saved[path] = device.read(path, 64 * 1024) + modified = True + device.private("rm -f " + shlex.join(list(saved))) + evidence["device"] = {key: device.shell("getprop", prop).stdout.decode().strip() for key, prop in ( + ("model", "ro.product.model"), ("fingerprint", "ro.build.fingerprint"))} + permissions = device.shell("dumpsys", "package", args.package).stdout.decode(errors="replace") + evidence["startup_location_permissions"] = [line.strip() for line in permissions.splitlines() + if re.search(r"android\.permission\.ACCESS_(?:FINE|COARSE)_LOCATION: granted=", line)] + if args.wake: + device.shell("input", "keyevent", "KEYCODE_WAKEUP") + device.shell("wm", "dismiss-keyguard", check=False) + metrics = display_metrics(device.shell("wm", "size").stdout.decode(), + device.shell("wm", "density").stdout.decode()) + evidence["display"] = metrics + evidence["visual_inspection"] = "required: verify readable fixture text, top-to-bottom order, and light/dark appearance" + root = app_root + "/files/studio-fixture/" + uuid.uuid4().hex + evidence["fixture_workspace"] = root + evidence["fixture_sha256"] = hashlib.sha256(FIXTURE.read_bytes()).hexdigest() + denied = run_case(device, root, "denied", args.output, args.timeout, metrics) + evidence["cases"].append(denied) + if denied["status"] != "passed": + raise ProbeError("pre-grant denial/profile check failed; developer mode was not provisioned") + profile_id = denied["profile"]["canonical"] + evidence["runtime_profile"] = denied["profile"] + device.stop() + device.write(home + "/developer-profile", b"studio-device-probe\n") + device.write(home + "/dev-mode.json", json.dumps({"scope": "all", "origin": "settings", + "since": int(time.time()), "profile_id": profile_id}).encode()) + for name in ("light", "dark", "background"): + evidence["cases"].append(run_case(device, root, name, args.output, args.timeout, metrics)) + cases = evidence["cases"] + if all(case["status"] == "passed" for case in cases): + if cases[1]["png"]["sha256"] == cases[2]["png"]["sha256"]: + raise ProbeError("light and dark renders are identical") + evidence["status"] = "passed" + except Exception as error: + evidence["error"] = str(error) + finally: + if modified: + try: + device.stop() + for path, data in saved.items(): + if data is None: + device.private("rm -f " + shlex.quote(path)) + else: + device.write(path, data) + evidence["developer_state_restored"] = True + except Exception as error: + evidence.update(status="failed", restore_error=str(error)) + try: + device.shell("input", "keyevent", "KEYCODE_HOME") + except Exception as error: + evidence.update(status="failed", home_error=str(error)) + evidence["finished_unix"] = int(time.time()) + (args.output / "evidence.json").write_text(json.dumps(evidence, indent=2) + "\n") + print(json.dumps({"status": evidence["status"], "evidence": str(args.output / "evidence.json")}, indent=2)) + return 0 if evidence["status"] == "passed" else 1 + + +if __name__ == "__main__": + sys.exit(main()) From d47ae422bd41d531a507bd78ae4eb97abc54a400 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Fri, 2 Oct 2026 21:13:33 -0700 Subject: [PATCH 02/15] feat(studio): run and inspect model-authored apps on device --- AGENTS.md | 2 +- README.md | 4 +- README.zh-CN.md | 4 +- crates/shell/src/apps.rs | 49 +- crates/shell/src/clients.rs | 4 +- crates/shell/src/host_tools/mod.rs | 4 +- crates/shell/src/host_tools/relay.rs | 10 +- crates/shell/src/host_tools/studio.rs | 310 ++++- crates/shell/src/host_tools/studio_bundles.rs | 825 +++++++++++++ crates/shell/src/host_tools/tests.rs | 32 + crates/shell/src/lib.rs | 52 + crates/shell/src/studio/apps.rs | 1086 +++++++++++++++++ crates/shell/src/studio/mod.rs | 4 + crates/shell/src/studio/module.rs | 26 + crates/shell/src/system_chat/session.rs | 2 +- docs/adr/0006-app-studio-on-the-phone.md | 80 +- docs/architecture-walkthrough.md | 39 +- docs/architecture-walkthrough.zh-CN.md | 39 +- docs/studio/oneplus6-validation.md | 42 + docs/studio/task-planner-brief.md | 156 +++ tools/studio-flow-device-test.py | 480 ++++++++ .../studio-flow/task-planner-acceptance.json | 70 ++ tools/studio-model-evidence.py | 205 ++++ 23 files changed, 3454 insertions(+), 71 deletions(-) create mode 100644 crates/shell/src/host_tools/studio_bundles.rs create mode 100644 crates/shell/src/studio/apps.rs create mode 100644 crates/shell/src/studio/module.rs create mode 100644 docs/studio/oneplus6-validation.md create mode 100644 docs/studio/task-planner-brief.md create mode 100644 tools/studio-flow-device-test.py create mode 100644 tools/studio-flow/task-planner-acceptance.json create mode 100644 tools/studio-model-evidence.py diff --git a/AGENTS.md b/AGENTS.md index 2761a0ec8..a7108b4ae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ | Which apps have an agent, their tools, and the relay that checks and routes every agent tool call | `crates/shell/src/apps.rs` (`agent_apps`), `crates/shell/src/host_tools/` (`script_apps.rs`, `relay.rs`, `files.rs`) | from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell host_tools`; `phone.yml` | | Approvals, first-use consent, standing rules, the audit | `crates/shell/src/approvals/` | from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell approvals`; `phone.yml` | | Glance cards (`glance.publish`), the desktop's glance panel and card window, a card's in-card chat | `crates/shell/src/glance*.rs`; the shared `sys.chat` host side in `crates/l0-chat/` | `cargo test --locked -p octosense-l0-chat`; from `phone/`: `cargo test --locked --features mobile-apps -p octosense-shell glance`; `apps.yml`, `phone.yml` | -| App Studio's developer-only L0 preview (`studio.render`) | `crates/shell/src/studio/` (UI/GPU), `crates/shell/src/host_tools/studio.rs` (workspace and executor), `docs/adr/0006-app-studio-on-the-phone.md` | from `phone/`: `cargo test --locked --features mobile-apps,dev-mode -p octosense-shell`; separate-package device checks are required for GPU acceptance | +| App Studio: L0 preview, scoped app instrumentation and local developer installs | `crates/shell/src/studio/` (UI/GPU/instrument/module), `crates/shell/src/host_tools/studio.rs` (tools/workspaces), `studio_bundles.rs` beside it (admission/DevTag receipts), `docs/adr/0006-app-studio-on-the-phone.md` | from `phone/`: `cargo test --locked --features mobile-apps,dev-mode -p octosense-shell`; separate-package device checks must cover a freshly authored app, real widget input, measured UX, installed persistence and revocation; unit/GPU fixture tests alone are insufficient | | The system toolbox's workflow templates (library, runner, forks, evaluation, `mod.research`) | `crates/toolbox/` (templates in `crates/toolbox/templates//`); offered to app agents by `crates/ai-host/src/toolbox_peers.rs` (feature `toolbox-peers`) | `cargo test --locked -p octosense-toolbox`, its README, `apps.yml` | | A system app | `apps//bundle/` | App Hub's `card-host --bundle apps//bundle --system`; then in a shell | | A system app's agent: its tools and the cards they publish | `apps//bundle/tools.json` and the manifest's `agent` block; the tools run on the app's host service | its host service's tests (below); then in a shell, with the system agent asking the app's agent | diff --git a/README.md b/README.md index 843d3272d..7c2a19475 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,9 @@ It was OctoSense-Desktop; OctoSense-ROM (retired; merged into this repository) a New to the codebase? Start with [From an app window to an agent turn](docs/architecture-walkthrough.md). It follows executable entry points, native and script hosting, app data access, human/system conversations, tool routing and the actual Tokio tasks. The [product walkthrough](desktop/docs/code-walkthrough.md) adds desktop, Home, ROM and system-app run recipes. -[App Studio on the phone](docs/adr/0006-app-studio-on-the-phone.md) is an accepted plan with milestone 1 in progress. Home can now be packaged with explicit developer options and a separate test identity ([phone build options](phone/README.md#build-and-run)); the complete render/check/refine/install loop remains under development. Device acceptance is recorded separately from unit and build checks. +[App Studio on the phone](docs/adr/0006-app-studio-on-the-phone.md) now has developer-only tools to check, open, inspect, exercise and locally install an offline `main.splash` app. Preview state is disposable; installed app state persists under its developer grant. Follow the [Studio code walkthrough](docs/architecture-walkthrough.md#follow-app-studio-from-the-agent-to-a-working-app). + +A fresh model-authored Task Planner passed 129 physical-device tool calls on OnePlus 6, including interaction and installed-state persistence. [Portrait app and keyboard visual review](docs/studio/oneplus6-validation.md) passed. Fault injection remains pending; image generation, the editable toolbox flow and public publishing remain future work. Home's [build options](phone/README.md#build-and-run) provide developer mode and a separate test package. ## How it fits together diff --git a/README.zh-CN.md b/README.zh-CN.md index 5813bdb8b..82f4bbea6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -18,7 +18,9 @@ 初次阅读源码?从[从应用窗口到 Agent 回合](docs/architecture-walkthrough.zh-CN.md)开始,沿着入口、原生/脚本托管、数据访问、人类/系统对话、工具路由以及实际 Tokio 任务阅读源码。[产品导读](desktop/docs/code-walkthrough.zh-CN.md)补充桌面、Home、ROM 和系统应用的运行方式。 -[手机上的 App Studio](docs/adr/0006-app-studio-on-the-phone.md) 已通过设计决策,里程碑 1 正在实现。Home 现可显式编译开发者选项并使用独立测试包名([手机构建选项](phone/README.zh-CN.md#构建与运行));完整的渲染、检查、改进与安装循环仍在开发中。设备验收与单元测试、构建检查分别记录。 +[手机上的 App Studio](docs/adr/0006-app-studio-on-the-phone.md) 现已提供开发者工具,可检查、打开、观测、操作并本地安装离线 `main.splash` 应用。预览状态可丢弃;已安装应用的状态在开发者授权范围内持久保留。参见 [Studio 代码导读](docs/architecture-walkthrough.zh-CN.md#从-agent-调用跟到-app-studio-中可运行的应用)。 + +模型从头编写的 Task Planner 已在 OnePlus 6 上通过 129 次真机工具调用,覆盖交互和已安装状态持久化。[竖屏应用与键盘视觉评审](docs/studio/oneplus6-validation.md)已通过。故障注入仍待完成;图像生成、可编辑工具箱流程与公开发布仍是后续工作。Home 的[构建选项](phone/README.zh-CN.md#构建与运行)提供开发者模式和独立测试包。 ## 整体如何运作 diff --git a/crates/shell/src/apps.rs b/crates/shell/src/apps.rs index 21957c97d..aef9966c5 100644 --- a/crates/shell/src/apps.rs +++ b/crates/shell/src/apps.rs @@ -45,7 +45,7 @@ impl Default for AppRegistry { /// A linked module by id, without a registry: what the launcher asks. pub fn is_linked(id: &str) -> bool { - linked_modules().iter().any(|m| m.id() == id) || is_card_app(id) + linked_modules().iter().any(|m| m.id() == id) || is_card_app(id) || is_studio_app(id) } /// The native apps this build links (`native-apps.json`, generated into @@ -60,6 +60,8 @@ fn linked_modules() -> Vec<&'static dyn AppModule> { let mut out: Vec<&'static dyn AppModule> = Vec::new(); crate::native_apps::link(&mut out); out.extend(crate::ext::linked_modules()); + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + out.push(&crate::studio::module::STUDIO_MODULE); out } @@ -396,6 +398,23 @@ fn cached_installed_apps(key: (std::path::PathBuf, u64), load: impl FnOnce() -> }) } +/// Local developer installs are a separate registry, never catalog packages. +pub fn studio_apps() -> Vec { + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + { return crate::host_tools::studio_bundles::installed_apps().into_iter() + .map(|bundle| studio_row(bundle.id.clone(), bundle.name.clone(), Vec::new())).collect(); } + #[cfg(not(all(unix, any(feature = "app-hub", native_mobile))))] + { Vec::new() } +} +fn is_studio_app(id: &str) -> bool { + id.starts_with("dev.studio.") && studio_apps().iter().any(|app| app.id == id) +} +#[cfg(all(unix, any(feature = "app-hub", native_mobile)))] +pub fn studio_row(id: String, label: String, args: Vec) -> crate::clients::AppDef { + crate::clients::AppDef { id, label, bin: "studio-app".into(), package: String::new(), + dir: String::new(), manifest: None, args, policy: crate::clients::LaunchPolicy::OrFocus, target_dir: None } +} + /// Every launcher row the Card runner opens: system apps, then installed ones. pub fn card_apps() -> Vec { let mut apps = system_card_apps(); @@ -416,7 +435,7 @@ fn is_card_app(id: &str) -> bool { /// Launch-or-focus: a Card app focuses only an instance of that same app, /// never a built-in (or another installed app) whose name it shares. pub fn matches_running_app(app: &crate::clients::AppDef, running_id: &str, title: &str) -> bool { - if app.bin == "card" || running_id.starts_with("hub:") { + if matches!(app.bin.as_str(), "card" | "studio-app") || running_id.starts_with("hub:") || running_id.starts_with("dev.studio.") { running_id == app.id } else { crate::clients::word_match(running_id, &app.id) || crate::clients::word_match(title, &app.id) @@ -434,6 +453,10 @@ pub fn module_open(module: &'static dyn AppModule, app: &crate::clients::AppDef) let manifest_id = card_manifest_id(app).ok_or_else(|| format!("{} names no app for the card runner", app.id))?; return schema.validate(&format!("{{\"app\":{}}}", makepad_strict_json::Value::Str(manifest_id.into()).to_json()), &[]); } + if module.id() == "studio-app" { + let instance = app.args.iter().find_map(|a| a.strip_prefix("--studio-instance=")).ok_or("Studio launch requires an admitted instance")?; + return schema.validate(&format!("{{\"instance_id\":{}}}", makepad_strict_json::Value::Str(instance.into()).to_json()), &[]); + } let config = std::env::var("MAKEPAD_APP_CONFIG").ok().and_then(|text| makepad_strict_json::parse(text.as_bytes()).ok()); if let Some(json) = config.as_ref().and_then(|c| c.get("module_open")).and_then(|v| v.get(module.id())).map(|v| v.to_json()) { return schema.validate(&json, &[]); @@ -452,6 +475,7 @@ pub fn module_open(module: &'static dyn AppModule, app: &crate::clients::AppDef) pub fn bundled_catalog() -> Vec { let mut catalog = bundled_modules_catalog(); catalog.extend(card_apps()); + catalog.extend(studio_apps()); catalog.retain(|app| catalog_visible(&app.id)); catalog } @@ -459,7 +483,7 @@ pub fn bundled_catalog() -> Vec { /// Whether an id may be a launcher row: the `card` host is internal (the /// apps it runs are the rows), and the retired empty `appstore` stays out. pub fn catalog_visible(id: &str) -> bool { - !matches!(id, "card" | "appstore") + !matches!(id, "card" | "appstore" | "studio-app") } /// Registry rows a shell surface launches but no list shows as an app: @@ -587,6 +611,9 @@ impl AppRegistry { if let Some(module) = self.modules.iter().copied().find(|m| m.id() == id) { return Some(module); } + if is_studio_app(id) { + return self.modules.iter().copied().find(|m| m.id() == "studio-app"); + } if is_card_app(id) { return self.modules.iter().copied().find(|m| m.id() == "card"); } @@ -603,6 +630,7 @@ impl AppRegistry { if !crate::host::processes_available() { return if self.module(id).is_some() { Hosting::Module } else { Hosting::Process }; } + if is_studio_app(id) && self.module(id).is_some() { return Hosting::Module; } // Neither the store nor Settings has a process form. if matches!(id, "apphub" | "settings") && self.module(id).is_some() { return Hosting::Module; @@ -817,6 +845,21 @@ mod tests { assert!(!matches_running_app(&system, "mailer", "Mail"), "a Card app focuses only itself"); } + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + #[test] + fn developer_apps_require_admitted_instances_and_exact_launch_identity() { + let module: &'static dyn AppModule = &crate::studio::module::STUDIO_MODULE; + let mut app = studio_row("dev.studio.planner".into(), "Planner".into(), Vec::new()); + assert!(!catalog_visible(module.id())); + assert!(module_open(module, &app).is_err()); + app.args.push("--studio-instance=host-issued-id".into()); + assert_eq!(module_open(module, &app).unwrap().text("instance_id"), Some("host-issued-id")); + assert!(matches_running_app(&app, "dev.studio.planner", "Any title")); + assert!(!matches_running_app(&app, "planner", "Planner")); + assert!(!matches_running_app(&app, "dev.studio.planner-copy", "Planner")); + assert!(!matches_running_app(&app, "studio-instance:preview", "Planner")); + } + /// The system apps this build packs (system-apps.json): each a Card app /// under its short id unless a native module of that id is linked, and /// each bundle registered with the runner. diff --git a/crates/shell/src/clients.rs b/crates/shell/src/clients.rs index f0b6139a5..bddb26872 100644 --- a/crates/shell/src/clients.rs +++ b/crates/shell/src/clients.rs @@ -134,11 +134,13 @@ fn manifest_value(manifest: &str, key: &str) -> Option { /// apps and installed apps, read fresh so an install needs no restart). pub fn registry() -> Vec { let base = crate::octosense::catalog::loaded().as_ref().cloned().unwrap_or_default(); + let mut installed = crate::apps::installed_card_apps(); + installed.extend(crate::apps::studio_apps()); merge_catalog( base, crate::apps::bundled_modules_catalog(), crate::apps::system_card_apps(), - crate::apps::installed_card_apps(), + installed, ) } diff --git a/crates/shell/src/host_tools/mod.rs b/crates/shell/src/host_tools/mod.rs index dc3237fd4..223b69e7d 100644 --- a/crates/shell/src/host_tools/mod.rs +++ b/crates/shell/src/host_tools/mod.rs @@ -38,6 +38,8 @@ pub mod files; pub mod relay; pub mod schema; pub mod studio; +#[cfg(all(unix, any(feature = "app-hub", native_mobile)))] +pub mod studio_bundles; #[cfg(feature = "toolbox-peers")] pub mod toolbox; #[cfg(any(feature = "app-hub", native_mobile))] @@ -389,7 +391,7 @@ impl HostExecutor { impl ToolExecutor for HostExecutor { fn execute(&self, call: HostToolCall, reply: ToolReply) { - if call.name == studio::RENDER { + if studio::is_tool(&call.name) { return self.studio.execute(call, reply); } if call.name == relay::DEV_RUN { diff --git a/crates/shell/src/host_tools/relay.rs b/crates/shell/src/host_tools/relay.rs index 3121fb40a..55ea4df27 100644 --- a/crates/shell/src/host_tools/relay.rs +++ b/crates/shell/src/host_tools/relay.rs @@ -74,7 +74,7 @@ pub const HOST_EXECUTOR: &str = "@shell"; /// Whether `tool` is one the shell runs itself, as the calling app's own /// tool, whatever its declaration names (never routed to the app). pub fn is_host_run(tool: &str) -> bool { - tool == DEV_RUN || tool == super::studio::RENDER || super::files::TOOLS.contains(&tool) + tool == DEV_RUN || super::studio::is_tool(tool) || super::files::TOOLS.contains(&tool) } /// A tool the shell runs, as `owner` declares it (its schemas). @@ -82,8 +82,8 @@ fn host_run_declaration(owner: &str, tool: &str) -> Option { if tool == DEV_RUN { return Some(super::dev_run::declaration(owner)); } - if tool == super::studio::RENDER { - return Some(super::studio::declaration(owner)); + if super::studio::is_tool(tool) { + return super::studio::declarations(owner).into_iter().find(|d| d["name"] == tool); } super::files::declarations(owner).into_iter().find(|d| d["name"] == tool) } @@ -384,7 +384,7 @@ impl Catalog { out.push(super::dev_run::declaration(app)); } if dev_all && super::studio::SUPPORTED { - out.push(super::studio::declaration(app)); + out.extend(super::studio::declarations(app)); } out } @@ -646,7 +646,7 @@ impl Relay { // 1. Authorize by (owning app, tool) and caller. let host_run = is_host_run(&tool); let (caller, granted) = match call.caller_kind { - _ if tool == super::studio::RENDER => ( + _ if super::studio::is_tool(&tool) => ( if call.caller_kind == CallerKind::System { Caller::SystemAgent } else { Caller::OwnAgent { client: call.client.clone() } }, super::studio::SUPPORTED && calling == owner && env.grants_all(&calling) && (call.caller_kind == CallerKind::AppPeer || calling == SYSTEM), diff --git a/crates/shell/src/host_tools/studio.rs b/crates/shell/src/host_tools/studio.rs index 661928024..a3a1ef3c5 100644 --- a/crates/shell/src/host_tools/studio.rs +++ b/crates/shell/src/host_tools/studio.rs @@ -13,6 +13,38 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; pub const RENDER: &str = "studio.render"; +pub const APP_TOOLS: &[&str] = &[ + "studio.bundle_check", + "studio.open", + "studio.input", + "studio.inspect", + "studio.close", + "studio.install", +]; +pub fn is_tool(name: &str) -> bool { + name == RENDER + || cfg!(all(unix, any(feature = "app-hub", native_mobile))) && APP_TOOLS.contains(&name) +} +pub fn declarations(app: &str) -> Vec { + let mut out = vec![declaration(app)]; + if cfg!(all(unix, any(feature = "app-hub", native_mobile))) { + out.extend(APP_TOOLS.iter().map(|name| app_declaration(app, name))); + } + out +} +fn app_declaration(app: &str, name: &str) -> Value { + let (description,properties,required)=match name { + "studio.bundle_check" => ("Stage and validate an offline main.splash developer bundle from your conversation workspace. Computes and stamps the digest only on a private copy; never modifies source, signs a publisher release, or installs.",json!({"bundle_path":{"type":"string","minLength":1,"maxLength":1024}}),vec!["bundle_path"]), + "studio.install" => ("Install an offline main.splash bundle locally as a developer app. Requires dev.studio.* id. It appears in Home while its developer grant remains valid; its own state persists across close/reopen. No catalog or publisher signature is created.",json!({"bundle_path":{"type":"string","minLength":1,"maxLength":1024}}),vec!["bundle_path"]), + "studio.open" => ("Open a visible contained app. Supply exactly one of bundle_path (disposable preview state) or app_id (previous developer install, persistent app state). Returns an instance id after layout; inspect it before targeting input.",json!({"bundle_path":{"type":"string","minLength":1,"maxLength":1024},"app_id":{"type":"string","minLength":1,"maxLength":100}}),vec![]), + "studio.input" => ("Send a real tap or text event to a visible enabled widget in your Studio app instance. Use the widget id from inspect. Never targets another app or host UI.",json!({"instance_id":{"type":"string","minLength":1,"maxLength":100},"widget_id":{"type":"string","minLength":1,"maxLength":512},"action":{"type":"string","enum":["tap","text","scroll"]},"text":{"type":"string","maxLength":2048},"delta_y":{"type":"number","minimum":-2000,"maximum":2000}}),vec!["instance_id","widget_id","action"]), + "studio.inspect" => ("Inspect your Studio app. Returns a compact page of visible widget selectors; pass each exact selector to studio.input. Follow next_offset with offset for more. path is a PNG for view_image; snapshot_path is the full diagnostic JSON.",json!({"instance_id":{"type":"string","minLength":1,"maxLength":100},"offset":{"type":"integer","minimum":0}}),vec!["instance_id"]), + _ => ("Close your Studio app instance. Preview state is discarded; an installed app's state persists.",json!({"instance_id":{"type":"string","minLength":1,"maxLength":100}}),vec!["instance_id"]), + }; + json!({"name":name,"app":app,"description":description, + "input_schema":{"type":"object","properties":properties,"required":required,"additionalProperties":false}, + "output_schema":{"type":"object"},"risk":if name=="studio.install" || name=="studio.input" {"act"} else {"read"},"background":false,"shareable":false}) +} pub const SUPPORTED: bool = cfg!(unix); pub const SOURCE_MAX: usize = 16 * 1024; pub const DATA_MAX: usize = 32 * 1024; @@ -115,6 +147,8 @@ impl ToolExecutor for StudioExecutor { flag.store(true, Ordering::SeqCst); } crate::studio::cancel(id); + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + crate::studio::apps::cancel(id); } } impl StudioExecutor { @@ -127,18 +161,28 @@ impl StudioExecutor { )); return; }; - if call.app != app || call.name != RENDER { + if call.app != app || !is_tool(&call.name) { reply.finish(ToolOutcome::error( "not_granted", "studio.render belongs to the calling agent", )); return; } - if let Err(why) = super::schema::check(&declaration(&app)["input_schema"], &call.args) { + let decl = if call.name == RENDER { + declaration(&app) + } else { + app_declaration(&app, &call.name) + }; + if let Err(why) = super::schema::check(&decl["input_schema"], &call.args) { reply.finish(ToolOutcome::error("invalid_args", why)); return; } - crate::dev_mode::audit_tool_call(&app, RENDER, &call.args.to_string(), &call.calling_app); + crate::dev_mode::audit_tool_call( + &app, + &call.name, + &call.args.to_string(), + &call.calling_app, + ); if IN_FLIGHT.fetch_add(1, Ordering::SeqCst) >= MAX_IN_FLIGHT { IN_FLIGHT.fetch_sub(1, Ordering::SeqCst); reply.finish(ToolOutcome::error( @@ -162,6 +206,13 @@ impl StudioExecutor { } let running = self.running.clone(); std::thread::spawn(move || { + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + let outcome = if call.name != RENDER { + super::studio_bundles::execute(&call, &root, &tag, &flag, &reply) + } else { + render_call(&call, &root, &app, &tag, &flag, &reply) + }; + #[cfg(not(all(unix, any(feature = "app-hub", native_mobile))))] let outcome = render_call(&call, &root, &app, &tag, &flag, &reply); reply.finish(outcome); running @@ -259,9 +310,14 @@ fn render_call( /// launch fixture, never from model tool arguments. No provider is required. pub fn test_action(spec_path: &str) -> Result<(), String> { let home = crate::octosense::paths::home(); - makepad_widgets::log!("studio-test-profile: home={} canonical={} build={:?}", - home.display(), std::fs::canonicalize(&home).unwrap_or(home.clone()).display(), - crate::dev_mode::BuildKind::current()); + makepad_widgets::log!( + "studio-test-profile: home={} canonical={} build={:?}", + home.display(), + std::fs::canonicalize(&home) + .unwrap_or(home.clone()) + .display(), + crate::dev_mode::BuildKind::current() + ); if !crate::dev_mode::grants_all(SYSTEM) { return Err("studio test action requires developer mode for system".into()); } @@ -314,8 +370,107 @@ pub fn test_action(spec_path: &str) -> Result<(), String> { Ok(()) } +/// Explicit launch-only instrument transport for an already authored project. +/// It shares the system owner and executor with real agent tools. The private +/// spool never enables developer mode and is not exposed as an agent tool. +#[cfg(unix)] +pub fn test_flow(spec_path: &str) -> Result<(), String> { + use std::io::{Read, Write}; + let Some(tag) = crate::dev_mode::tag().filter(|t| authorized(SYSTEM, t)) else { + return Err("studio flow requires developer mode for system".into()); + }; + let mut text = String::new(); + std::fs::File::open(spec_path) + .map_err(|e| e.to_string())? + .take(8193) + .read_to_string(&mut text) + .map_err(|e| e.to_string())?; + if text.len() > 8192 { + return Err("studio flow config exceeds limit".into()); + } + let config: Value = serde_json::from_str(&text).map_err(|e| e.to_string())?; + let root = PathBuf::from(config["workspace"].as_str().ok_or("flow needs workspace")?); + if !root.is_absolute() { + return Err("flow workspace must be absolute".into()); + } + for name in ["requests", "responses"] { + let dir = root.join(name); + std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?; + if std::fs::symlink_metadata(&dir) + .map_err(|e| e.to_string())? + .file_type() + .is_symlink() + { + return Err("flow directory must not be a symlink".into()); + } + } + let scope = scoped::Workspace::open(&root, None)?; + std::thread::spawn(move || { + let executor = StudioExecutor::default(); + let mut seen = std::collections::HashSet::new(); + while authorized(SYSTEM, &tag) && seen.len() < 1024 { + let Ok(entries) = std::fs::read_dir(root.join("requests")) else { + break; + }; + for entry in entries.flatten() { + if seen.len() >= 1024 { + break; + } + let name = entry.file_name().to_string_lossy().into_owned(); + let Some(id) = name.strip_suffix(".json") else { + continue; + }; + if uuid::Uuid::parse_str(id).is_err() || seen.contains(&name) { + continue; + } + // A durable response (including an interrupted empty claim) + // means this UUID was consumed. Never replay input/install + // when the test process restarts to check persistent state. + if scope + .read(&format!("responses/{name}"), 1024 * 1024) + .is_ok() + { + continue; + } + let Ok(text) = scope.read(&format!("requests/{name}"), 16 * 1024) else { + continue; + }; + let Ok(request) = serde_json::from_str::(&text) else { + continue; + }; + seen.insert(name.clone()); + let tool = request["name"].as_str().unwrap_or(""); + if !is_tool(tool) { + continue; + } + let mut call = match HostToolCall::parse( + &json!({"call_id":id,"name":tool,"app":SYSTEM,"session_id":crate::system_chat::session::SYSTEM_SESSION,"caller":{"kind":"system"},"args":request["args"],"timeout_ms":30_000,"risk":"read"}), + ) { + Ok(call) => call, + Err(_) => continue, + }; + call.calling_app = SYSTEM.into(); + let output = match scope.create_file(&format!("responses/{name}")) { + Ok(file) => file, + Err(_) => continue, + }; + let reply = ToolReply::new(id.to_string(), move |fields| { + let data = fields.to_string(); + if data.len() <= 1024 * 1024 { + let mut out = &output; + let _ = out.write_all(data.as_bytes()).and_then(|_| out.sync_all()); + } + }); + executor.execute_scoped(call, reply, root.clone()); + } + std::thread::sleep(Duration::from_millis(100)); + } + }); + Ok(()) +} + #[cfg(unix)] -mod scoped { +pub(super) mod scoped { use super::*; use std::ffi::{CString, OsStr}; use std::fs::File; @@ -365,6 +520,45 @@ mod scoped { Ok(parts) } impl Workspace { + pub fn create_file(&self, path: &str) -> Result { + let names = parts(path)?; + let mut dir = self.dir.clone(); + for name in &names[..names.len() - 1] { + dir = Arc::new(openat( + dir.as_raw_fd(), + name, + libc::O_RDONLY | libc::O_DIRECTORY, + )?); + } + openat( + dir.as_raw_fd(), + names[names.len() - 1], + libc::O_WRONLY | libc::O_CREAT | libc::O_EXCL, + ) + .map(File::from) + } + /// Copy only bounded regular files through directory descriptors. + /// Neither a symlink swap nor an authored relative path can redirect + /// this snapshot outside the calling conversation's workspace. + pub fn copy_bundle( + &self, + path: &str, + target: &Path, + files: usize, + bytes: usize, + per_file: usize, + ) -> Result<(), String> { + let mut dir = self.dir.clone(); + for name in parts(path)? { + dir = Arc::new(openat( + dir.as_raw_fd(), + name, + libc::O_RDONLY | libc::O_DIRECTORY, + )?); + } + let mut budget = (files, bytes); + copy_directory(dir.as_raw_fd(), target, 0, &mut budget, per_file) + } pub fn open(root: &Path, context: Option<&str>) -> Result { let mut fd = openat( libc::AT_FDCWD, @@ -438,7 +632,13 @@ mod scoped { .map(File::from) } pub fn output(&self) -> Result { - let name = format!(".studio-{}.png", uuid::Uuid::new_v4()); + self.output_extension("png") + } + pub fn output_json(&self) -> Result { + self.output_extension("json") + } + fn output_extension(&self, extension: &str) -> Result { + let name = format!(".studio-{}.{}", uuid::Uuid::new_v4(), extension); let fd = openat( self.dir.as_raw_fd(), OsStr::new(&name), @@ -452,6 +652,78 @@ mod scoped { }) } } + fn copy_directory( + fd: i32, + target: &Path, + depth: usize, + budget: &mut (usize, usize), + per_file: usize, + ) -> Result<(), String> { + use std::os::unix::ffi::OsStringExt; + if depth > 8 { + return Err("bundle directory depth exceeds 8".into()); + } + let duplicate = unsafe { libc::dup(fd) }; + if duplicate < 0 { + return Err(std::io::Error::last_os_error().to_string()); + } + let stream = unsafe { libc::fdopendir(duplicate) }; + if stream.is_null() { + unsafe { + libc::close(duplicate); + } + return Err(std::io::Error::last_os_error().to_string()); + } + struct Dir(*mut libc::DIR); + impl Drop for Dir { + fn drop(&mut self) { + unsafe { + libc::closedir(self.0); + } + } + } + let stream = Dir(stream); + loop { + let entry = unsafe { libc::readdir(stream.0) }; + if entry.is_null() { + break; + } + let name = unsafe { std::ffi::CStr::from_ptr((*entry).d_name.as_ptr()) }.to_bytes(); + if name == b"." || name == b".." { + continue; + } + if budget.0 == 0 { + return Err("bundle exceeds file/directory count limit".into()); + } + budget.0 -= 1; + let name = std::ffi::OsString::from_vec(name.to_vec()); + let input = File::from(openat(fd, &name, libc::O_RDONLY)?); + let meta = input.metadata().map_err(|e| e.to_string())?; + let output = target.join(&name); + if meta.is_dir() { + std::fs::create_dir(&output).map_err(|e| e.to_string())?; + copy_directory(input.as_raw_fd(), &output, depth + 1, budget, per_file)?; + } else if meta.is_file() { + let cap = per_file.min(budget.1); + if meta.len() > cap as u64 { + return Err("bundle exceeds byte limit".into()); + } + let mut bytes = Vec::new(); + input + .take(cap as u64 + 1) + .read_to_end(&mut bytes) + .map_err(|e| e.to_string())?; + if bytes.len() > cap { + return Err("bundle exceeds byte limit".into()); + } + budget.1 -= bytes.len(); + std::fs::write(&output, bytes).map_err(|e| e.to_string())?; + } else { + return Err("bundle accepts only regular files and directories".into()); + } + } + Ok(()) + } pub struct Output { pub file: File, dir: Arc, @@ -459,6 +731,9 @@ mod scoped { keep: bool, } impl Output { + pub fn path(&self) -> &str { + &self.name + } pub fn keep(mut self) -> String { self.keep = true; self.name.clone() @@ -536,6 +811,8 @@ mod tests { let scope = scoped::Workspace::open(&root, None).unwrap(); { let _output = scope.output().unwrap(); + let artifact = scope.output_json().unwrap(); + assert!(artifact.path().ends_with(".json")); } assert_eq!(std::fs::read_dir(&root).unwrap().count(), 0); std::fs::rename(&root, temp.0.as_path().join("old")).unwrap(); @@ -568,6 +845,23 @@ mod tests { } } + #[test] + fn flow_response_claim_survives_restart_without_replaying_a_request() { + let temp = crate::app_storage::tests::Scratch::new("studio-flow-claim"); + std::fs::create_dir(temp.0.join("responses")).unwrap(); + let path = "responses/11111111-1111-4111-8111-111111111111.json"; + { + let first = scoped::Workspace::open(&temp.0, None).unwrap(); + let _claim = first.create_file(path).unwrap(); + assert!(first.create_file(path).is_err()); + } + let restarted = scoped::Workspace::open(&temp.0, None).unwrap(); + // Even a crash before writing the response consumes the UUID. The + // polling loop sees this before adding it to its per-process limit. + assert_eq!(restarted.read(path, 1024 * 1024).unwrap(), ""); + assert!(restarted.create_file(path).is_err()); + } + #[test] fn cancellation_marks_the_running_call() { let executor = StudioExecutor::default(); diff --git a/crates/shell/src/host_tools/studio_bundles.rs b/crates/shell/src/host_tools/studio_bundles.rs new file mode 100644 index 000000000..cdcb8bc5f --- /dev/null +++ b/crates/shell/src/host_tools/studio_bundles.rs @@ -0,0 +1,825 @@ +//! Host-owned developer bundle snapshots. These are local installs, never +//! signed catalog releases. Source trees remain in the author's workspace; +//! admitted bytes and DevTag receipts stay outside every app's storage jail. +use super::studio::scoped::Workspace; +use crate::ai_host::app_peers::host_tools::HostToolCall; +use crate::dev_mode::DevTag; +use octosense_app_policy::{AppManifest, AppPolicy, HostLimits, RefuseAllSignatures}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + +pub const MAX_FILES: usize = 128; +pub const MAX_BYTES: usize = 2 * 1024 * 1024; +pub const MAX_FILE_BYTES: usize = 512 * 1024; +pub const MAX_SOURCE_BYTES: usize = 64 * 1024; +static INSTALL_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + +/// Identity comes from broker/session stamping, never tool arguments. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Owner { + pub app: String, + pub account: Option, + pub session: String, + pub context: Option, +} +impl Owner { + pub fn of(call: &HostToolCall) -> Self { + Self { + app: call.calling_app.clone(), + account: call.account.clone(), + session: call.session_id.clone(), + context: call.context_id.clone(), + } + } +} + +pub struct Bundle { + pub id: String, + pub name: String, + pub version: String, + pub digest: String, + pub root: PathBuf, + pub source: String, + pub policy: AppPolicy, + pub owner: Owner, + pub dev_tag: DevTag, + /// Installed snapshots are retained; temporary previews remove theirs. + retained: std::sync::atomic::AtomicBool, +} +impl Bundle { + pub fn valid(&self) -> bool { + crate::dev_mode::tag_valid(&self.dev_tag) + && crate::dev_mode::grants_all(super::app_of_peer(&self.owner.app)) + } + pub fn summary(&self) -> Value { + json!({"app_id":self.id,"name":self.name,"version":self.version, + "digest":self.digest,"source_modified":false,"admission":"local_developer", + "capabilities":self.policy.capabilities,"publisher_signed":false}) + } +} +impl Drop for Bundle { + fn drop(&mut self) { + if !self.retained.load(std::sync::atomic::Ordering::Acquire) { + let _ = std::fs::remove_dir_all(&self.root); + } + } +} +fn base() -> Result { + crate::octosense::paths::private_dir("studio").map_err(|e| e.to_string()) +} +fn private_child(parent: &Path, child: &str) -> Result { + let path = parent.join(child); + std::fs::create_dir_all(&path).map_err(|e| e.to_string())?; + let meta = std::fs::symlink_metadata(&path).map_err(|e| e.to_string())?; + if !meta.is_dir() || meta.file_type().is_symlink() { + return Err("studio directory must be private and real".into()); + } + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o700)) + .map_err(|e| e.to_string())?; + Ok(path) +} +fn valid_id(id: &str) -> Result<(), String> { + if !id.starts_with("dev.studio.") + || id.len() > 100 + || id.len() <= 11 + || !id.bytes().all(|b| { + b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'.' | b'-' | b'_') + }) + { + return Err("developer app ids must be under dev.studio.*".into()); + } + crate::apps::check_script_app_id(id) +} + +pub fn stage( + scope: &Workspace, + relative: &str, + owner: Owner, + tag: DevTag, +) -> Result, String> { + let snapshots = private_child(&base()?, "bundles")?; + let root = snapshots.join(uuid::Uuid::new_v4().to_string()); + std::fs::create_dir(&root).map_err(|e| e.to_string())?; + let result = (|| { + scope.copy_bundle(relative, &root, MAX_FILES, MAX_BYTES, MAX_FILE_BYTES)?; + admit_snapshot(root.clone(), owner, tag, None) + })(); + if result.is_err() { + let _ = std::fs::remove_dir_all(&root); + } + result.map(Arc::new) +} +fn admit_snapshot( + root: PathBuf, + owner: Owner, + tag: DevTag, + expected: Option<(&str, &str)>, +) -> Result { + let manifest_path = root.join("manifest.json"); + let text = + std::fs::read_to_string(&manifest_path).map_err(|e| format!("manifest.json: {e}"))?; + if text.len() > 64 * 1024 { + return Err("manifest exceeds 64 KiB".into()); + } + let mut raw: Value = serde_json::from_str(&text).map_err(|e| e.to_string())?; + if raw + .pointer("/integrity/signature") + .is_some_and(|s| !s.is_null()) + { + return Err( + "developer bundles must be unsigned; Studio never impersonates a publisher".into(), + ); + } + let digest = octosense_app_policy::digest_dir(&root)?; + if let Some((wanted_digest, wanted_manifest)) = expected { + if digest != wanted_digest || text != wanted_manifest { + return Err("installed developer bundle changed after admission".into()); + } + } else { + if !raw.is_object() { + return Err("manifest must be an object".into()); + } + if raw.get("integrity").is_none() { + raw["integrity"] = json!({}); + } + if !raw["integrity"].is_object() { + return Err("integrity must be an object".into()); + } + raw["integrity"]["bundle_blake3"] = json!(digest); + } + let text = if expected.is_some() { + text + } else { + serde_json::to_string_pretty(&raw).map_err(|e| e.to_string())? + }; + let manifest = AppManifest::parse(&text)?; + valid_id(&manifest.id)?; + if manifest.capabilities.iter().any(|c| c != "storage") + || !manifest.network.hosts.is_empty() + || manifest.agent.is_some() + || manifest.storage.accounts + { + return Err("this Studio release accepts offline, storage-only script apps without accounts or agents".into()); + } + let policy = octosense_app_policy::admit_and_resolve_dir( + &text, + &digest, + &HostLimits::default() + .with_require_signature(false) + .with_max_storage_bytes(1024 * 1024) + .with_max_instruction_budget(5_000_000) + .with_max_memory_bytes(16 * 1024 * 1024), + &RefuseAllSignatures, + )?; + let source = + octosense_app_policy::script_source(&root, "").ok_or("bundle needs main.splash")??; + if source.is_empty() || source.len() > MAX_SOURCE_BYTES { + return Err("main.splash must be nonempty and at most 64 KiB".into()); + } + if source.contains("{{assets}}") || source.contains("http_resource(") { + return Err("this Studio release accepts resource-free script screens; local launcher artwork may still ship in the bundle".into()); + } + if expected.is_none() { + std::fs::write(&manifest_path, &text).map_err(|e| e.to_string())?; + } + Ok(Bundle { + id: manifest.id, + name: manifest.name, + version: manifest.version, + digest, + root, + source, + policy, + owner, + dev_tag: tag, + retained: std::sync::atomic::AtomicBool::new(expected.is_some()), + }) +} + +#[derive(Serialize, Deserialize)] +struct Receipt { + owner: Owner, + profile_id: String, + since: u64, + snapshot: String, + digest: String, + manifest: String, +} +/// Commit only host-owned, already-admitted bytes. Existing data remains for +/// an update by the same owner; a different conversation cannot take its id. +pub fn install(bundle: &Arc) -> Result { + let _guard = INSTALL_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + if !bundle.valid() { + return Err("developer grant expired".into()); + } + let receipts = private_child(&base()?, "installed")?; + let path = receipts.join(format!("{}.json", bundle.id)); + let previous = if path.exists() { + let old: Receipt = + serde_json::from_slice(&std::fs::read(&path).map_err(|e| e.to_string())?) + .map_err(|e| e.to_string())?; + if old.owner != bundle.owner { + return Err("this app id belongs to another Studio conversation".into()); + } + Some(old.snapshot) + } else { + if std::fs::read_dir(&receipts) + .map_err(|e| e.to_string())? + .flatten() + .filter(|e| e.path().extension().is_some_and(|s| s == "json")) + .count() + >= 16 + { + return Err("Studio supports at most 16 local developer installs".into()); + } + None + }; + let receipt = Receipt { + owner: bundle.owner.clone(), + profile_id: bundle.dev_tag.profile_id.clone(), + since: bundle.dev_tag.since, + snapshot: bundle + .root + .file_name() + .ok_or("invalid snapshot")? + .to_string_lossy() + .into_owned(), + digest: bundle.digest.clone(), + manifest: std::fs::read_to_string(bundle.root.join("manifest.json")) + .map_err(|e| e.to_string())?, + }; + let temp = receipts.join(format!(".{}.tmp", uuid::Uuid::new_v4())); + use std::io::Write; + use std::os::unix::fs::OpenOptionsExt; + let mut file = std::fs::OpenOptions::new() + .write(true) + .create_new(true) + .mode(0o600) + .open(&temp) + .map_err(|e| e.to_string())?; + file.write_all(&serde_json::to_vec(&receipt).map_err(|e| e.to_string())?) + .and_then(|_| file.sync_all()) + .map_err(|e| e.to_string())?; + if !bundle.valid() { + let _ = std::fs::remove_file(&temp); + return Err("developer grant expired".into()); + } + std::fs::rename(&temp, &path).map_err(|e| e.to_string())?; + bundle + .retained + .store(true, std::sync::atomic::Ordering::Release); + if let Some(old) = previous.filter(|old| uuid::Uuid::parse_str(old).is_ok()) { + let old = bundle.root.parent().unwrap().join(old); + if old != bundle.root { + let _ = std::fs::remove_dir_all(old); + } + } + let mut result = bundle.summary(); + result["installed"] = json!(true); + Ok(result) +} +/// Launcher calls with None; host tools pass the full calling identity. +pub fn installed(id: &str, owner: Option<&Owner>) -> Result, String> { + valid_id(id)?; + let root = base()?; + let bytes = std::fs::read(root.join("installed").join(format!("{id}.json"))) + .map_err(|e| e.to_string())?; + if bytes.len() > 128 * 1024 { + return Err("invalid developer receipt".into()); + } + let receipt: Receipt = serde_json::from_slice(&bytes).map_err(|e| e.to_string())?; + if owner.is_some_and(|o| o != &receipt.owner) { + return Err("this app belongs to another Studio conversation".into()); + } + let tag = DevTag { + profile_id: receipt.profile_id, + since: receipt.since, + }; + if !crate::dev_mode::tag_valid(&tag) + || !crate::dev_mode::grants_all(super::app_of_peer(&receipt.owner.app)) + { + return Err("developer grant expired".into()); + } + uuid::Uuid::parse_str(&receipt.snapshot).map_err(|_| "invalid developer snapshot")?; + let bundle = admit_snapshot( + root.join("bundles").join(receipt.snapshot), + receipt.owner, + tag, + Some((&receipt.digest, &receipt.manifest)), + )?; + if bundle.id != id { + return Err("developer receipt identity mismatch".into()); + } + Ok(Arc::new(bundle)) +} +pub fn installed_apps() -> Vec> { + let Ok(root) = base() else { return Vec::new() }; + let Ok(entries) = std::fs::read_dir(root.join("installed")) else { + return Vec::new(); + }; + entries + .flatten() + .filter_map(|e| e.path().file_stem()?.to_str().map(str::to_owned)) + .filter_map(|id| installed(&id, None).ok()) + .collect() +} +pub fn data_dir(bundle: &Bundle) -> Result { + if !bundle.valid() { + return Err("developer grant expired".into()); + } + private_child(&private_child(&base()?, "data")?, &bundle.id) +} + +static GENERATION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); +pub fn generation() -> u64 { + GENERATION.load(std::sync::atomic::Ordering::Acquire) +} +fn owner_key(owner: &Owner) -> String { + serde_json::to_string(owner).expect("owner serializes") +} +fn open_bundle(bundle: Arc, persistent: bool) -> Result { + if !bundle.valid() { + return Err("developer grant expired".into()); + } + let instance_id = uuid::Uuid::new_v4().to_string(); + let jail = if persistent { + data_dir(&bundle)? + } else { + private_child(&private_child(&base()?, "previews")?, &instance_id)? + }; + let admission = crate::studio::apps::stage_open(crate::studio::apps::OpenSpec { + instance_id: instance_id.clone(), + app_id: if persistent { + Some(bundle.id.clone()) + } else { + None + }, + owner: owner_key(&bundle.owner), + dev_tag: bundle.dev_tag.clone(), + source: bundle.source.clone(), + storage_quota: if bundle.policy.allows("storage") { + bundle.policy.storage_bytes + } else { + 0 + }, + instruction_budget: bundle.policy.instruction_budget, + memory_bytes: bundle.policy.memory_bytes, + jail: jail.clone(), + persistent, + title: bundle.name.clone(), + }); + if let Err(error) = admission { + if !persistent { + let _ = std::fs::remove_dir_all(&jail); + } + return Err(error); + } + Ok(instance_id) +} +/// A person launches a validated installed receipt through the same UI queue. +pub fn request_launch_installed(id: &str) -> Result { + open_bundle(installed(id, None)?, true) +} + +pub fn execute( + call: &HostToolCall, + root: &Path, + tag: &DevTag, + cancel: &std::sync::atomic::AtomicBool, + reply: &crate::ai_host::app_peers::host_tools::ToolReply, +) -> crate::ai_host::app_peers::host_tools::ToolOutcome { + use crate::ai_host::app_peers::host_tools::ToolOutcome; + let run = || -> Result { + let scope = Workspace::open(root, call.context_id.as_deref())?; + let owner = Owner::of(call); + let mut opened: Option = None; + let mut guard = OpenGuard { + instance: None, + owner: owner_key(&owner), + tag: tag.clone(), + }; + let mut opened_summary = None; + let mut output = None; + let (instance_id, action) = match call.name.as_str() { + "studio.bundle_check" | "studio.install" => { + let bundle = stage( + &scope, + call.args["bundle_path"].as_str().unwrap_or(""), + owner, + tag.clone(), + )?; + if cancel.load(std::sync::atomic::Ordering::Acquire) || !reply.is_open() { + return Err("studio_cancelled".into()); + } + scope.verify(root, call.context_id.as_deref())?; + if call.name == "studio.install" { + let result = install(&bundle)?; + GENERATION.fetch_add(1, std::sync::atomic::Ordering::AcqRel); + makepad_widgets::SignalToUI::set_ui_signal(); + return Ok(result); + } + return Ok(bundle.summary()); + } + "studio.open" => { + let path = call.args["bundle_path"].as_str(); + let id = call.args["app_id"].as_str(); + if path.is_some() == id.is_some() { + return Err("supply exactly one of bundle_path or app_id".into()); + } + let persistent = id.is_some(); + let bundle = if let Some(id) = id { + installed(id, Some(&owner))? + } else { + stage(&scope, path.unwrap(), owner.clone(), tag.clone())? + }; + opened_summary = Some(bundle.summary()); + let instance = open_bundle(bundle, persistent)?; + guard.instance = Some(instance.clone()); + opened = Some(instance.clone()); + (instance, crate::studio::apps::Action::Ready) + } + "studio.input" => { + let kind = call.args["action"].as_str().unwrap_or(""); + let text = call.args["text"].as_str().map(str::to_owned); + let delta_y = call.args["delta_y"].as_f64(); + if kind == "scroll" && (delta_y.is_none() || text.is_some()) { + return Err("scroll requires delta_y and no text".into()); + } + if kind != "scroll" && delta_y.is_some() { + return Err("delta_y is only for scroll".into()); + } + if kind == "text" && text.is_none() { + return Err("text action requires text".into()); + } + if kind == "tap" && text.is_some() { + return Err("tap action takes no text".into()); + } + ( + call.args["instance_id"].as_str().unwrap_or("").into(), + crate::studio::apps::Action::Input { + widget_id: call.args["widget_id"].as_str().unwrap_or("").into(), + kind: kind.into(), + text, + delta_y, + }, + ) + } + "studio.inspect" => { + let file = scope.output()?; + let action = crate::studio::apps::Action::Inspect { + output: file.file.try_clone().map_err(|e| e.to_string())?, + }; + output = Some(file); + ( + call.args["instance_id"].as_str().unwrap_or("").into(), + action, + ) + } + "studio.close" => ( + call.args["instance_id"].as_str().unwrap_or("").into(), + crate::studio::apps::Action::Close, + ), + _ => return Err("unsupported Studio operation".into()), + }; + let (tx, rx) = std::sync::mpsc::channel(); + crate::studio::apps::submit(crate::studio::apps::Request { + id: call.call_id.clone(), + owner: owner_key(&owner), + dev_tag: tag.clone(), + instance_id: instance_id.clone(), + action, + reply: tx, + })?; + let deadline = std::time::Instant::now() + + std::time::Duration::from_millis( + call.timeout_ms.saturating_sub(500).clamp(1, 25_000), + ); + let mut result = loop { + if cancel.load(std::sync::atomic::Ordering::Acquire) + || !reply.is_open() + || !crate::dev_mode::tag_valid(tag) + || !crate::dev_mode::grants_all(super::app_of_peer(&owner.app)) + || std::time::Instant::now() >= deadline + { + crate::studio::apps::cancel(&call.call_id); + return Err("studio_cancelled_or_timed_out".into()); + } + match rx.recv_timeout(std::time::Duration::from_millis(25)) { + Ok(result) => break result?, + Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {} + Err(_) => return Err("studio app runner stopped".into()), + } + }; + scope.verify(root, call.context_id.as_deref())?; + if !crate::dev_mode::tag_valid(tag) + || !reply.is_open() + || cancel.load(std::sync::atomic::Ordering::Acquire) + { + return Err("studio_cancelled".into()); + } + if let Some(id) = opened { + result["instance_id"] = json!(id); + if let Some(summary) = opened_summary { + result["bundle"] = summary; + } + } + if let Some(output) = output { + use std::io::Write; + result["path"] = json!(output.path()); + // Line-oriented diagnostics let read_file offset/limit recover a + // bounded section through the kernel's own model-output budget. + let bytes = serde_json::to_vec_pretty(&result).map_err(|e| e.to_string())?; + if bytes.len() > INSPECT_ARTIFACT_BYTES { + return Err("studio_snapshot_too_large".into()); + } + let mut artifact = scope.output_json()?; + artifact + .file + .write_all(&bytes) + .and_then(|_| artifact.file.sync_all()) + .map_err(|e| e.to_string())?; + let offset = call + .args + .get("offset") + .map(|v| { + v.as_u64() + .and_then(|n| usize::try_from(n).ok()) + .ok_or("studio_inspect_offset_invalid") + }) + .transpose()? + .unwrap_or(0); + result = compact_inspect(&result, artifact.path(), offset)?; + // Both guards unlink on any error, including revocation or a + // replaced context directory during artifact serialization. + scope.verify(root, call.context_id.as_deref())?; + if !crate::dev_mode::tag_valid(tag) + || !crate::dev_mode::grants_all(super::app_of_peer(&owner.app)) + || !reply.is_open() + || cancel.load(std::sync::atomic::Ordering::Acquire) + { + return Err("studio_cancelled".into()); + } + output.keep(); + artifact.keep(); + } + guard.instance = None; + Ok(result) + }; + match run() { + Ok(value) => ToolOutcome::Ok(value), + Err(why) => ToolOutcome::error("studio_app_failed", why), + } +} + +// The pinned kernel context manager exposes only a 4096-byte prefix of tool +// output to the model. Keep actionable selectors intact inside that budget; +// verbose geometry/tree data lives in the separate caller-scoped artifact. +const INSPECT_REPLY_BYTES: usize = 3800; +const INSPECT_ARTIFACT_BYTES: usize = 1024 * 1024; +fn compact_inspect(full: &Value, snapshot_path: &str, offset: usize) -> Result { + let widgets: Vec<&Value> = full["snapshot"]["widgets"] + .as_array() + .ok_or("studio_snapshot_widgets_missing")? + .iter() + .filter(|w| w["visible"] == true && w["type"] != "Splash") + .collect(); + if offset > widgets.len() { + return Err("studio_inspect_offset_out_of_range".into()); + } + let findings = full["snapshot"]["checks"]["findings"].as_array(); + let mut result = json!({ + "instance_id": full["instance_id"], "path": full["path"], + "snapshot_path": snapshot_path, "width": full["width"], "height": full["height"], + "settled": full["settled"], "offset": offset, "total_widgets": widgets.len(), + "next_offset": null, + "snapshot": {"widgets": [], "checks": { + "pass": full["snapshot"]["checks"]["pass"], + "finding_count": findings.map_or(0, |f| f.len()), + "error_count": findings.map_or(0, |f| f.iter().filter(|v| v["severity"] == "error").count()) + }} + }); + for (i, widget) in widgets.iter().enumerate().skip(offset) { + let mut row = json!({"selector":widget["selector"], "type":widget["type"], + "visible":true, "enabled":widget["enabled"]}); + for field in ["text", "value"] { + if let Some(text) = widget[field].as_str() { + let mut end = text.len().min(160); + while !text.is_char_boundary(end) { + end -= 1; + } + row[field] = json!(&text[..end]); + if end < text.len() { + row[format!("{field}_truncated")] = json!(true); + } + } + } + for field in ["checked", "selected"] { + if !widget[field].is_null() { + row[field] = widget[field].clone(); + } + } + result["snapshot"]["widgets"] + .as_array_mut() + .unwrap() + .push(row); + result["next_offset"] = if i + 1 < widgets.len() { + json!(i + 1) + } else { + Value::Null + }; + if serde_json::to_vec(&result) + .map_err(|e| e.to_string())? + .len() + > INSPECT_REPLY_BYTES + { + result["snapshot"]["widgets"].as_array_mut().unwrap().pop(); + result["next_offset"] = json!(i); + if i == offset { + return Err("studio_widget_selector_exceeds_reply_budget".into()); + } + break; + } + } + if serde_json::to_vec(&result) + .map_err(|e| e.to_string())? + .len() + > INSPECT_REPLY_BYTES + { + return Err("studio_inspect_metadata_exceeds_reply_budget".into()); + } + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn inspect_pages_fit_model_budget_without_losing_unicode_selectors() { + let widgets: Vec = (0..100).map(|i| json!({ + "selector":format!("完成任务{i}@0"),"type":"Button", "visible":true,"enabled":true, + "text":"中文😀\"\n".repeat(1000), "value":"记录".repeat(100), "checked":false + })).collect(); + let full = json!({"instance_id":"test", "path":".studio-test.png", "width":1080, + "height":2000,"settled":false,"snapshot":{"widgets":widgets, + "checks":{"pass":true,"findings":[]},"geometry":{"large":"x".repeat(50000)},"tree":"verbose"}}); + let mut offset = 0; + let mut seen = Vec::new(); + loop { + let page = compact_inspect(&full, ".studio-test.json", offset).unwrap(); + let serialized = serde_json::to_vec(&page).unwrap(); + assert!(serialized.len() <= INSPECT_REPLY_BYTES); + let rows = page["snapshot"]["widgets"].as_array().unwrap(); + assert!(!rows.is_empty()); + for row in rows { + assert_eq!(row["text_truncated"], true); + assert_eq!(row["value_truncated"], true); + seen.push(row["selector"].as_str().unwrap().to_owned()); + } + match page["next_offset"].as_u64() { + Some(next) => { + assert!(next as usize > offset); + offset = next as usize; + } + None => break, + } + } + assert_eq!( + seen, + (0..100) + .map(|i| format!("完成任务{i}@0")) + .collect::>() + ); + assert!(compact_inspect(&full, "artifact.json", 101).is_err()); + } + #[test] + fn inspect_paging_excludes_hidden_and_source_widgets_and_refuses_unpageable_selector() { + let mut full = json!({"snapshot":{"widgets":[ + {"type":"Splash","visible":true,"selector":"source@0"}, + {"type":"Button","visible":false,"selector":"hidden@0"}, + {"type":"Button","visible":true,"enabled":false,"selector":"disabled@0","text":"No"} + ],"checks":{"pass":false,"findings":[{"severity":"error"},{"severity":"warning"}]}}}); + let page = compact_inspect(&full, "artifact.json", 0).unwrap(); + assert_eq!(page["total_widgets"], 1); + assert_eq!(page["snapshot"]["widgets"][0]["selector"], "disabled@0"); + assert_eq!(page["snapshot"]["checks"]["error_count"], 1); + assert!( + compact_inspect(&full, "artifact.json", 1).unwrap()["snapshot"]["widgets"] + .as_array() + .unwrap() + .is_empty() + ); + full["snapshot"]["widgets"][2]["selector"] = json!("x".repeat(4000)); + assert!(compact_inspect(&full, "artifact.json", 0).is_err()); + } + + fn owner() -> Owner { + Owner { + app: "system".into(), + account: None, + session: "system-session".into(), + context: None, + } + } + fn tag() -> DevTag { + DevTag { + profile_id: "test-profile".into(), + since: 1, + } + } + fn fixture(root: &Path, edit: impl FnOnce(&mut Value)) { + std::fs::create_dir_all(root).unwrap(); + std::fs::write(root.join("main.splash"), "View{Label{text: \"authored\"}}").unwrap(); + let mut manifest = json!({"schema":1,"id":"dev.studio.planner","version":"0.1.0","name":"Planner","capabilities":["storage"],"integrity":{"bundle_blake3":"pending"}}); + edit(&mut manifest); + std::fs::write(root.join("manifest.json"), manifest.to_string()).unwrap(); + } + #[test] + fn staging_is_bounded_confined_and_never_stamps_author_source() { + let home = crate::app_storage::tests::Scratch::new("studio-bundle"); + let author = home.0.join("author"); + fixture(&author, |_| {}); + let original = std::fs::read(author.join("manifest.json")).unwrap(); + let snapshot = home.0.join("copy"); + std::fs::create_dir(&snapshot).unwrap(); + let scope = Workspace::open(&home.0, None).unwrap(); + scope + .copy_bundle("author", &snapshot, MAX_FILES, MAX_BYTES, MAX_FILE_BYTES) + .unwrap(); + let bundle = admit_snapshot(snapshot, owner(), tag(), None).unwrap(); + assert_eq!( + std::fs::read(author.join("manifest.json")).unwrap(), + original + ); + assert_ne!(bundle.digest, "pending"); + assert!(bundle.policy.hosts.is_empty()); + assert_eq!(bundle.policy.storage_bytes, 1024 * 1024); + let manifest = std::fs::read_to_string(bundle.root.join("manifest.json")).unwrap(); + std::fs::write(bundle.root.join("main.splash"), "different").unwrap(); + assert!(admit_snapshot( + bundle.root.clone(), + owner(), + tag(), + Some((&bundle.digest, &manifest)) + ) + .is_err()); + assert!(scope + .copy_bundle("../escape", &home.0.join("never"), 10, 1024, 1024) + .is_err()); + let tiny = home.0.join("tiny"); + std::fs::create_dir(&tiny).unwrap(); + assert!(scope.copy_bundle("author", &tiny, 10, 1, 1).is_err()); + } + #[test] + fn local_admission_cannot_claim_system_ids_publishers_network_or_agents() { + for change in 0..4 { + let home = crate::app_storage::tests::Scratch::new("studio-refusal"); + fixture(&home.0, |m| match change { + 0 => m["id"] = json!("os.mail"), + 1 => m["integrity"]["signature"] = json!({"key_id":"fake","value":"fake"}), + 2 => m["capabilities"] = json!(["storage", "net"]), + _ => m["storage"] = json!({"accounts":true}), + }); + assert!(admit_snapshot(home.0.clone(), owner(), tag(), None).is_err()); + } + } + #[test] + fn snapshot_refuses_symlinks_and_owner_includes_account_and_context() { + let home = crate::app_storage::tests::Scratch::new("studio-links"); + fixture(&home.0.join("author"), |_| {}); + std::os::unix::fs::symlink("/etc/hosts", home.0.join("author/link")).unwrap(); + let target = home.0.join("copy"); + std::fs::create_dir(&target).unwrap(); + let scope = Workspace::open(&home.0, None).unwrap(); + assert!(scope + .copy_bundle("author", &target, MAX_FILES, MAX_BYTES, MAX_FILE_BYTES) + .is_err()); + let a = owner(); + let mut b = a.clone(); + b.context = Some("other".into()); + assert_ne!(owner_key(&a), owner_key(&b)); + b = a.clone(); + b.account = Some("other-account".into()); + assert_ne!(owner_key(&a), owner_key(&b)); + b = a.clone(); + b.session = "different-peer".into(); + assert_ne!(owner_key(&a), owner_key(&b)); + } +} + +struct OpenGuard { + instance: Option, + owner: String, + tag: DevTag, +} +impl Drop for OpenGuard { + fn drop(&mut self) { + if let Some(id) = &self.instance { + let _ = crate::studio::apps::abandon(id, &self.owner, &self.tag); + } + } +} diff --git a/crates/shell/src/host_tools/tests.rs b/crates/shell/src/host_tools/tests.rs index caeff24bb..d2ed8a4d3 100644 --- a/crates/shell/src/host_tools/tests.rs +++ b/crates/shell/src/host_tools/tests.rs @@ -557,6 +557,8 @@ fn a_peer_is_offered_exactly_its_granted_toolbox_tools_marked_with_their_owner_a let mut every = shareable_native_tools(); every.push(super::relay::DEV_RUN.to_string()); if super::studio::SUPPORTED { every.push(super::studio::RENDER.to_string()); } + #[cfg(all(unix, any(feature="app-hub", native_mobile)))] + every.extend(super::studio::APP_TOOLS.iter().map(|name| name.to_string())); every.sort(); assert_eq!(offered_names(&relay, "calendar", true, true), every); } @@ -1212,3 +1214,33 @@ fn studio_render_is_scoped_revocable_and_schema_checked() { assert!(!relay.catalog.offered("os.news", false, true).iter().any(|d| d["name"] == super::studio::RENDER)); assert!(relay.catalog.offered("os.news", true, true).iter().any(|d| d["name"] == super::studio::RENDER)); } + +#[cfg(all(unix, any(feature="app-hub",native_mobile)))] +#[test] +fn studio_app_tools_are_own_caller_only_and_disappear_on_revocation() { + let mut relay=Relay::default(); + let host=Arc::new(Exec::default()); + relay.set_executor(super::relay::HOST_EXECUTOR,Some(host.clone())); + let mut w=World::new(FixedDevMode::all());w.dev_all=true; + for (index,name) in super::studio::APP_TOOLS.iter().enumerate(){ + let id=format!("studio-app-{index}"); + let mut c=call(&id,name,"os.news");c.app="os.news".into(); + c.args=match *name { + "studio.bundle_check"|"studio.install"|"studio.open"=>json!({"bundle_path":"planner"}), + "studio.input"=>json!({"instance_id":"one","widget_id":"button","action":"tap"}), + _=>json!({"instance_id":"one"}), + }; + let (r,_)=reply(&id); + relay.handle(Event::Call{call:c.clone(),reply:r},&mut w); + let (r,sent)=reply(&format!("cross-{index}")); + c.call_id=format!("cross-{index}");c.app="os.mail".into(); + relay.handle(Event::Call{call:c.clone(),reply:r},&mut w); + assert_eq!(sent.lock().unwrap()[0]["error"]["kind"],"not_granted"); + c.app="os.news".into();c.call_id=format!("revoked-{index}");w.dev_all=false; + let (r,sent)=reply(&c.call_id); + relay.handle(Event::Call{call:c,reply:r},&mut w); + assert_eq!(sent.lock().unwrap()[0]["error"]["kind"],"not_granted"); + w.dev_all=true; + } + assert_eq!(host.0.lock().unwrap().len(),super::studio::APP_TOOLS.len()); +} diff --git a/crates/shell/src/lib.rs b/crates/shell/src/lib.rs index e87ef39da..434c2635a 100644 --- a/crates/shell/src/lib.rs +++ b/crates/shell/src/lib.rs @@ -508,6 +508,8 @@ pub struct App { #[rust] pub test_capture: Option<(Timer, std::path::PathBuf)>, #[rust] pub studio: StudioRenderer, + #[rust] pub studio_clients: HashMap, + #[rust] pub studio_registry_generation: u64, #[rust] pub test_capture_ticket: Option, #[rust] pub test_recording: bool, /// `--test-action page:`: the home page to jump to, once the phone @@ -665,6 +667,34 @@ pub struct DividerDrag { } impl App { + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + fn drain_studio(&mut self, cx: &mut Cx) { + for instance in studio::apps::take_closes() { + if let Some(client) = self.studio_clients.remove(&instance) { + self.close_client(cx, client, false); + } + } + for instance in studio::apps::take_launches() { + let Some((app_id, title)) = studio::apps::launch_info(&instance) else { continue }; + let app = apps::studio_row(app_id.unwrap_or_else(|| format!("studio-instance:{instance}")), + title, vec![format!("--studio-instance={instance}")]); + let client = self.next_id; + self.launch_module_as(cx, &studio::module::STUDIO_MODULE, &app); + if self.state.as_ref().is_some_and(|state| state.clients.contains_key(&client)) { + self.studio_clients.insert(instance, client); + } else { + studio::apps::shutdown(cx, &instance); + } + } + let generation = host_tools::studio_bundles::generation(); + if generation != self.studio_registry_generation { + self.studio_registry_generation = generation; + shell::launcher::invalidate_apps(); + self.redraw_all(cx); + } + } + + fn desk(&self, cx: &mut Cx) -> WidgetRef { self.ui.widget(cx, ids!(desk)) } @@ -792,6 +822,14 @@ impl App { } } + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + if app.bin == "studio-app" { + if let Err(error) = host_tools::studio_bundles::request_launch_installed(&app.id) { + self.notify(cx, "Could not open developer app", &error); + } + return; + } + // In-process hosting (aicontrol §3): a linked module the person // switched on (or a dev `--module` run) becomes an instance in an // isolate of its own — never a process, never the pool. @@ -1963,6 +2001,7 @@ impl App { } fn remove_client(&mut self, cx: &mut Cx, client: ClientId) { + self.studio_clients.retain(|_, id| *id != client); log!("wm: removing client {}", client); peer_link::process_gone(client); self.process_close.gone(client); @@ -3469,6 +3508,7 @@ impl App { self.pane_links.reannounce(); // App peers take `dev.run` and developer grants, or lose them. crate::host_tools::developer_mode_changed(); + shell::launcher::invalidate_apps(); for notice in dev_mode::take_notices() { self.notify(cx, "Developer mode", ¬ice); } @@ -5137,6 +5177,8 @@ impl App { fn route_texture_readbacks(&mut self, cx: &mut Cx) { for result in cx.try_take_texture_readbacks() { let Some(result) = self.studio.readback(cx, result) else {continue}; + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + let Some(result) = studio::apps::readback(cx, result) else {continue}; #[cfg(target_os = "android")] if Some(result.ticket) == self.test_capture_ticket { let Some((_, path)) = self.test_capture.clone() else {continue}; @@ -5289,6 +5331,12 @@ impl App { i += 2; continue; } + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + if let Some(path) = name.strip_prefix("studio-flow:") { + if let Err(error) = host_tools::studio::test_flow(path) { log!("studio flow: {error}"); } + i += 2; + continue; + } // capture:: write the presented frame to // every 5 s — a scripted run's screen, with no display // (or a locked one) needed. @@ -6230,6 +6278,8 @@ impl MatchEvent for App { // Its tools, grants and kernel tools, as installed (ADR 0004 §7). host_tools::script_app_installed(&id); } + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + self.drain_studio(cx); self.drain_hub(cx); self.drain_client_lines(cx); self.drain_module_upstream(); @@ -6350,6 +6400,8 @@ impl App { } fn shell_handle_event_inner(&mut self, cx: &mut Cx, event: &Event) { + #[cfg(all(unix, any(feature = "app-hub", native_mobile)))] + studio::apps::tick(cx, event); let studio_surface = studio::surface_geometry(cx); self.studio.event(cx, event, studio_surface); self.route_texture_readbacks(cx); diff --git a/crates/shell/src/studio/apps.rs b/crates/shell/src/studio/apps.rs new file mode 100644 index 000000000..62fbe2fd1 --- /dev/null +++ b/crates/shell/src/studio/apps.rs @@ -0,0 +1,1086 @@ +//! Developer app runner. Real visible Splash widgets, private app storage, and +//! Makepad's in-process instrument; no global widget-tree or remote server swap. +use super::{rgba, MAX_PNG}; +use crate::dev_mode::{self, DevTag}; +use makepad_widgets::makepad_platform::event::{ + ScrollEvent, TouchPoint, TouchState, TouchUpdateEvent, +}; +use makepad_widgets::{widget_tree::WidgetTree, *}; +use serde_json::{json, Value}; +use std::{ + cell::RefCell, + collections::{HashMap, VecDeque}, + fs::File, + io::Write, + path::PathBuf, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::Sender, + Arc, Mutex, OnceLock, + }, + time::{Duration, Instant}, +}; + +const REQUEST_TIMEOUT: Duration = Duration::from_secs(20); +const MAX_REQUESTS: usize = 16; +const MAX_INSTANCES: usize = 4; +static FOREGROUND: AtomicBool = AtomicBool::new(true); + +/// Constructed only by the host's admitted, immutable bundle staging path. +#[derive(Clone)] +pub struct OpenSpec { + pub instance_id: String, + pub app_id: Option, + pub owner: String, + pub dev_tag: DevTag, + pub source: String, + pub jail: PathBuf, + pub persistent: bool, + pub title: String, + pub storage_quota: u64, + pub instruction_budget: u64, + pub memory_bytes: u64, +} +pub enum Action { + Ready, + Input { + widget_id: String, + kind: String, + text: Option, + delta_y: Option, + }, + Inspect { + output: File, + }, + Close, +} +pub struct Request { + pub id: String, + pub owner: String, + pub dev_tag: DevTag, + pub instance_id: String, + pub action: Action, + pub reply: Sender>, +} +struct Pending { + request: Request, + started: Instant, + cancelled: Arc, +} +#[derive(Default)] +struct Queue { + specs: HashMap, + launches: VecDeque, + closes: VecDeque, + requests: VecDeque, + tokens: HashMap>, + request_instances: HashMap, + failures: HashMap, +} +fn queue() -> &'static Mutex { + static QUEUE: OnceLock> = OnceLock::new(); + QUEUE.get_or_init(Default::default) +} +fn granted(owner: &str, tag: &DevTag) -> bool { + let identity: Option = serde_json::from_str(owner).ok(); + let app = identity + .as_ref() + .and_then(|v| v["app"].as_str()) + .unwrap_or(owner); + dev_mode::tag_valid(tag) && dev_mode::grants_all(crate::host_tools::app_of_peer(app)) +} +fn valid(p: &Pending) -> Result<(), String> { + if !FOREGROUND.load(Ordering::Acquire) { + return Err("not_foreground".into()); + } + if p.cancelled.load(Ordering::Acquire) { + return Err("studio_cancelled".into()); + } + if !granted(&p.request.owner, &p.request.dev_tag) { + return Err("studio_grant_expired".into()); + } + if p.started.elapsed() >= REQUEST_TIMEOUT { + return Err("studio_timeout".into()); + } + Ok(()) +} +fn finish(p: Pending, result: Result) { + let id = p.request.id.clone(); + let _ = p.request.reply.send(result); + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + q.tokens.remove(&id); + q.request_instances.remove(&id); +} +pub fn stage_open(spec: OpenSpec) -> Result<(), String> { + if !FOREGROUND.load(Ordering::Acquire) { + return Err("not_foreground".into()); + } + if !granted(&spec.owner, &spec.dev_tag) { + return Err("studio_grant_expired".into()); + } + if spec.source.len() > 256 * 1024 || spec.source.trim().is_empty() { + return Err("studio_source_size".into()); + } + if spec.storage_quota > 1024 * 1024 + || spec.instruction_budget == 0 + || spec.instruction_budget > 5_000_000 + || spec.memory_bytes == 0 + || spec.memory_bytes > 16 * 1024 * 1024 + { + return Err("studio_resource_limits".into()); + } + // Initial script-app support deliberately has no external resource route. + // This is also enforced by the isolate; this check makes refusal explicit. + if spec.source.contains("{{assets}}") || spec.source.contains("http_resource(") { + return Err("studio_bundle_assets_unsupported".into()); + } + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + if q.specs.len() >= MAX_INSTANCES || q.specs.contains_key(&spec.instance_id) { + return Err("studio_busy".into()); + } + if spec.app_id.as_ref().is_some_and(|id| { + q.specs + .values() + .any(|open| open.app_id.as_ref() == Some(id)) + }) { + return Err("studio_app_already_open".into()); + } + q.launches.push_back(spec.instance_id.clone()); + q.specs.insert(spec.instance_id.clone(), spec); + SignalToUI::set_ui_signal(); + Ok(()) +} +pub fn take_launches() -> Vec { + queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .launches + .drain(..) + .collect() +} +pub fn take_closes() -> Vec { + queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .closes + .drain(..) + .collect() +} +pub fn launch_info(instance_id: &str) -> Option<(Option, String)> { + queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .specs + .get(instance_id) + .map(|s| (s.app_id.clone(), s.title.clone())) +} +pub fn submit(request: Request) -> Result<(), String> { + if !FOREGROUND.load(Ordering::Acquire) { + return Err("not_foreground".into()); + } + if !granted(&request.owner, &request.dev_tag) { + return Err("studio_grant_expired".into()); + } + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + if !q.specs.contains_key(&request.instance_id) { + if let Some((owner, tag, message)) = q.failures.get(&request.instance_id) { + if owner == &request.owner && tag == &request.dev_tag { + return Err(message.clone()); + } + } + return Err("studio_instance_not_found".into()); + } + let spec = &q.specs[&request.instance_id]; + if spec.owner != request.owner || spec.dev_tag != request.dev_tag { + return Err("studio_instance_scope".into()); + } + if q.tokens.len() >= MAX_REQUESTS || q.tokens.contains_key(&request.id) { + return Err("studio_busy".into()); + } + let token = Arc::new(AtomicBool::new(false)); + q.tokens.insert(request.id.clone(), token.clone()); + q.request_instances + .insert(request.id.clone(), request.instance_id.clone()); + q.requests.push_back(Pending { + request, + started: Instant::now(), + cancelled: token, + }); + SignalToUI::set_ui_signal(); + Ok(()) +} +/// Roll back an open that never reached its caller. Cleanup does not require a +/// still-valid grant, but the original owner and generation must match. +pub fn abandon(instance_id: &str, owner: &str, tag: &DevTag) -> Result<(), String> { + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + let Some(spec) = q.specs.get(instance_id) else { + return Ok(()); + }; + if spec.owner != owner || &spec.dev_tag != tag { + return Err("studio_instance_scope".into()); + } + if q.launches.iter().any(|id| id == instance_id) { + let spec = q.specs.remove(instance_id).unwrap(); + q.launches.retain(|id| id != instance_id); + drop(q); + if !spec.persistent { + let _ = std::fs::remove_dir_all(&spec.jail); + } + } else { + if !q.closes.iter().any(|id| id == instance_id) { + q.closes.push_back(instance_id.into()); + } + for (id, instance) in &q.request_instances { + if instance == instance_id { + if let Some(token) = q.tokens.get(id) { + token.store(true, Ordering::Release); + } + } + } + } + SignalToUI::set_ui_signal(); + Ok(()) +} +pub fn cancel(id: &str) { + if let Some(token) = queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .tokens + .get(id) + { + token.store(true, Ordering::Release); + } + SignalToUI::set_ui_signal(); +} +thread_local! { + static ROOTS: RefCell> = RefCell::new(HashMap::new()); + static CAPTURES: RefCell> = const { RefCell::new(Vec::new()) }; +} +struct Capture { + pending: Pending, + snapshot: Value, + frozen: makepad_widgets::view::ViewTextureSnapshot, + ticket: ReadbackTicket, +} + +script_mod! { + use mod.prelude.widgets.* + mod.widgets.StudioApp = set_type_default() do #(StudioApp::register_widget(vm)) { + width: Fill height: Fill flow: Overlay + capture := CachedView { + width: Fill height: Fill flow: Overlay + card := Splash { width: Fill height: Fill } + } + } +} +#[derive(Script, ScriptHook, Widget)] +pub struct StudioApp { + #[deref] + view: View, + #[rust] + spec: Option, + #[rust] + started: bool, + #[rust] + draws: usize, + #[rust] + timer: Timer, + #[rust] + pending: Option, + #[rust] + text_next: Option, + #[rust] + last_draw: Option, +} +pub fn create(vm: &mut ScriptVm, instance_id: &str) -> WidgetRef { + let spec = queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .specs + .get(instance_id) + .cloned(); + let value = script_eval!(vm,{use mod.widgets.* StudioApp{}}); + let root = WidgetRef::script_from_value(vm, value); + if let Some(mut app) = root.borrow_mut::() { + app.spec = spec; + } + ROOTS.with(|roots| roots.borrow_mut().insert(instance_id.into(), root.clone())); + root +} +pub fn shutdown(cx: &mut Cx, instance_id: &str) { + let root = ROOTS.with(|roots| roots.borrow_mut().remove(instance_id)); + if let Some(root) = root { + if let Some(mut app) = root.borrow_mut::() { + app.stop(cx); + } + } + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + q.specs.remove(instance_id); + q.launches.retain(|id| id != instance_id); + let mut rejected = Vec::new(); + let mut keep = VecDeque::new(); + for p in q.requests.drain(..) { + if p.request.instance_id == instance_id { + rejected.push(p) + } else { + keep.push_back(p) + } + } + q.requests = keep; + drop(q); + for p in rejected { + finish(p, Err("studio_closed".into())); + } + CAPTURES.with(|captures| { + for c in captures + .borrow() + .iter() + .filter(|c| c.pending.request.instance_id == instance_id) + { + c.pending.cancelled.store(true, Ordering::Release); + cx.cancel_texture_readback(c.ticket); + } + }); +} +/// Called before shell event routes. Readback workers also keep this token. +pub fn tick(cx: &mut Cx, event: &Event) { + match event { + Event::Pause | Event::Background => FOREGROUND.store(false, Ordering::Release), + Event::Resume | Event::Foreground => FOREGROUND.store(true, Ordering::Release), + _ => {} + } + let background = matches!(event, Event::Pause | Event::Background); + if background { + for token in queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .tokens + .values() + { + token.store(true, Ordering::Release); + } + } + let expired: Vec = queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .specs + .iter() + .filter(|(_, s)| !granted(&s.owner, &s.dev_tag)) + .map(|(id, _)| id.clone()) + .collect(); + for id in expired { + shutdown(cx, &id); + } + let mut expired_requests = Vec::new(); + { + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + let mut keep = VecDeque::new(); + for p in q.requests.drain(..) { + if let Err(error) = valid(&p) { + expired_requests.push((p, error)); + } else { + keep.push_back(p); + } + } + q.requests = keep; + } + for (p, error) in expired_requests { + finish(p, Err(error)); + } + CAPTURES.with(|captures| { + for c in captures.borrow().iter() { + if valid(&c.pending).is_err() { + cx.cancel_texture_readback(c.ticket); + } + } + }); +} +impl StudioApp { + fn stop(&mut self, cx: &mut Cx) { + self.view.splash(cx, ids!(card)).set_text(cx, ""); + cx.stop_timer(self.timer); + if let Some(p) = self.pending.take() { + finish(p, Err("studio_closed".into())); + } + if let Some(spec) = self.spec.take() { + if !spec.persistent { + let _ = std::fs::remove_dir_all(&spec.jail); + } + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + q.specs.remove(&spec.instance_id); + for (id, instance) in &q.request_instances { + if instance == &spec.instance_id { + if let Some(token) = q.tokens.get(id) { + token.store(true, Ordering::Release); + } + } + } + if !q.closes.contains(&spec.instance_id) { + q.closes.push_back(spec.instance_id.clone()); + } + let mut rejected = Vec::new(); + let mut keep = VecDeque::new(); + for p in q.requests.drain(..) { + if p.request.instance_id == spec.instance_id { + rejected.push(p); + } else { + keep.push_back(p); + } + } + q.requests = keep; + drop(q); + for p in rejected { + let error = queue() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .failures + .get(&spec.instance_id) + .map(|(_, _, e)| e.clone()) + .unwrap_or_else(|| "studio_closed".into()); + finish(p, Err(error)); + } + SignalToUI::set_ui_signal(); + } + self.view.visible = false; + self.view.redraw(cx); + } + fn start(&mut self, cx: &mut Cx) -> Result<(), String> { + let spec = self.spec.as_ref().ok_or("studio_instance_not_found")?; + if !granted(&spec.owner, &spec.dev_tag) { + return Err("studio_grant_expired".into()); + } + if !spec.jail.is_dir() { + return Err("studio_storage_missing".into()); + } + crate::glance_card::ensure_vocabulary(cx); + let card = self.view.splash(cx, ids!(card)); + if let Some(mut s) = card.borrow_mut() { + s.set_allow_net(false); + s.set_debug_name(&format!("studio/{}/main.splash", spec.instance_id)); + } + card.set_sandbox_dir(cx, Some(spec.jail.clone())); + card.set_storage_quota(cx, Some(spec.storage_quota)); + card.set_host_caps(cx, Vec::new()); + card.set_host_prompts(cx, false); + card.set_policy(cx, Some(Vec::new()), Some(spec.instruction_budget)); + card.set_memory_bytes(cx, Some(spec.memory_bytes as usize)); + card.set_text(cx, &spec.source); + let mut children = 0; + if let Some(s) = card.borrow() { + s.children(&mut |_, _| children += 1); + } + let running = card + .borrow_mut() + .and_then(|mut s| s.isolate_heap_key(cx)) + .is_some_and(makepad_widgets::splash_policy::may_run); + if children == 0 || !running { + return Err(format!("studio_eval_failed: {}/main.splash produced no root content or exceeded its admitted instruction budget",spec.instance_id)); + } + self.timer = cx.start_interval(0.08); + Ok(()) + } + fn dispatch(&mut self, cx: &mut Cx, event: &Event) { + let card = self.view.splash(cx, ids!(card)); + let vm = card + .borrow() + .and_then(|s| cx.script_ref_vm_id(&s.view.source)); + if let Some(vm) = vm { + widget_async::with_isolate(cx, vm, |cx| { + self.view.handle_event(cx, event, &mut Scope::empty()) + }); + } + } + fn instrument(&self, cx: &mut Cx) -> Value { + let tree = WidgetTree::default(); + let card = self.view.widget(cx, ids!(card)); + tree.set_root_widget(card.clone()); + let mut names: HashMap = HashMap::new(); + let widgets: Vec = tree + .snapshot(cx) + .into_iter() + .map(|w| { + let occurrence = names.entry(w.id.clone()).or_default(); + let selector = format!("{}@{}", w.id, *occurrence); + *occurrence += 1; + // Splash.text() is source code, not painted UI text. Never + // let source literals satisfy a functional visible-text check. + let text = if w.widget_type == "Splash" { + None + } else { + w.text + }; + let value = if w.widget_type == "Splash" { + None + } else { + w.value + }; + json!({ + "id":w.id,"selector":selector,"type":w.widget_type,"rect":[w.x,w.y,w.width,w.height], + "visible":w.visible,"enabled":w.enabled,"text":text,"value":value, + "checked":w.checked,"selected":w.selected}) + }) + .collect(); + let mut geometry: Value = + serde_json::from_str(&tree.geometry_json(cx)).unwrap_or(Value::Null); + // ScrollYView is a View template, so its Rust type alone cannot tell + // intentional viewport clipping from a layout bug. Read its inherited + // scroll_bars setting in its own VM without evaluating any app code. + let rows = tree.flat_tree(cx); + let mut name_counts = HashMap::::new(); + for row in &rows { + *name_counts.entry(row.name.clone()).or_default() += 1; + } + let vm = card + .borrow::() + .and_then(|s| cx.script_ref_vm_id(&s.view.source)); + let mut scroll_y = Vec::new(); + if let Some(vm) = vm { + widget_async::with_isolate(cx, vm, |cx| { + cx.with_vm(|vm| { + for row in &rows { + if name_counts.get(&row.name) != Some(&1) { + continue; + } + let source = tree.widget(WidgetUid(row.uid)).script_source(); + if source == ScriptObject::ZERO { + continue; + } + if let Some(bars) = vm + .bx + .heap + .value(source, id!(scroll_bars).into(), NoTrap) + .as_object() + { + if vm + .bx + .heap + .value(bars, id!(show_scroll_y).into(), NoTrap) + .as_bool() + == Some(true) + { + scroll_y.push(row.name.clone()); + } + } + } + }) + }); + } + if let Some(nodes) = geometry["widgets"].as_array_mut() { + for node in nodes { + let is_scroll = node["id"] + .as_str() + .is_some_and(|name| scroll_y.iter().any(|id| id == name)); + if is_scroll { + node["scroll_y"] = json!(true); + } + } + } + let checks = check_geometry(&geometry); + json!({"widgets":widgets,"geometry":geometry,"tree":tree.compact_dump(cx),"checks":checks}) + } + fn target(&self, cx: &mut Cx, id: &str) -> Result { + let snapshot = self.instrument(cx); + let widgets = snapshot["widgets"] + .as_array() + .ok_or("studio_snapshot_failed")?; + let candidate = select_widget(widgets, id)?; + let r = candidate["rect"].as_array().ok_or("studio_widget_rect")?; + let n = |i: usize| r[i].as_f64().unwrap_or(0.0); + if n(2) <= 0.0 || n(3) <= 0.0 { + return Err("studio_widget_not_drawn".into()); + } + let abs = dvec2(n(0) + n(2) * 0.5, n(1) + n(3) * 0.5); + Ok(abs) + } + fn scroll(&mut self, cx: &mut Cx, id: &str, dy: f64) -> Result<(), String> { + let abs = self.target(cx, id)?; + let window_id = cx.windows.id_iter().next().ok_or("studio_no_window")?; + self.dispatch( + cx, + &Event::Scroll(ScrollEvent { + window_id, + abs, + scroll: dvec2(0., dy), + modifiers: Default::default(), + is_mouse: false, + time: cx.seconds_since_app_start(), + handled_x: Default::default(), + handled_y: Default::default(), + phase: Default::default(), + }), + ); + self.view.redraw(cx); + Ok(()) + } + fn tap(&mut self, cx: &mut Cx, id: &str) -> Result<(), String> { + let abs = self.target(cx, id)?; + let window_id = cx.windows.id_iter().next().ok_or("studio_no_window")?; + // Same TouchUpdate hit/handler path as physical input, scoped to this + // visible app. Keep synthetic pointer capture out of the live fingers. + let fingers = std::mem::take(&mut cx.fingers); + for (offset, state) in [(0.0, TouchState::Start), (0.03, TouchState::Stop)] { + let time = cx.seconds_since_app_start() + offset; + self.dispatch( + cx, + &Event::TouchUpdate(TouchUpdateEvent { + time, + window_id, + modifiers: Default::default(), + touches: vec![TouchPoint { + uid: 0x53545544, + state, + abs, + time, + rotation_angle: 0.0, + force: 1.0, + radius: dvec2(1., 1.), + handled: Default::default(), + sweep_lock: Default::default(), + }], + }), + ); + } + cx.fingers = fingers; + self.view.redraw(cx); + Ok(()) + } + fn pump(&mut self, cx: &mut Cx) { + let Some(spec) = self.spec.as_ref() else { + return; + }; + if self.pending.is_none() { + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + if let Some(i) = q + .requests + .iter() + .position(|p| p.request.instance_id == spec.instance_id) + { + self.pending = q.requests.remove(i); + } + } + let Some(p) = self.pending.take() else { return }; + if let Err(e) = valid(&p) { + self.text_next = None; + finish(p, Err(e)); + return; + } + if self.draws < 2 { + self.pending = Some(p); + self.view.redraw(cx); + return; + } + if let Some(text) = self.text_next.take() { + self.dispatch( + cx, + &Event::TextInput(TextInputEvent { + input: text, + replace_last: false, + was_paste: true, + ..Default::default() + }), + ); + self.view.redraw(cx); + finish(p, Ok(json!({"ok":true}))); + return; + } + match &p.request.action { + Action::Ready => finish(p, Ok(json!({"instance_id":spec.instance_id,"ready":true}))), + Action::Input { + widget_id, + kind, + text, + delta_y, + } => { + if kind != "tap" && kind != "text" && kind != "scroll" { + finish(p, Err("studio_input_kind".into())); + return; + } + if kind == "text" && text.as_ref().is_none_or(|t| t.len() > 4096) { + finish(p, Err("studio_input_text".into())); + return; + } + let follow = (kind == "text").then(|| text.clone().unwrap()); + let result = if kind == "scroll" { + match delta_y { + Some(dy) if dy.is_finite() && dy.abs() <= 2000.0 => { + self.scroll(cx, widget_id, *dy) + } + _ => Err("studio_input_scroll".into()), + } + } else { + self.tap(cx, widget_id) + }; + match result { + Err(e) => finish(p, Err(e)), + Ok(()) => { + if let Some(text) = follow { + self.text_next = Some(text); + self.pending = Some(p); + } else { + finish(p, Ok(json!({"ok":true}))); + } + } + } + } + Action::Inspect { .. } => { + let snapshot = self.instrument(cx); + if snapshot.to_string().len() > 1024 * 1024 { + finish(p, Err("studio_snapshot_too_large".into())); + return; + } + let frozen = self + .view + .view(cx, ids!(capture)) + .borrow_mut() + .and_then(|mut view| view.take_texture_snapshot(cx)); + if let Some(frozen) = frozen { + match frozen + .texture() + .read_back(cx, ReadbackRequest { next_render: false }) + { + Ok(ticket) => CAPTURES.with(|c| { + c.borrow_mut().push(Capture { + pending: p, + snapshot, + frozen, + ticket, + }) + }), + Err(e) => finish(p, Err(format!("studio_readback: {e}"))), + } + self.view.redraw(cx); + } else { + self.pending = Some(p); + self.view.redraw(cx); + } + } + Action::Close => { + finish(p, Ok(json!({"closed":true}))); + self.stop(cx); + } + } + } +} +impl Widget for StudioApp { + fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) { + if !self.started { + self.started = true; + if let Err(e) = self.start(cx) { + let id = self + .spec + .as_ref() + .map(|s| s.instance_id.clone()) + .unwrap_or_default(); + error!("studio app {id}: {e}"); + if let Some(spec) = self.spec.as_ref() { + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + if q.failures.len() >= MAX_REQUESTS { + if let Some(old) = q.failures.keys().next().cloned() { + q.failures.remove(&old); + } + } + q.failures + .insert(id, (spec.owner.clone(), spec.dev_tag.clone(), e)); + } + self.stop(cx); + return; + } + } + if self.spec.is_none() { + return; + } + self.dispatch(cx, event); + self.pump(cx); + } + fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { + let card = self.view.splash(cx, ids!(card)); + let vm = card + .borrow() + .and_then(|s| cx.script_ref_vm_id(&s.view.source)); + let result = if let Some(vm) = vm { + widget_async::with_isolate(cx, vm, |cx| self.view.draw_walk(cx, scope, walk)) + } else { + self.view.draw_walk(cx, scope, walk) + }; + if result.is_done() { + self.draws += 1; + self.last_draw = Some(Instant::now()); + } + result + } +} +fn select_widget<'a>(widgets: &'a [Value], id: &str) -> Result<&'a Value, String> { + let mut matches = widgets.iter().filter(|w| { + (w["id"].as_str() == Some(id) || w["selector"].as_str() == Some(id)) + && w["visible"] == true + && w["enabled"] == true + }); + let first = matches.next().ok_or("studio_widget_missing_or_ambiguous")?; + if matches.next().is_some() { + return Err("studio_widget_missing_or_ambiguous".into()); + } + Ok(first) +} +/// Pure measured checks on Makepad's actual ink/clip rectangles; no model score. +fn check_geometry(geometry: &Value) -> Value { + let mut findings = Vec::new(); + if geometry["widgets"] + .as_array() + .is_none_or(|nodes| nodes.is_empty()) + { + findings.push(json!({"code":"empty_app","severity":"error"})); + } + if let Some(nodes) = geometry["widgets"].as_array() { + let by_index: HashMap = nodes + .iter() + .filter_map(|node| node["i"].as_i64().map(|i| (i, node))) + .collect(); + for node in nodes { + let n = |key: &str| node[key].as_f64().unwrap_or(0.0); + let kind = node["kind"].as_str().unwrap_or(""); + if (kind.contains("Label") || kind.contains("TextInput")) + && node["text"].as_str().is_some_and(|s| !s.is_empty()) + && (n("cw") + 1. < n("w") || n("ch") + 1. < n("h")) + && !scroll_explains_clip(node, &by_index) + { + findings.push(json!({"code":"text_clipped","severity":"error","id":node["id"]})); + } + if kind.contains("Button") && (n("w") < 44. || n("h") < 44.) { + findings + .push(json!({"code":"small_tap_target","severity":"warning","id":node["id"]})); + } + } + } + json!({"pass":!findings.iter().any(|f|f["severity"]=="error"),"findings":findings,"instrument":"makepad_widget_tree"}) +} +fn scroll_explains_clip(node: &Value, by_index: &HashMap) -> bool { + let n = |key: &str| node[key].as_f64().unwrap_or(0.0); + // A vertical scroll viewport never excuses horizontal truncation. + if n("cw") + 1.0 < n("w") && n("ch") > 0.0 { + return false; + } + let mut at = node["parent"].as_i64(); + for _ in 0..64 { + let Some(parent) = at.and_then(|i| by_index.get(&i).copied()) else { + return false; + }; + if parent["scroll_y"] == true { + let top = parent["cy"].as_f64().unwrap_or(0.0); + let bottom = top + parent["ch"].as_f64().unwrap_or(0.0); + let expected_top = n("y").max(top); + let expected_bottom = (n("y") + n("h")).min(bottom); + let expected_height = (expected_bottom - expected_top).max(0.0); + if (expected_height - n("ch")).abs() <= 1.0 + && (expected_height == 0.0 || (expected_top - n("cy")).abs() <= 1.0) + { + return true; + } + } + at = parent["parent"].as_i64(); + } + false +} +pub fn readback(cx: &mut Cx, result: TextureReadback) -> Option { + let capture = CAPTURES.with(|all| { + let mut all = all.borrow_mut(); + all.iter() + .position(|c| c.ticket == result.ticket) + .map(|i| all.remove(i)) + }); + let Some(capture) = capture else { + return Some(result); + }; + let bytes = match valid(&capture.pending).and_then(|_| rgba(&result, false)) { + Ok(bytes) => bytes, + Err(e) => { + finish(capture.pending, Err(e)); + return None; + } + }; + let reply = capture.pending.request.reply.clone(); + let id = capture.pending.request.id.clone(); + // `frozen` pins the exact app framebuffer until readback completion above. + drop(capture.frozen); + let task=cx.task_pool().submit(Lane::Heavy,move||{ + let mut p=capture.pending; + let outcome=(||{ + valid(&p)?; + let png=Cx::encode_rgba_as_png(result.width as u32,result.height as u32,&bytes).map_err(|e|format!("studio_png: {e:?}"))?; + if png.len()>MAX_PNG{return Err("studio_png_too_large".into());} + valid(&p)?; + let Action::Inspect{output}=&mut p.request.action else{return Err("studio_capture_kind".into());}; + output.write_all(&png).and_then(|_|output.flush()).map_err(|e|e.to_string())?; + valid(&p)?; + Ok(json!({"instance_id":p.request.instance_id,"width":result.width,"height":result.height,"settled":false,"snapshot":capture.snapshot})) + })();finish(p,outcome); + }); + match task { + Ok(task) => task.detach(), + Err(e) => { + let _ = reply.send(Err(format!("studio_worker: {e}"))); + let mut q = queue().lock().unwrap_or_else(|e| e.into_inner()); + q.tokens.remove(&id); + q.request_instances.remove(&id); + } + } + None +} +#[cfg(test)] +mod tests { + use super::*; + /// Records the actual StudioApp widget through its cached-view draw path. + /// This catches incompatible View flags and stack imbalance; GPU pixels + /// remain the separate device acceptance test. + #[test] + fn studio_widget_records_a_cached_frame_without_background_or_stack_conflicts() { + let mut cx = Cx::new(Box::new(|_, _| {})); + let root = cx.with_vm(|vm| { + makepad_widgets::script_mod(vm); + super::script_mod(vm); + let value = script_eval!(vm,{use mod.widgets.* StudioApp{}}); + WidgetRef::script_from_value(vm, value) + }); + root.splash(&mut cx, ids!(card)) + .set_text(&mut cx, "Label{text: \"Studio render regression\"}"); + let cache = root.view(&mut cx, ids!(capture)); + assert!(cache.borrow().unwrap().texture_caching); + assert!(!cache.borrow().unwrap().show_bg); + record_frame(&mut cx, &root); + } + fn record_frame(cx: &mut Cx, root: &WidgetRef) { + let pass = DrawPass::new(cx); + pass.set_size(cx, dvec2(390., 600.)); + let mut list = DrawList2d::new(cx); + let event = DrawEvent::default(); + let mut draw = CxDraw::new(cx, &event); + let mut cx = Cx2d::new(&mut draw); + cx.begin_pass(&pass, Some(1.)); + list.begin_always(&mut cx); + cx.begin_root_turtle(dvec2(390., 600.), Layout::flow_overlay()); + let mark = cx.unwind_mark(); + root.draw_walk_all(&mut cx, &mut Scope::empty(), Walk::fixed(390., 600.)); + assert!(cx.unwind_mark().is_balanced_with(&mark)); + cx.end_pass_sized_turtle(); + list.end(&mut cx); + cx.end_pass(&pass); + } + #[test] + fn production_shutdown_parks_the_vm_reclaims_scratch_and_allows_next_app_draw() { + use makepad_app_module::{ + AppModule, InstanceHandles, InstanceScope, ModuleWindows, ReplySink, Viewport, + }; + let instance = format!("shutdown-test-{}", uuid::Uuid::new_v4()); + let jail = std::env::temp_dir().join(&instance); + std::fs::create_dir(&jail).unwrap(); + let spec = OpenSpec { + instance_id: instance.clone(), + app_id: None, + owner: "system".into(), + dev_tag: DevTag { + profile_id: "test-only".into(), + since: 0, + }, + source: String::new(), + jail: jail.clone(), + persistent: false, + title: "test".into(), + storage_quota: 0, + instruction_budget: 5_000_000, + memory_bytes: 16 * 1024 * 1024, + }; + // No grant is minted: the test seats an internal spec solely to exercise + // the production factory/shutdown boundary and disposal of its jail. + queue().lock().unwrap().specs.insert(instance.clone(), spec); + let mut cx = Cx::new(Box::new(|_, _| {})); + let storage = cx.storage(&instance); + let parts = cx.with_vm(|vm| { + makepad_widgets::script_mod(vm); + let module = &super::super::module::STUDIO_MODULE; + module.register(vm); + let open = module + .open_schema() + .validate(&json!({"instance_id":instance}).to_string(), &[]) + .unwrap(); + module.create( + vm, + open, + InstanceHandles { + scope: InstanceScope::new(1, 1), + storage, + viewport: Viewport { + size: dvec2(390., 600.), + }, + replies: ReplySink::pair().0, + windows: ModuleWindows::new(false), + }, + ) + }); + let card = parts.root.splash(&mut cx, ids!(card)); + card.set_sandbox_dir(&mut cx, Some(jail.clone())); + card.set_text(&mut cx, "Label{text: \"Before shutdown\"}"); + record_frame(&mut cx, &parts.root); + let shutdown = parts.shutdown; + // Invoke the actual FnOnce while Cx's VM is taken, exactly the boundary + // that used to panic. No ModuleHost catch can turn a panic into success. + cx.with_vm(|vm| shutdown(vm)); + assert!(!jail.exists()); + assert!(!ROOTS.with(|roots| roots.borrow().contains_key(&instance))); + assert!(!queue().lock().unwrap().specs.contains_key(&instance)); + assert!(parts.root.borrow::().unwrap().spec.is_none()); + let next = cx.with_vm(|vm| { + let value = script_eval!(vm,{use mod.widgets.* StudioApp{}}); + WidgetRef::script_from_value(vm, value) + }); + next.splash(&mut cx, ids!(card)) + .set_text(&mut cx, "Label{text: \"After shutdown\"}"); + record_frame(&mut cx, &next); + queue().lock().unwrap().closes.retain(|id| id != &instance); + } + #[test] + fn scoped_selectors_refuse_ambiguous_hidden_and_disabled_targets() { + let rows = vec![ + json!({"id":"complete","selector":"complete@0","visible":true,"enabled":true}), + json!({"id":"complete","selector":"complete@1","visible":true,"enabled":true}), + json!({"id":"delete","selector":"delete@0","visible":false,"enabled":true}), + json!({"id":"blocked","selector":"blocked@0","visible":true,"enabled":false}), + ]; + assert!(select_widget(&rows, "complete").is_err()); + assert_eq!(select_widget(&rows, "complete@1").unwrap(), &rows[1]); + assert!(select_widget(&rows, "delete@0").is_err()); + assert!(select_widget(&rows, "blocked@0").is_err()); + assert!(select_widget(&rows, "outside_instance").is_err()); + } + #[test] + fn missing_geometry_never_passes_inspection() { + assert_eq!(check_geometry(&json!({"widgets":[]}))["pass"], false); + } + #[test] + fn scroll_viewport_clipping_is_distinct_from_a_short_row_or_horizontal_overflow() { + let mut report = json!({"widgets":[ + {"i":0,"parent":-1,"id":"list","kind":"View","scroll_y":true,"cy":0,"ch":100}, + {"i":1,"parent":0,"id":"row","kind":"Label","text":"task","y":90,"w":100,"h":20,"cy":90,"cw":100,"ch":10}]}); + assert_eq!(check_geometry(&report)["pass"], true); + report["widgets"][1]["ch"] = json!(5); + assert_eq!(check_geometry(&report)["pass"], false); + report["widgets"][1]["ch"] = json!(10); + report["widgets"][1]["cw"] = json!(50); + assert_eq!(check_geometry(&report)["pass"], false); + } + #[test] + fn instrument_checks_report_real_clipping_and_small_controls() { + let report = check_geometry(&json!({"widgets":[ + {"id":"title","kind":"Label","text":"Tasks","w":100,"h":20,"cw":40,"ch":20}, + {"id":"add","kind":"Button","w":30,"h":30,"cw":30,"ch":30}]})); + assert_eq!(report["pass"], false); + assert_eq!(report["findings"].as_array().unwrap().len(), 2); + } + #[test] + fn empty_or_complete_text_is_not_clipped() { + assert_eq!( + check_geometry( + &json!({"widgets":[{"id":"title","kind":"Label","text":"Tasks","w":100,"h":20,"cw":100,"ch":20}]}) + )["pass"], + true + ); + } +} diff --git a/crates/shell/src/studio/mod.rs b/crates/shell/src/studio/mod.rs index 211d159c9..fb4e5638e 100644 --- a/crates/shell/src/studio/mod.rs +++ b/crates/shell/src/studio/mod.rs @@ -1,5 +1,9 @@ //! Foreground-only, unpublished L0 previews. No app policy or live app storage //! is inherited: each render receives a disposable, zero-quota isolate. +#[cfg(all(unix, any(feature = "app-hub", native_mobile)))] +pub mod apps; +#[cfg(all(unix, any(feature = "app-hub", native_mobile)))] +pub mod module; use crate::dev_mode::DevTag; use makepad_widgets::*; use serde_json::Value; diff --git a/crates/shell/src/studio/module.rs b/crates/shell/src/studio/module.rs new file mode 100644 index 000000000..5c422865c --- /dev/null +++ b/crates/shell/src/studio/module.rs @@ -0,0 +1,26 @@ +//! Internal native wrapper for an admitted developer script instance. +use makepad_widgets::*; +use makepad_app_module::{AppModule, ExecOutcome, InstanceHandles, InstanceParts, + OpenArgKind, OpenSchema, ServiceExecutor, ValidatedOpen, + makepad_ai_services::wire::{ServiceCall, ServiceManifest, ToolResult}}; +pub struct StudioModule; +pub static STUDIO_MODULE: StudioModule = StudioModule; +impl AppModule for StudioModule { + fn id(&self) -> &'static str { "studio-app" } + fn label(&self) -> &'static str { "App Studio" } + fn register(&self, vm: &mut ScriptVm) { super::apps::script_mod(vm); } + fn open_schema(&self) -> OpenSchema { OpenSchema::new(1).arg("instance_id", OpenArgKind::Text, true) } + fn capabilities(&self) -> &'static [&'static str] { &[] } + fn create(&self, vm: &mut ScriptVm, open: ValidatedOpen, _handles: InstanceHandles) -> InstanceParts { + let id = open.text("instance_id").unwrap_or_default().to_owned(); + InstanceParts { root: super::apps::create(vm, &id), executor: Box::new(StudioExecutor), + shutdown: Box::new(move |cx| cx.with_cx_mut(|cx| super::apps::shutdown(cx, &id))) } + } +} +struct StudioExecutor; +impl ServiceExecutor for StudioExecutor { + fn manifest(&self) -> ServiceManifest { ServiceManifest::new("studio-app", "App Studio", "An isolated local developer app.") } + fn execute(&mut self, _cx: &mut Cx, call: &ServiceCall) -> ExecOutcome { + ExecOutcome::Done(ToolResult::unavailable(&call.call_id, "Use the scoped studio tools")) + } +} diff --git a/crates/shell/src/system_chat/session.rs b/crates/shell/src/system_chat/session.rs index 40a614d5c..1b6297dbb 100644 --- a/crates/shell/src/system_chat/session.rs +++ b/crates/shell/src/system_chat/session.rs @@ -113,7 +113,7 @@ impl SystemHost for ShellSystemHost { // BEFORE adding this connection-owned host set. No baseline kernel // tool grant is needed, and external turns cannot inherit this set. if crate::host_tools::studio::SUPPORTED && crate::dev_mode::grants_all(crate::host_tools::SYSTEM) { - decls.push(crate::host_tools::studio::declaration(crate::host_tools::SYSTEM)); + decls.extend(crate::host_tools::studio::declarations(crate::host_tools::SYSTEM)); } decls } diff --git a/docs/adr/0006-app-studio-on-the-phone.md b/docs/adr/0006-app-studio-on-the-phone.md index fbef59f5b..679da8e92 100644 --- a/docs/adr/0006-app-studio-on-the-phone.md +++ b/docs/adr/0006-app-studio-on-the-phone.md @@ -1,45 +1,58 @@ # ADR 0006: App Studio on the phone - **Date:** 2026-10-02 -- **Status:** Accepted (2026-10-03). Milestone 1 is in progress; device acceptance is still required. Milestones 2–5 remain unimplemented. +- **Status:** Accepted (2026-10-03). Implementation in progress: a fresh model-authored app passed physical-device functional checks; portrait visual review passed; fault injection and the full image/workflow pipeline remain pending. - **Scope:** How an agent on the phone turns an image (a generated design or a screenshot of an existing app) into an OctoSense app or glance card, looks at its own result and improves it, entirely on the phone, at first in developer mode. Covers the inputs, the in-process renderer, the checks, the rules that can change without a build, the tools agents get, and what of the OctoScript App Design Flow moves to the phone. There is no compile in the loop and no Mac. - **Relates to:** [ADR 0002](0002-event-driven-app-agents.md) (§6 the `card_render` and `card_critique_payload` toolbox tools; §7 a card is rendered, critiqued and revised before it is published; milestone M6); [ADR 0004](0004-native-apps-hosting-and-peers.md) (app agents, host tools, approvals, §13 developer mode); [ADR 0005](0005-app-contract.md) (the app contract and bundles); [Home ADR 0004](home/0004-system-apps-are-contained-script-apps.md) (contained script apps); [Home ADR 0005](home/0005-settings-octoscript-controller.md) and [Home ADR 0006](home/0006-builtin-settings.md) (Settings and its developer options); App Hub's [`card-studio`](https://github.com/OctoSense-org/OctoSense-App-Hub/tree/main/crates/card-studio) crate and [skill](https://github.com/OctoSense-org/OctoSense-App-Hub/tree/main/skills/card-studio); the [OctoScript App Design Flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow) (`flows/image-to-card`, `flows/image-lib`); octos issue #1149, closed, which added the `image_generation` stub (its backend needs a new issue). ## Implementation status -Milestone 1 is in progress. Its first implementation is a developer-only -`studio.render` tool for L0 glance previews on Unix hosts, including Android. -The system conversation uses the workspace confirmed by the kernel; an app -agent uses its own account/context workspace. The model supplies relative -`source_path`, optional `data_path`, and optional `dark`; it cannot select a -workspace, output destination or rendering width. Source is limited to 16 KiB -and data JSON to 32 KiB. The result is `{path, width, height, settled}`, with a -relative PNG path for a separate `view_image` call and dimensions in pixels. - -This slice uses the real glance width, theme and height bounds, with disposable -storage, no network or host-service grants, and foreground/cancellation checks. -Bundle screens, images and interactive chat are not supported by this first -renderer. The render deadline is 20 seconds, inside the host wait and kernel -call deadline. It does not implement checks, comparison, the editable studio -runner, bundle installation, image generation or ROM capture sessions. - -The Android build wrapper accepts `--dev-mode`, separately from the -`--development` signing choice; `--package-name` selects a standalone Home -test identity. Python tests cover build plans and receipt guards. Renderer and -routing validation, followed by OnePlus 6 acceptance and model image delivery, -are still required before milestone 1 is complete. The [device probe](../../tools/studio-device-probe.py) exercises denial, light/dark PNGs and background cancellation in an already installed, debuggable studio test package; it does not test model image delivery. The remaining sections -describe the full accepted design, including work for milestones 2–5. +The implementation now covers L0 glance rendering and a local, offline script-app authoring path. A fresh Task Planner authored by **DeepSeek V4 Flash passed 129 instrument-driven tool calls on a physical OnePlus 6**. This establishes the functional path from a new design to a working local app. Portrait app and keyboard visual review also passed; fault injection remains pending. A fixture PNG, a passing unit test or a successful build alone would not establish that result. + +The shell registers the following tools only while developer mode covers the calling system or app agent: + +| Implemented tool | Current behavior | +| --- | --- | +| `studio.render` | L0 glance source/data → PNG, using the real glance width and height bounds. Arguments are relative `source_path`, optional `data_path` and `dark`. | +| `studio.bundle_check` | Copies a workspace-relative bundle into host-private staging, computes its digest and admits that copy. The author's source is unchanged. | +| `studio.open` | Opens a visible script app from either `bundle_path` (disposable preview) or `app_id` (local developer install with persistent state). | +| `studio.inspect` | Captures app pixels and scoped Makepad diagnostics. Returns PNG `path`, compact `snapshot.widgets`/check summary, and a full diagnostic JSON `snapshot_path`; optional `offset` follows `next_offset` pagination. | +| `studio.input` | Sends tap, text or scroll events to visible, enabled widget selectors within the caller's own instance. | +| `studio.close` | Closes that instance; preview state is discarded and installed state is retained. | +| `studio.install` | Records a local developer install under `dev.studio.*`, available from Home while its `DevTag` is valid. | + +Inspection replies are bounded to 3,800 UTF-8 bytes so actionable selectors survive the pinned kernel's 4 KiB model-output limit. Pages retain exact selectors, report `total_widgets` and `next_offset`, and explicitly mark shortened text/value fields. Pass the returned selector to `studio.input`; a painted label is not an identity. Each page observes the current UI. The full original result is written as pretty-printed JSON (at most 1 MiB) in the caller's workspace, retaining all widget rectangles, geometry, tree and findings. `read_file` can read bounded line ranges through `snapshot_path`. Both artifacts are removed if completion fails, the grant expires or the workspace scope changes. + +Tools are not shareable or available to background turns. The relay supplies the trusted caller and workspace: the system session's kernel-confirmed root, or the app peer's confirmed root and own conversation context. The model supplies relative input paths and instance ids, never another app's identity or an output directory. `studio.install` and `studio.input` are `act` tools. A private install receipt binds the authoring app/account/session/context, developer activation, manifest and digest. Launch revalidates it; revocation closes the app and removes launcher availability. A second instance of the same installed app is refused to prevent concurrent state writes. + +The first script-app path accepts `main.splash` with storage-only permissions: no app agent, accounts, external network, host services or in-screen resource loader. An original launcher icon may be present in the bundle; rich artwork routes are still future work. Staging permits 128 files/directories, eight directory levels, 2 MiB total, 512 KiB per file and 64 KiB for `main.splash`. The manifest's resolved ceilings are at most 1 MiB app storage, five million script instructions and a 16 MiB heap. These limits are installed before evaluation. Private preview writes are allowed for functional tests and discarded on close; developer installs write only their own persistent jail. No signed catalog or publisher identity is fabricated, and local admission does not count as App Hub publishing approval. + +L0 glance rendering remains narrower: 16 KiB source, 32 KiB JSON data, zero storage quota, no chat or image resources. It reports `settled: true` after three matching pixel samples with shaders ready. Interactive app inspection returns `settled: false`, since a current frame does not prove an arbitrary script has stopped changing. PNGs are limited to 5 MiB. UI/render requests have a 20-second deadline inside a 25-second host wait and the normal 30-second kernel call deadline; cancellation, foreground state and the developer grant are checked through readback and encoding. + +Android still has no HTTP remote instrument at the pinned Makepad revision. Studio calls Makepad's underlying widget instrument **in process**, rooted only at its own app; it does not inspect other apps or the shell's controls. Current checks report empty geometry, clipped text and small buttons. The shared `card-studio` check/critique port, image comparison, `mod.studio` toolbox adapter, editable workflow templates, image generation and capture sessions remain unimplemented. + +Start at [host tools and admission](../../crates/shell/src/host_tools/studio_bundles.rs), then [the visible app runner](../../crates/shell/src/studio/apps.rs) and [the bilingual architecture walkthrough](../architecture-walkthrough.md#follow-app-studio-from-the-agent-to-a-working-app). The [fresh-app brief](../studio/task-planner-brief.md) and [functional device harness](../../tools/studio-flow-device-test.py) define acceptance against a newly authored project. The older [L0 device probe](../../tools/studio-device-probe.py) covers denial, palette/PNG behavior and background cancellation only. The Android wrapper's `--dev-mode` enables developer options; `--development` selects signing and `--package-name` selects an isolated test identity. The physical functional result is recorded below, separately from build and unit results. + +### Physical functional evidence + +The `adr0006-task-planner-acceptance-v2` run used the isolated `dev.makepad.octosense.studio` test package. Its generation receipt identifies `deepseek/deepseek-v4-flash` and binds the model-authored design, manifest, `main.splash` and icon to bundle digest `7892501e8b69d752b002130879dde11af8f7c7e7b7e620a6854b97892fc95f3b`. The model called `view_image`; its tool result reported `shown_to_model: true`, and the model continued its turn. This records image delivery and continuation, not an independent guarantee that every visual detail was understood. + +The 129-call harness reported `functional_passed_visual_review_required`. It exercised task entry, completion and All/Active/Done filters; discarded preview state; verified separate installed storage; and retained installed state through close/reopen and process restart. It also verified exact Chinese text after native input and reopening, and scrolled through seven additional rows. The harness made no direct source or app-storage writes. + +Subsequent manual review passed portrait readability, long-title wrapping, bottom-row reachability, Chinese glyphs, app contrast and absence of unintended app overlap. An Android-wide screenshot also confirmed keyboard presentation and correct viewport shrinkage; app-texture PNGs alone exclude keyboard pixels. Generous spacing leaves about one row above the keyboard. A shell floating overlay and low-contrast status bar remain separate observations; rotation/landscape was not established. See the [validation report](../studio/oneplus6-validation.md) for provenance and limits. Malformed saved JSON preservation and reported save failures were **not tested** because they require isolated fault injection. The full image-generation/refinement workflow and `mod.studio` adapter remain unimplemented. + +The remaining decision describes the complete intended design. Some local app support from milestone 3 is implemented ahead of the shared checks and workflow adapter; the milestones as a whole are not complete. ## Context -Making an OctoSense app or card from a picture works today only on a desktop: +At the time of the proposal, the image-driven app/card flow was provided by these desktop tools. The phone implementation above does not yet port that image pipeline: - **The OctoScript App Design Flow** is about 12,150 lines of Python tooling (about 16,000 with its tests), plus per-app examples. It runs on a Mac with cargo, Makepad Studio and App Hub's `card-host --remote`, and also needs Python 3.12 with numpy, OpenCV, Pillow and fontTools, Swift and Node. Its two image paths are: - the Sketch kit (`flows/kits/sketch`), which needs `sketchtool`, Swift and a licensed kit, and has its own gates (`gate_structure`, `gate_composition`, `gate_fill`, `gate_visual`); - image-to-card (`flows/image-to-card`, `flows/image-lib`): crop scenes from an image, read their text with Apple Vision OCR (through Swift), map regions to widgets, generate L0, render and compare. Its gate is `flows/image-lib/gate.py`: native geometry, OCR text, ink and colour checks, and the visual-review receipt. - **App Hub's `card-studio`** renders a card in a hidden `card-host --remote`, grabs a PNG, runs measured checks (hidden, clipped or truncated text, overflow, overlap, empty or failed states, fit, lint, realize) and builds a critique payload for a vision model. The checks and the payload are plain Rust that depends only on serde and serde_json. They read the remote instrument's snapshot and dump formats and card-host's widget ids, and the critique prompt is written for an L0 glance card. The rendering needs the remote instrument and a separate process. -On the phone none of that runs: +The desktop process/HTTP pipeline cannot run unchanged on the phone: - There is no Python, and no Apple Vision for OCR. - Makepad's remote instrument is compiled out on Android (`platform/src/remote.rs`, the `target_os = "android"` stub), and there is no second process to host a card. @@ -96,7 +109,7 @@ The Sketch kit, its gates included, stays a desktop tool and is not ported. ### 3. The output is a glance card or a contained script app - **A glance card:** L0 only, as for every generated card, published through `glance.publish` as the app. -- **A script app:** a bundle per [ADR 0005](0005-app-contract.md), with `main.splash`, Octoscript controllers, its kit and its data. In developer mode it installs locally as a developer bundle. App Hub installs only from the signed catalog today, so this adds a local install path: a developer bundle carries developer mode's `DevTag`, runs only while that tag is valid, and is never offered through the catalog. Publishing goes through App Hub's usual gate and signing. +- **A script app:** a bundle per [ADR 0005](0005-app-contract.md), with `main.splash`, Octoscript controllers, its kit and its data. In developer mode it installs locally as a developer bundle. App Hub's ordinary install path remains tied to its signed catalog. Studio now adds a separate local path for offline, storage-only `main.splash` apps: a developer bundle carries developer mode's `DevTag`, runs only while that tag is valid, and is never offered through the catalog. Broader bundle support remains planned. Publishing goes through App Hub's usual gate and signing. A cloned app is the person's own prototype. Section 9 says what may and may not be copied. @@ -113,7 +126,7 @@ It then: 2. reads the pixels back, one readback at a time (makepad's 32 MiB readback budget is shared by every pending readback), composes the premultiplied RGBA onto the surface's backdrop and writes an opaque PNG; 3. builds the widget-tree capture the checks read, in the capture format `card-studio` reads (section 5). -There is no remote instrument, no `card-host` process and no window grab. Studio renders never go through the glance store, so its publish rate and card limits do not apply. Every preview uses a no-side-effect policy established before source evaluation: a fresh disposable storage jail (or a bounded snapshot copied into it), no live app/account files, and disabled external network access. Bundle artwork is staged by the host through a bounded local asset path; service data comes from explicit preview fixtures. Mutating `host.request` calls are refused, but that gate alone is insufficient: Splash’s direct `fs.write`/`append`/`remove` and network APIs must remain confined or disabled too. Preview state is discarded on completion or cancellation. The target’s layout and lowering are reused; its live storage and service authority are not. +There is no HTTP remote server, `card-host` process or whole-window grab. The local app path uses Makepad's in-process widget instrument, scoped to its own root. Studio renders never go through the glance store, so its publish rate and card limits do not apply. Every preview uses a policy that confines side effects before source evaluation: a fresh disposable storage jail (or a bounded snapshot copied into it), no live app/account files, and disabled external network access. Bundle artwork is staged by the host through a bounded local asset path; service data comes from explicit preview fixtures. Mutating `host.request` calls are refused, but that gate alone is insufficient: Splash’s direct `fs.write`/`append`/`remove` and network APIs must remain confined or disabled too. A glance render discards its state on completion or cancellation. An interactive preview may write within its disposable jail for functional tests, then discards that state on close; an installed developer app keeps its own private state. The target’s layout and lowering are reused; its live account storage and service authority are not. ### 5. Checks and comparison are Rust, shared by phone and desktop @@ -156,10 +169,11 @@ The system toolbox's runner becomes the studio runner. ### 7. Agents drive the loop through host tools -Host tools on the shell: +The complete design includes these host tools; the implementation table above identifies the current subset: - `studio.render`, `studio.check`, `studio.compare`, `studio.critique_payload` and `studio.bundle_check`, declared `risk: read`; -- `studio.install`, for a developer bundle, declared `risk: act`, because it changes what is installed. +- `studio.install`, for a developer bundle, declared `risk: act`, because it changes what is installed; +- the implemented interactive app path also has `studio.open`, `studio.inspect` and `studio.close` (`read`), plus `studio.input` (`act`) for stateful behavior tests within the caller's own app. None is a background tool, because a render needs Home in front (section 10). Each registers a call timeout that covers settle and readback; octos's default is 30 s. @@ -190,7 +204,7 @@ An octos change adds a media field to `peer/tool/result`, mapped onto octos's in - **Screenshots can hold personal data.** They stay in the project's folder and are sent only to the model provider the person chose. Deleting a project deletes its folder and purges the conversation sessions that viewed its images, because octos keeps viewed images as message media. - **Other apps are captured only in an approved session** started by the person (section 2). A session is visible on screen, never runs in the background, and leaves secure windows out. -- **A clone is for the person's own use or as a prototype.** Studio projects never ship logos, brand marks, icons or copyrighted images in a bundle. The studio records where every image came from, and `studio.bundle_check` refuses any raster asset derived from a captured screenshot. The studio regenerates or draws artwork instead. Publishing a clone needs the publisher's own assets and goes through App Hub's review. +- **A clone is for the person's own use or as a prototype.** Studio projects never ship logos, brand marks, icons or copyrighted images in a bundle. The planned image workflow records where every image came from and adds a provenance gate refusing raster assets derived from captured screenshots. The current `studio.bundle_check` checks local developer admission and resource limits; it does not establish image provenance. The studio regenerates or draws artwork instead. Publishing a clone needs the publisher's own assets and goes through App Hub's review. - **Generated cards stay L0** and script apps stay contained. The studio widens no policy. ### 10. Limits on the device @@ -215,9 +229,9 @@ An octos change adds a media field to `peer/tool/result`, mapped onto octos's in ## Milestones -1. **Render on the phone.** Renderer, readback router, `studio.render`, developer build. Verified on a device: pixels and orientation, settle timing, cost of the repaint, the readback when Home leaves the front, and an image reaching DeepSeek V4 Flash (`shown_to_model`). +1. **Render on the phone.** Renderer, readback router, `studio.render`, developer build. Acceptance requires device evidence for: pixels and orientation, settle timing, cost of the repaint, the readback when Home leaves the front, and an image reaching DeepSeek V4 Flash (`shown_to_model`). 2. **Check and compare.** `card-studio` as a library, the new checks, comparison with a screenshot, the OCR choice. End-to-end: a screenshot of an app becomes a glance card refined in three iterations. -3. **Rules in Octoscript.** The studio runner and `mod.studio`; the `card-refine` template; image-to-card code generation; script-app output and developer install. +3. **Rules in Octoscript.** The studio runner and `mod.studio`; the `card-refine` template; image-to-card code generation. Local offline script-app output, instrumentation and developer install now have a Rust path; the template/adapter port and fault injection remain pending. Fresh-app functional and portrait visual checks have passed on OnePlus 6. 4. **Images in.** The image-generation backend; capture sessions on the ROM; clone a multi-screen app from captured screens. 5. **Normal mode.** Approvals for capture and install; MediaProjection on stock Android; background rendering if it is solved. @@ -225,7 +239,7 @@ An octos change adds a media field to `peer/tool/result`, mapped onto octos's in - One implementation of checks and comparison for desktop and phone, so a card judged on a Mac and on a phone gets the same report. - The design flow's Python shrinks to the Sketch kit and desktop-only tools as the image-to-card parts move to Octoscript and Rust. -- The shell gains a renderer that can show any card offscreen. It is a new attack surface for script cards, which is why studio renders run without side effects. +- The shell gains a renderer that can show any card offscreen. It is a new attack surface for script cards, which is why preview effects stay in disposable storage with external services disabled. - Rules and templates change on the phone without a build, under the toolbox's digest and budget rules. - Developer mode gains the `studio.*` tools, with person-only approvals for capture sessions and any future guarded input that changes external state. - ADR 0002 is amended (its amendment of 2026-10-03): its §6 toolbox tools `card_render` and `card_critique_payload` become the `studio.*` host tools, and its §7 rule that the phone evaluates only while charging does not apply to renders the person starts. diff --git a/docs/architecture-walkthrough.md b/docs/architecture-walkthrough.md index 6b6249c91..46b2988fd 100644 --- a/docs/architecture-walkthrough.md +++ b/docs/architecture-walkthrough.md @@ -279,19 +279,42 @@ Read `mpsc` as a many-sender mailbox, `oneshot` as one correlated answer, and `w The broker's synchronous `bind`/`host_request` wrappers wait for channel replies: do not call such blocking interfaces from a rendering callback. Async model I/O can overlap across sessions; synchronous filesystem work still needs the existing worker boundary. One peer per app and account is an identity rule. The number of tasks and worker threads follows the runtimes and active operations above. -### A foreground render crosses three execution boundaries +### Follow App Studio from the agent to a working app -The first App Studio implementation adds `studio.render`, a shell-owned tool declared in [host_tools/studio.rs](../crates/shell/src/host_tools/studio.rs). The relay offers it only while developer mode covers the caller. It is not shareable, does not install a bundle, and does not add tools to a script manifest. A system call uses the workspace returned when its kernel session opens; an app call uses its broker-bound workspace, narrowed to `contexts//` for a human conversation. The caller supplies a relative `source_path`, optional `data_path`, and optional `dark` boolean. It cannot supply another app identity or an output directory. +App Studio adds shell-owned tools to the existing system or app agent while developer mode covers that caller. Their [declarations and executor](../crates/shell/src/host_tools/studio.rs) use the same relay, audit and cancellation path as other host tools. A system call uses the workspace confirmed by `session/open`; an app call uses the broker-confirmed peer workspace, narrowed to its own `contexts//` for a human conversation. Arguments cannot replace that identity or choose an output directory. -Follow one call from [the executor](../crates/shell/src/host_tools/studio.rs) to [the renderer](../crates/shell/src/studio/mod.rs): +An agent can write a fresh `manifest.json` and `main.splash` with its normal file tools, then follow this sequence: -1. A host worker opens bounded source/data files through the workspace directory descriptor. Symlinks and `..` cannot escape that directory. It creates a unique output file there, queues a `RenderJob`, and waits for a correlated reply. -2. Makepad's UI loop checks the current developer grant, prepares the L0 card, and draws a separate offscreen pass. It uses the live glance lowering with an explicit light/dark argument, the window width minus 40 points, and measured height clamped to 72–440 points. It never publishes a tile or changes the shell's global theme. -3. The shell's shared readback router dispatches GPU results by ticket, preserving the existing test-capture route. After three matching pixel samples and ready shaders, a heavy worker flattens premultiplied pixels onto an opaque backdrop and encodes the PNG. The reply returns a workspace-relative path, pixel dimensions and `settled`; the agent can then call `view_image`. +| Tool | What it does | +| --- | --- | +| `studio.bundle_check {bundle_path}` | Copies bounded files from the conversation workspace, computes the digest and admits a private developer snapshot. It leaves the author's files unchanged. | +| `studio.open {bundle_path}` | Opens a visible preview with disposable app state; returns `instance_id`. | +| `studio.inspect {instance_id, offset?}` | Returns a PNG `path`, a compact page of widget selectors and checks, and `snapshot_path` for full diagnostic JSON. | +| `studio.input {instance_id, widget_id, action, …}` | Sends a real `tap`, `text` or `scroll` event to an inspected widget. Text uses `text`; scrolling uses `delta_y`. | +| `studio.close {instance_id}` | Closes the app and discards preview state. | +| `studio.install {bundle_path}` | Records a local developer install, visible in Home. Open it with `studio.open {app_id}` or its launcher tile; its own state survives closing and reopening. | + +The first full-app path accepts offline, storage-only `main.splash` bundles under `dev.studio.*`. These apps have no agent of their own, account access, network or host-service grants. A bundle may contain an original launcher icon, but in-screen resource routes are not supported yet. In [studio_bundles.rs](../crates/shell/src/host_tools/studio_bundles.rs), admission limits the bundle to 128 files/directories, eight directory levels and 2 MiB total, with at most 512 KiB per file and 64 KiB for `main.splash`. The resolved policy allows at most 1 MiB of private app storage, five million script instructions and a 16 MiB heap; a lower manifest limit remains lower. + +A developer install is separate from App Hub's signed catalog. Its host-private receipt binds the admitted bytes to the authoring app, account, session, context and `DevTag`. The tag identifies the developer profile and the activation that authorized it. Opening an installed app rechecks its receipt and digest. Ending that grant removes its launcher availability and stops its running instance. Another conversation cannot inspect, control or replace it. One installed app runs in one instance at a time, avoiding concurrent writes to its state. + +Now follow the Rust execution boundaries: + +1. The host executor runs bounded file reads and admission on a worker thread, then queues an `OpenSpec` or instrument request. It waits for a reply channel without blocking Makepad's UI thread. +2. The shell drains the launch queue into a normal window-manager client. [StudioModule](../crates/shell/src/studio/module.rs) creates the [StudioApp](../crates/shell/src/studio/apps.rs) widget and owns its shutdown. Splash evaluates the source after the private jail and admitted limits are installed. Preview writes stay in a disposable jail; installed writes stay in that app's persistent jail. +3. The UI thread builds a Makepad `WidgetTree` rooted at that app alone. Inspection reads its real rectangles and control state; input resolves a visible, enabled widget and follows the event path. Duplicate names have unique `selector` values. GPU readback uses the shell's shared ticket router; PNG compression runs on the heavy worker pool. The reply returns to the same agent call. + +The model receives at most 3,800 UTF-8 bytes from `studio.inspect`. `snapshot.widgets` lists visible non-Splash widgets with exact `selector` values; pass one as `studio.input.widget_id`, rather than guessing a button's painted label. Long text/value fields carry `text_truncated` or `value_truncated`. If `next_offset` is an integer, call inspect again with that `offset` to read the next page. Each call observes the current UI, so page through a stable app state. `snapshot.checks` summarizes the pass result and finding/error counts. + +The `snapshot_path` file contains the full original result, including all widgets, rectangles, geometry, tree and findings, plus the PNG path. This pretty-printed JSON is limited to 1 MiB and stored alongside the PNG in the caller's conversation workspace; `read_file` can retrieve bounded line ranges. Full diagnostics stay available without pushing selectors beyond the kernel's 4 KiB model-visible tool-output limit. + +The Android HTTP remote instrument is unavailable at this pin. Studio uses its underlying Makepad widget APIs in process, scoped to its own app. The current checks detect empty geometry, clipped text and small buttons; they do not prove the app's behavior or overall UX. App inspection returns `settled: false`: it captures a current frame without claiming that an arbitrary interactive script has finished changing. The agent can call `view_image` on the returned PNG and then test the app's behavior with further inputs. + +`studio.render` remains the separate L0 glance path: relative `source_path`, optional `data_path` and `dark`, with 16/32 KiB source/data limits. It uses the actual glance width and 72–440 point height bounds, a zero-quota disposable jail, and no network or host capabilities. Three matching readbacks after shader readiness produce `settled: true`; chat and image resources are explicitly unsupported. Both capture paths cap PNGs at 5 MiB and check cancellation, foreground state and the developer grant. UI requests have a 20-second deadline inside the host's 25-second wait and normal 30-second kernel call deadline. -The preview isolate has no network, host capabilities or prompts. Its disposable storage directory has a zero-byte quota, its instruction budget is five million, and its heap limit is 16 MiB. Source/data limits match glance admission (16/32 KiB). Own-app digest references are resolved from host data; chat sources and image resources currently return explicit unsupported errors. Contained script-app screens are a later milestone. +None of this creates an agent peer or assigns one Tokio task to each app. The existing agent invokes a host tool; Rust workers handle files and encoding, and the UI thread owns widgets and GPU submission. -A render has a 20-second deadline including queueing and encoding. Backgrounding Home, cancellation or an expired developer grant prevents a successful reply, including while PNG encoding is in progress. A canceled GPU lease still has to drain before the platform releases its memory. The output is capped at 5 MiB; failed calls remove it. This is UI/GPU work followed by a worker-pool job—not a new agent peer or one Tokio task per render. Device evidence and the remaining model-image check belong to [ADR 0006](adr/0006-app-studio-on-the-phone.md). +A fresh Task Planner authored by DeepSeek V4 Flash passed **129 tool calls on a physical OnePlus 6**: task entry/completion/filtering, disposable preview state, separate installed state, close/reopen, process restart, exact Chinese text input and scrolling. The harness did not modify app source or storage directly. Portrait app and keyboard visual review passed, with generous spacing and separate shell overlay/status-bar observations; malformed-storage/save-failure fault injection remains pending. See the [validation report](studio/oneplus6-validation.md), [ADR 0006](adr/0006-app-studio-on-the-phone.md) and the [fresh-app brief](studio/task-planner-brief.md). The `mod.studio` toolbox adapter, image generation/comparison, richer asset routes and public publishing are not implemented by this slice. ## 11. Tests and remaining implementation diff --git a/docs/architecture-walkthrough.zh-CN.md b/docs/architecture-walkthrough.zh-CN.md index 4a56367ca..b24161515 100644 --- a/docs/architecture-walkthrough.zh-CN.md +++ b/docs/architecture-walkthrough.zh-CN.md @@ -273,19 +273,42 @@ flowchart LR Broker 同步 `bind` / `host_request` 会等待通道回复,不应在绘制回调中调用。不同 session 的异步模型 I/O 可重叠,同步文件工作仍走已有的工作线程边界。每应用/账号一个 peer 是身份规则;task 和线程数量由上表的 runtime 与活跃操作决定。 -### 前台渲染跨越三个执行边界 +### 从 Agent 调用跟到 App Studio 中可运行的应用 -App Studio 的首个实现增加了 Shell 所有的 `studio.render`,声明位于 [host_tools/studio.rs](../crates/shell/src/host_tools/studio.rs)。只有开发者模式覆盖当前调用方时,relay 才提供此工具。它不可共享,不安装应用包,也不向脚本清单添加工具。系统调用使用内核打开 session 时返回的 workspace;应用调用使用 broker 绑定的 workspace,用户对话进一步限定到 `contexts//`。调用方只传相对 `source_path`、可选 `data_path` 和可选 `dark` 布尔值,不能指定别的应用身份或输出目录。 +开发者模式覆盖当前调用方时,App Studio 会向已有的系统或应用 Agent 提供 Shell 所有的工具。[工具声明与执行器](../crates/shell/src/host_tools/studio.rs)沿用宿主工具的 relay、审计与取消路径。系统调用使用 `session/open` 确认的 workspace;应用调用使用 broker 确认的 peer workspace,人类对话进一步限定到自己的 `contexts//`。工具参数不能替换调用身份,也不能选择任意输出目录。 -从[执行器](../crates/shell/src/host_tools/studio.rs)跟随一次调用进入[渲染器](../crates/shell/src/studio/mod.rs): +Agent 可先用普通文件工具从头编写 `manifest.json` 和 `main.splash`,再执行以下步骤: -1. 宿主工作线程通过 workspace 目录描述符打开大小受限的源文件与数据文件。符号链接和 `..` 不能越出目录。它在该目录创建唯一输出文件,将 `RenderJob` 入队,等待对应回复。 -2. Makepad UI 循环检查当前开发者授权,准备 L0 卡片,绘制独立的离屏 pass。它复用真实 glance lowering,显式传入明暗模式;宽度为窗口宽减 40 点,测得的高度限制在 72–440 点。它不会发布卡片,也不会改变 Shell 的全局主题。 -3. Shell 共享的读回路由按 ticket 分发 GPU 结果,保留已有测试截图的路线。着色器就绪且连续三次像素样本相同后,重型工作线程把预乘像素合成到不透明背景上,再编码 PNG。回复返回 workspace 相对路径、像素尺寸和 `settled`,Agent 随后可调用 `view_image`。 +| 工具 | 行为 | +| --- | --- | +| `studio.bundle_check {bundle_path}` | 从当前对话 workspace 复制大小受限的文件,计算摘要,对宿主私有副本做开发者准入;作者的文件不变。 | +| `studio.open {bundle_path}` | 打开可见预览,使用可丢弃的应用状态,返回 `instance_id`。 | +| `studio.inspect {instance_id, offset?}` | 返回 PNG `path`、一页精简的控件 selector 与检查摘要,以及完整诊断 JSON 的 `snapshot_path`。 | +| `studio.input {instance_id, widget_id, action, …}` | 向检查到的控件发送真实 `tap`、`text` 或 `scroll` 事件。输入文字用 `text`,滚动用 `delta_y`。 | +| `studio.close {instance_id}` | 关闭应用,丢弃预览状态。 | +| `studio.install {bundle_path}` | 登记本地开发者安装,在 Home 中显示。随后可用 `studio.open {app_id}` 或启动器图块打开;应用自己的状态在关闭、重开后保留。 | + +首个完整应用路径接受 `dev.studio.*` 命名空间下的离线、仅存储权限 `main.splash` 包。应用本身没有 Agent、账户访问、网络或宿主服务授权。包中可携带原创启动器图标,但暂不支持屏幕内的资源加载路径。[studio_bundles.rs](../crates/shell/src/host_tools/studio_bundles.rs)把包限制为 128 个文件/目录、八层目录、总计 2 MiB;单文件最多 512 KiB,`main.splash` 最多 64 KiB。解析后的策略最多允许 1 MiB 私有应用存储、五百万脚本指令和 16 MiB 堆;清单中更低的限制仍然生效。 + +开发者安装独立于 App Hub 的签名目录。宿主私有收据把准入后的字节绑定到作者所属的应用、账户、session、context 与 `DevTag`。该标记记录开发者 profile 和授予权限的那次启用。打开已安装应用时会重新检查收据和摘要;授权结束后,应用从启动器可用列表移除,运行中的实例停止。另一个对话不能检查、操控或替换它。同一已安装应用只允许一个实例运行,避免同时写入其状态。 + +再沿着 Rust 的执行边界阅读: + +1. 宿主执行器在工作线程读取受限文件并做准入,然后将 `OpenSpec` 或检查请求入队。它通过回复 channel 等待结果,不阻塞 Makepad UI 线程。 +2. Shell 把启动队列中的请求变成普通窗口管理器 client。[StudioModule](../crates/shell/src/studio/module.rs)创建 [StudioApp](../crates/shell/src/studio/apps.rs) 控件并负责关闭。Splash 在私有 jail 与准入限制设置完毕后才求值源码。预览写入可丢弃的 jail;安装后的写入保存在该应用自己的持久 jail。 +3. UI 线程以该应用为根构建 Makepad `WidgetTree`。检查读取真实矩形与控件状态;输入先定位可见、启用的控件,再走事件路径。重名控件有唯一的 `selector` 值。GPU 读回复用 Shell 的 ticket 路由,PNG 压缩运行在重型工作线程池;结果回到原来的 Agent 工具调用。 + +`studio.inspect` 返回给模型的内容最多为 3,800 个 UTF-8 字节。`snapshot.widgets` 列出可见的非 Splash 控件及其准确 `selector`;把该值传给 `studio.input.widget_id`,不要猜测按钮显示文字就是控件 ID。较长的 text/value 会附带 `text_truncated` 或 `value_truncated` 标记。若 `next_offset` 是整数,用该值作为 `offset` 再调用 inspect 获取下一页。每次调用都观察当前 UI,因此翻页时应保持应用状态稳定。`snapshot.checks` 汇总是否通过以及发现项和错误数量。 + +`snapshot_path` 指向完整原始结果文件,包含所有控件、矩形、geometry、tree、检查详情和 PNG 路径。该 JSON 按行缩进,最多 1 MiB,与 PNG 一起存入调用方的对话 workspace;可用 `read_file` 按有限行数读取。这样既保留完整诊断,也避免重要 selector 被内核对模型工具结果的 4 KiB 截断隐藏。 + +当前 pin 在 Android 上没有 HTTP 远程 instrument。Studio 直接在进程内调用底层 Makepad 控件 API,范围限定为自身应用。现有检查能发现空几何信息、文字裁切与过小按钮,不能据此证明功能或整体 UX。应用检查返回 `settled: false`:它捕获当前帧,不声称任意交互脚本已经停止变化。Agent 可用 `view_image` 查看返回的 PNG,再发送输入测试实际行为。 + +`studio.render` 仍是独立的 L0 glance 路径:参数为相对 `source_path`、可选 `data_path` 和 `dark`,源码/数据上限为 16/32 KiB。它使用真实 glance 宽度与 72–440 点高度范围,临时 jail 配额为零,没有网络或宿主能力。着色器就绪且连续三次读回一致后返回 `settled: true`;聊天与图像资源明确返回不支持。两条截图路径都把 PNG 限制为 5 MiB,并检查取消、前台状态与开发者授权。UI 请求期限为 20 秒,处于宿主 25 秒等待和内核默认 30 秒工具期限之内。 -预览 isolate 没有网络、宿主能力或弹窗权限。临时存储目录配额为零,指令预算为五百万,堆上限为 16 MiB。源码/数据限制与 glance 准入一致,为 16/32 KiB。自身应用的 digest 引用由宿主数据解析;聊天来源和图像资源当前明确返回不支持。隔离脚本应用的完整屏幕属于后续里程碑。 +这些操作不会创建 Agent peer,也不是每个应用对应一个 Tokio task。已有 Agent 调用宿主工具,Rust 工作线程负责文件与编码,UI 线程持有控件并提交 GPU 工作。 -渲染的 20 秒期限包含排队和编码。Home 退到后台、取消或开发者授权失效都会阻止成功回复,PNG 编码阶段也不例外。已取消的 GPU 租约仍需排空,平台才释放其内存。输出上限为 5 MiB,失败调用会删除输出。这是 UI/GPU 工作接上工作线程池任务,不是新 Agent peer,也不是每次渲染对应一个 Tokio task。设备证据及尚待验证的模型图像链路见 [ADR 0006](adr/0006-app-studio-on-the-phone.md)。 +DeepSeek V4 Flash 从头编写的 Task Planner 已在**真实 OnePlus 6 上通过 129 次工具调用**,覆盖任务输入、完成与筛选、预览状态丢弃、独立安装状态、关闭重开、进程重启、准确的中文输入及滚动。测试工具没有直接修改应用源码或存储。竖屏应用与键盘视觉评审已通过;间距较宽,Shell 浮层和状态栏另有观察记录。损坏存储与保存失败的故障注入仍待完成,详见[验收报告](studio/oneplus6-validation.md)。见 [ADR 0006](adr/0006-app-studio-on-the-phone.md) 和[新应用需求](studio/task-planner-brief.md)。本次实现不包含 `mod.studio` 工具箱适配器、图像生成/比较、更完整的资源路径或公开发布。 ## 11. 测试与尚待实现的部分 diff --git a/docs/studio/oneplus6-validation.md b/docs/studio/oneplus6-validation.md new file mode 100644 index 000000000..9b6e85077 --- /dev/null +++ b/docs/studio/oneplus6-validation.md @@ -0,0 +1,42 @@ +# App Studio: OnePlus 6 validation + +A fresh Task Planner authored by DeepSeek V4 Flash passed physical-device functional testing and manual app visual review in the isolated `dev.makepad.octosense.studio` package. This validates the implemented local, offline app path; it does not complete ADR 0006's image-generation and editable-workflow design. + +## Scope and provenance + +The model authored `DESIGN.md`, the bundle manifest, `main.splash` and an original icon. The shared kernel drove Studio tools; five recorded `view_image` calls returned `shown_to_model: true`, followed by continued model work. That establishes image delivery and continuation, not perfect visual understanding. The independent [device harness](../../tools/studio-flow-device-test.py) then exercised the resulting bundle without directly modifying its source or app storage. + +Evidence identifiers from the retained local run artifacts: + +| Record | Digest | +| --- | --- | +| Admitted bundle, BLAKE3 | `7892501e8b69d752b002130879dde11af8f7c7e7b7e620a6854b97892fc95f3b` | +| Model transcript, SHA-256 | `2d35937d1165d22e9f1ee8a1509064afe33df7f311ee7e2b80420ff4af34e219` | +| Generation receipt, SHA-256 | `495461c440b451047bd32114a47ba69e778a9cb25eafdb72c1f99094fb0da6db` | +| Acceptance contract, SHA-256 | `58a2091f2593a231982f755f643559b9dc29649b8d657b67c4edc3d3cbf715` | + +The functional run is `adr0006-task-planner-acceptance-v2`; the generation receipt is `adr0006-generated-app-final/generation-receipt.json`. These tests used the implementation working tree before final rebase/integration. They are evidence for that tested build and bundle, not a claim that a later rebased binary was rerun. Raw provider configuration, personal paths and device identifiers are not published here. + +The final tools Python test run passed 105 tests before rebase. This supplements the device evidence; it does not replace it. + +## Functional result + +Version 2 completed **129 tool calls**. It verified task entry, completion, All/Active/Done filtering, disposable preview state, separate installed storage, installed close/reopen and process-restart persistence. Exact Chinese input survived reopening. Seven additional rows made a real scrollable list; the instrument reached the last row. The full diagnostic artifacts retained native widget values, geometry and screenshots while compact tool replies exposed usable selectors to the model. + +The first harness run stopped after ten calls with “Add did not clear the text input.” This was a harness assertion error: Makepad exposes the placeholder as display text, while the editable `value` was empty. The corrected assertion requires the actual TextInput value. Version 2 retained the same admitted bundle digest; no app-source change was used to bypass the failure. + +The model's earlier turn stopped at its 32-call host budget and explicitly left persistence, scrolling, Chinese glyphs and some visual checks unverified. The independent device run and manual review below supply those additional observations; the model's self-report alone is not the acceptance result. + +## Manual visual review + +Portrait app captures showed readable text, correct long-title wrapping, reachable bottom rows, readable Chinese glyphs, good app contrast and no unintended app overlap. A separate Android-wide screenshot showed the keyboard open: the input, Add button and filters remained above it, and the list viewport shrank correctly. App-only texture captures cannot establish keyboard presentation by themselves. + +The UI is usable, with generous spacing that leaves only about one task row above the keyboard. Two shell observations are separate from the generated app: the floating four-dot overlay sits near the Done filter without obscuring its label, and white status-bar content has low contrast against the light background. This is a successful scoped review, not a claim of flawless UI. + +The harness's machine-readable status remains `functional_passed_visual_review_required`; the subsequent manual review recorded here completes the observed portrait visual checks. Rotation/landscape behavior was not established. + +## Remaining limits + +Malformed saved JSON preservation and save-failure reporting were not fault-injected. This run does not claim a full-app developer-revocation fault test; earlier L0 denial/background tests cover a narrower path. The app remains a local `dev.studio.*` developer install, with no publisher signature or public catalog admission. + +The `mod.studio` toolbox adapter, editable flow templates, image generation/comparison and rich in-screen asset routes remain unimplemented. See [ADR 0006](../adr/0006-app-studio-on-the-phone.md) for the implemented boundary and intended later work. diff --git a/docs/studio/task-planner-brief.md b/docs/studio/task-planner-brief.md new file mode 100644 index 000000000..e7b68233b --- /dev/null +++ b/docs/studio/task-planner-brief.md @@ -0,0 +1,156 @@ +# Task Planner: a fresh App Studio acceptance project + +This is a design brief for the model authoring a new app. It contains no app +implementation. Passing a preview of an existing card does not satisfy it. +The acceptance run starts with an empty project in the test package, records +the model's tool calls and resulting bundle, and exercises that bundle through +Makepad's actual widget input path. + +## Brief to give the authoring model + +Create **Task Planner**, a small offline phone app for keeping a short personal +task list. Its app id is `dev.studio.task-planner`. Build a fresh contained Splash +app (`main.splash`), not an L0 glance card or a modified existing app example. +Use the installed Studio tool schemas exactly; do not invent tool names, +widget methods, syntax, host services or a successful tool result. + +Design a calm, readable portrait screen with a warm neutral background, dark +text, one blue accent and generous spacing. Put the title and remaining-task +count at the top, a text field and Add button underneath, then All / Active / +Done filters and a scrollable task list. Use real editable and tappable +widgets. Show completion with both a readable label/state and a visual change, +not color alone. Keep controls at least 44 logical pixels high. The layout +must fit the current device, keep long titles readable and remain usable with +the keyboard open. Draw a simple original checklist icon as local vector +artwork. No generated raster design, borrowed screenshots, network artwork or +existing A2 app/card/assets are needed. + +A new app opens with no tasks, `0 active`, All selected, and `No tasks yet`. +Typing a nonblank title and pressing Add creates one active task, clears the +field and updates the count. Blank input creates nothing and explains that a +title is required. Completion is reversible. All shows every task, Active +shows incomplete tasks, and Done shows completed tasks; a filter with no +matches shows a short empty-state message. Filters do not change task data. +Titles and completion survive closing and reopening the app, including a +restart of the test Home process. On reopen, select All. There is no login, +network request, AI feature in the resulting planner, or permission request. + +The only app capability is `storage`. Keep task data in the app's own storage +jail, separate from the Studio project's source and the agent conversation. +Persist a versioned JSON document in `tasks.json`: + +```json +{"schema":1,"next_id":1,"tasks":[]} +``` + +Each task is `{ "id": , "title": , "completed": + }`; increment `next_id` after insertion. The filter is session UI +state. Do not silently overwrite a malformed saved document: show a useful +storage error and preserve its bytes. Surface a failed save rather than +claiming that the change was persisted. Use only storage/error APIs supported +by the pinned runtime. + +Give the input, Add button and filter controls stable source names: +`task_title`, `add_task`, `filter_all`, `filter_active`, `filter_done`. +Name the scrollable list `task_list`, and status and validation text +`task_count` and `task_error`. Task titles +must appear as native widget text. Each task has a visible `Complete` or +`Reopen` control associated with that task; the test can resolve its current +native widget id from inspection. These names are test selectors, not a +requirement to invent dynamic ids or custom runtime APIs. + +Before implementation, write a short `DESIGN.md` outside `bundle/` stating the +screen layout, states, data ownership and supported runtime APIs. Then author +`bundle/manifest.json`, `bundle/main.splash` and `bundle/assets/icon.svg` from +scratch. Include other metadata only when the real local bundle checker +requires it. Do not invent publisher identity, signatures, public submission, +or platform claims. Keep source notes, logs, runtime state and test evidence +outside the bundle. + +The local developer checker may compute integrity and stamp its own immutable +snapshot; it must leave the authored source unchanged. A developer check is +not publisher signing. Check the bundle, open it in Studio, inspect its real widget tree and image, +and drive its controls through Studio's actual input tool. Inspect after each +state change. `studio.inspect` returns a compact page in `snapshot.widgets`; +if `next_offset` is an integer, inspect the same instance with that `offset` +until it is null. Use the exact returned `selector` for `widget_id`; truncated +`text` or `value` is explicitly flagged and is not the complete field. +`snapshot_path` names the complete JSON inspection artifact relative to your +conversation workspace, and `path` names its PNG. The functional harness reads +the bounded full artifact so pagination cannot hide missing controls or +failed layout checks. The model should use selector pages for interaction; +reading a large artifact through a text tool is still subject to that tool's +output limit. Use `view_image` on the PNG for visual review. +Repair source errors or UX failures, recheck the new revision, +and reopen that revision before testing it. Preview state and installed app +state are separate; a preview does not prove installed-app persistence. +Leave final installation to the acceptance harness after your preview checks; +close the preview and return the final checked digest. The harness installs +into the developer test profile and exercises persistence through the same +Studio tools. Do not prepopulate installed task data. Never replace the +installed production Home or Bridge. + +Report which checks ran and the final immutable bundle revision. Report +unavailable tools, errors and unfinished steps directly. A returned filename +or `settled: true` alone does not prove a readable or functional app. Leave +publication unsigned and local; the requested result is a working developer +app, not a public App Hub submission. + +## Acceptance sequence + +The machine-readable contract is +[`task-planner-acceptance.json`](../../tools/studio-flow/task-planner-acceptance.json). +Run it against the model's final bundle, not a separately hand-authored +replacement. Preserve the generation transcript and source revision with the +receipt; the functional harness cannot prove authorship by itself. + +1. Inspect the empty screen and capture it. Check that the input, Add and three + filters are visible, enabled where appropriate, and at least 44 logical + pixels high. Blank Add must leave zero tasks and show validation. +2. Through native text input and button clicks, add `Buy oats`, `Call Sam`, + and `Read ten pages`. Verify three task titles and `3 active`. +3. Complete `Call Sam`; verify `2 active`, All still contains all three, + Active contains only Buy oats and Read ten pages, and Done only Call Sam. +4. Reopen Call Sam from Done; Done becomes empty and the count returns to + three. Return to All, then complete Call Sam again for persistence testing. +5. Close and reopen the preview: its disposable storage must reset to the + empty state. Developer-install the checked revision and open it by app id. + Repeat the three-task sequence in the installed app, then close/reopen it + and force-stop/relaunch the separate test Home package. Verify all three + titles, Call Sam's completed state and `2 active` after each installed-app + restart. Installed storage must not inherit preview test data. +6. Through native text input, add `测试中文任务:买燕麦`, reopen the installed + app and verify the exact title and `3 active`. Capture the typed, added + and reopened states for glyph review: correct widget text does not prove + correct font rendering. Then add a long title, inspect wrapping and scroll + to the final item. Verify + typing remains possible with the keyboard open. Review actual native + captures for clipping, overlap, contrast and top-to-bottom orientation. +7. In an isolated copy of the app data only, exercise malformed saved JSON and + a save failure if the test API can create that condition. Preserve existing + data and mark any unsupported fault-injection check unverified. + +The overall result is incomplete if model authoring, actual input behavior, +installed-app persistence or visual review is missing. A developer bundle +check is not a full App Hub publishing approval. + +## Existing flow and runtime boundaries + +The procedure follows Design Flow's +[text-brief flow](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/0e59346e810ed694702b1df48f4283dc8104358c/flows/script-app/FLOW.md) +and [script API](https://github.com/OctoSense-org/OctoScript-App-Design-Flow/blob/0e59346e810ed694702b1df48f4283dc8104358c/docs/SCRIPT-API.md). +That revision pins the same OctoScript-Makepad release as this shell's +`native-runtime.lock.json`. Use the pinned implementation when a historical +API description disagrees with source. + +Desktop tests use an owned hidden `card-host --remote` and real `/snap`, +`/click`, `/t` and `/g` operations. The current Android build has no HTTP +remote server. Its Studio integration must call Makepad's underlying widget +inspection and event dispatch on the UI thread; desktop captures do not count +as phone evidence. The earlier L0 `studio.render` probe verifies offscreen +rendering only. + +The existing toolbox library runs declared `mod.research` methods. It does +not yet provide a `mod.studio` authoring adapter. This brief and test contract +are inputs to the Studio flow, not a claim that an executable OctoScript +workflow template has already been installed. diff --git a/tools/studio-flow-device-test.py b/tools/studio-flow-device-test.py new file mode 100644 index 000000000..af1c22ec3 --- /dev/null +++ b/tools/studio-flow-device-test.py @@ -0,0 +1,480 @@ +#!/usr/bin/env python3 +"""Exercise a real model-authored Task Planner through the Studio instrument. + +Only the separate developer test package is in scope. This harness never +writes application source or changes task storage to manufacture success. +""" +import argparse +import hashlib +import importlib.util +import json +from pathlib import Path +import re +import time +import uuid + +ROOT = Path(__file__).resolve().parents[1] +CONTRACT = ROOT / "tools/studio-flow/task-planner-acceptance.json" +_spec = importlib.util.spec_from_file_location("studio_device_probe", ROOT / "tools/studio-device-probe.py") +_probe = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(_probe) +ProbeError = _probe.ProbeError +Device = _probe.Device + + +def widgets(snapshot): + if not isinstance(snapshot, dict) or not isinstance(snapshot.get("widgets"), list): + raise ProbeError("inspect result needs a native widgets array") + return [node for node in snapshot["widgets"] if isinstance(node, dict) and node.get("visible") is True] + + +def node_text(node): + return str(node.get("text") or node.get("value") or "").strip() + + +def input_value(node): + """Read editable content, never the placeholder exposed as native text.""" + if "TextInput" not in str(node.get("type", "")) or not isinstance(node.get("value"), str): + raise ProbeError("selected TextInput must expose its actual string value") + return node["value"] + + +def bounds(node): + value = node.get("rect") + if not isinstance(value, list) or len(value) != 4 or not all(isinstance(n, (int, float)) for n in value): + raise ProbeError("native widget has no measured rectangle") + if value[2] <= 0 or value[3] <= 0: + raise ProbeError("native widget has empty bounds") + return value + + +def unique_node(snapshot, *, source_id=None, text=None, kind=None, enabled=True): + nodes = widgets(snapshot) + if source_id is not None: + nodes = [node for node in nodes if str(node.get("id", "")) == source_id + or str(node.get("id", "")).endswith(("/" + source_id, "." + source_id))] + if text is not None: + nodes = [node for node in nodes if node_text(node) == text] + if kind is not None: + nodes = [node for node in nodes if kind.lower() in str(node.get("type", "")).lower()] + if len(nodes) != 1: + raise ProbeError(f"selector must match one visible native widget, found {len(nodes)}: " + f"id={source_id!r} text={text!r} type={kind!r}") + node = nodes[0] + bounds(node) + if enabled and node.get("enabled") is not True: + raise ProbeError("selected native control is disabled") + return node + + +def task_action(snapshot, title, action): + title_node = unique_node(snapshot, text=title, enabled=False) + title_rect = bounds(title_node) + row_center = title_rect[1] + title_rect[3] / 2 + candidates = [] + for node in widgets(snapshot): + if node_text(node) != action or node.get("enabled") is not True: + continue + rect = bounds(node) + if rect[1] <= row_center <= rect[1] + rect[3]: + candidates.append(node) + if len(candidates) != 1: + raise ProbeError(f"cannot identify one {action} control beside task {title!r}") + return candidates[0] + + +def assert_tasks(snapshot, expected, all_titles): + shown = {node_text(node) for node in widgets(snapshot)} + actual = [title for title in all_titles if title in shown] + if set(actual) != set(expected): + raise ProbeError(f"visible tasks differ: expected {expected!r}, found {actual!r}") + + +def assert_count(snapshot, source_id, number): + node = unique_node(snapshot, source_id=source_id, enabled=False) + if not re.search(rf"\b{number}\s+active\b", node_text(node), re.IGNORECASE): + raise ProbeError(f"expected {number} active; native status is {node_text(node)!r}") + + +def verify_controls(snapshot, contract): + names = contract["selectors"] + checks = [] + for key in ("title_input", "add", "all", "active", "done"): + node = unique_node(snapshot, source_id=names[key]) + rect = bounds(node) + if rect[3] < contract["minimum_control_height_logical"]: + raise ProbeError(f"{names[key]} is only {rect[3]} logical pixels high") + checks.append({"selector": names[key], "widget_id": node["id"], "rect": rect}) + return checks + + +def safe_relative(value): + if not isinstance(value, str) or not value or value.startswith("/") or "\\" in value: + raise ProbeError("expected a relative workspace path") + if any(part in ("", ".", "..") for part in value.split("/")): + raise ProbeError("workspace path contains traversal or empty components") + return value + + +def full_inspection(device, workspace, instance, compact): + encoded = json.dumps(compact, ensure_ascii=False, separators=(",", ":")).encode("utf-8") + if len(encoded) > 3800: + raise ProbeError("inspect response exceeds the model-visible 3800-byte budget") + path = safe_relative(compact.get("snapshot_path")) + raw = device.read(workspace + "/" + path, 1024 * 1024) + if raw is None: + raise ProbeError("inspect returned a missing full snapshot artifact") + if len(raw) > 1024 * 1024: + raise ProbeError("full snapshot artifact exceeds 1MiB") + try: + full = json.loads(raw) + except (UnicodeDecodeError, json.JSONDecodeError) as error: + raise ProbeError("full snapshot artifact is not complete UTF-8 JSON") from error + if not isinstance(full, dict) or full.get("instance_id") != instance: + raise ProbeError("full snapshot artifact belongs to another instance") + for key in ("instance_id", "path", "width", "height", "settled"): + if key not in full or full[key] != compact.get(key): + raise ProbeError(f"full snapshot and compact response disagree on {key}") + widgets(full.get("snapshot")) + return full + + +class Studio: + def __init__(self, device, workspace, evidence_dir, timeout): + self.device, self.workspace, self.evidence_dir = device, workspace, evidence_dir + self.timeout = timeout + self.calls = [] + self.inspections = 0 + self.instance = None + self.snapshot = None + + def start(self): + import shlex + self.device.stop() + self.device.private("umask 077; mkdir -p " + shlex.join([ + self.workspace + "/requests", self.workspace + "/responses"])) + config_path = self.workspace + "/studio-flow-config.json" + self.device.write(config_path, json.dumps({"workspace": self.workspace}).encode()) + config = json.dumps({"test_actions": ["studio-flow:" + config_path]}, separators=(",", ":")) + launch = self.device.shell("am", "start", "-n", self.device.package + "/.MakepadApp", + "--es", "makepad.APP_CONFIG", config) + if b"Error:" in launch.stdout or b"Error:" in launch.stderr: + raise ProbeError("studio flow activity failed to start") + self.instance = None + self.snapshot = None + + def call(self, name, args): + import shlex + request_id = str(uuid.uuid4()) + request = {"name": name, "args": args} + payload = json.dumps(request).encode() + if len(payload) > 16 * 1024: + raise ProbeError("test request exceeds 16KiB") + temporary = self.workspace + "/requests/" + request_id + ".pending" + destination = self.workspace + "/requests/" + request_id + ".json" + self.device.write(temporary, payload) + self.device.private("mv " + shlex.join([temporary, destination])) + started = time.monotonic() + response = None + while time.monotonic() - started < self.timeout: + raw = self.device.read(self.workspace + "/responses/" + request_id + ".json", 1024 * 1024) + if raw: + try: + response = json.loads(raw) + break + except json.JSONDecodeError: + pass + time.sleep(0.1) + record = {"request_id": request_id, "request": request, "reply": response, + "elapsed_seconds": round(time.monotonic() - started, 3)} + self.calls.append(record) + (self.evidence_dir / "tool-calls.json").write_text(json.dumps(self.calls, indent=2) + "\n") + if not isinstance(response, dict): + raise ProbeError(f"{name}: no complete response before timeout") + if response.get("ok") is not True: + raise ProbeError(f"{name}: {response.get('error')}") + result = response.get("data") + if not isinstance(result, dict): + raise ProbeError(f"{name}: expected object result") + return result + + def open(self, **args): + result = self.call("studio.open", args) + instance = result.get("instance_id") + if not isinstance(instance, str) or not instance: + raise ProbeError("studio.open returned no instance_id") + self.instance = instance + return result + + def close(self): + if self.instance is not None: + self.call("studio.close", {"instance_id": self.instance}) + self.instance = None + self.snapshot = None + + def inspect(self, label): + compact = self.call("studio.inspect", {"instance_id": self.instance}) + result = full_inspection(self.device, self.workspace, self.instance, compact) + snapshot = result["snapshot"] + self.snapshot = snapshot + self.inspections += 1 + stem = f"{self.inspections:03d}-{label}" + (self.evidence_dir / f"{stem}.json").write_text(json.dumps(result, indent=2) + "\n") + (self.evidence_dir / f"{stem}.compact.json").write_text(json.dumps(compact, indent=2) + "\n") + path = safe_relative(result.get("path")) + png = self.device.read(self.workspace + "/" + path, _probe.PNG_LIMIT) + if png is None: + raise ProbeError("inspect returned a missing PNG") + metadata = _probe.png_metadata(png) + (self.evidence_dir / f"{stem}.png").write_bytes(png) + if metadata["width"] < 240 or metadata["height"] < 240: + raise ProbeError("full-app inspection PNG is implausibly small") + if (metadata["width"], metadata["height"]) != (result["width"], result["height"]): + raise ProbeError("inspection PNG dimensions disagree with its full snapshot") + checks = snapshot.get("checks") + if not isinstance(checks, dict) or checks.get("pass") is not True: + raise ProbeError(f"native layout checks did not pass: {checks}") + return snapshot + + def input(self, node, action="tap", text=None): + args = {"instance_id": self.instance, "widget_id": node.get("selector", node["id"]), "action": action} + if text is not None: + args["text"] = text + return self.call("studio.input", args) + + def tap(self, source_id, label): + self.input(unique_node(self.snapshot, source_id=source_id)) + return self.inspect(label) + + def add(self, title, selectors, label): + field = unique_node(self.snapshot, source_id=selectors["title_input"]) + self.input(field, "text", title) + self.inspect(label + "-typed") + return self.tap(selectors["add"], label + "-added") + + +def exercise(studio, contract, label): + names = contract["selectors"] + titles = contract["fixture_titles"] + snap = studio.inspect(label + "-empty") + controls = verify_controls(snap, contract) + assert_tasks(snap, [], titles) + assert_count(snap, names["count"], 0) + unique_node(snap, text="No tasks yet", enabled=False) + snap = studio.tap(names["add"], label + "-blank") + assert_tasks(snap, [], titles) + error = unique_node(snap, source_id=names["validation"], enabled=False) + if not node_text(error): + raise ProbeError("blank Add did not explain validation") + for index, title in enumerate(titles): + snap = studio.add(title, names, label + f"-task-{index + 1}") + assert_tasks(snap, titles[:index + 1], titles) + assert_count(snap, names["count"], index + 1) + if input_value(unique_node(snap, source_id=names["title_input"])) != "": + raise ProbeError("Add did not clear the text input") + studio.input(task_action(snap, contract["completed_title"], "Complete")) + snap = studio.inspect(label + "-completed") + assert_count(snap, names["count"], 2) + for filter_name in ("active", "done", "all"): + snap = studio.tap(names[filter_name], label + "-" + filter_name) + assert_tasks(snap, contract["expected_before_restart"][filter_name], titles) + snap = studio.tap(names["done"], label + "-done-before-reopen") + studio.input(task_action(snap, contract["completed_title"], "Reopen")) + snap = studio.inspect(label + "-task-reopened") + assert_tasks(snap, [], titles) + assert_count(snap, names["count"], 3) + snap = studio.tap(names["all"], label + "-all-before-persist") + studio.input(task_action(snap, contract["completed_title"], "Complete")) + snap = studio.inspect(label + "-persist-state") + assert_count(snap, names["count"], 2) + return {"controls": controls, "state": contract["expected_before_restart"]} + + +def assert_persisted(studio, contract, label): + snap = studio.inspect(label + "-all") + names = contract["selectors"] + assert_count(snap, names["count"], 2) + assert_tasks(snap, contract["fixture_titles"], contract["fixture_titles"]) + snap = studio.tap(names["done"], label + "-done") + assert_tasks(snap, [contract["completed_title"]], contract["fixture_titles"]) + studio.tap(names["all"], label + "-all-restored") + + +def exercise_multilingual(studio, contract): + title = contract["multilingual_title"] + names = contract["selectors"] + snapshot = studio.add(title, names, "multilingual") + unique_node(snapshot, text=title, enabled=False) + assert_count(snapshot, names["count"], 3) + studio.close() + studio.open(app_id=contract["app_id"]) + snapshot = studio.inspect("multilingual-reopened") + unique_node(snapshot, text=title, enabled=False) + assert_count(snapshot, names["count"], 3) + return {"status": "passed", "title": title, + "verified": "exact native text after input and installed-app reopen", + "glyph_review": "required; exact widget text alone does not prove readable glyphs"} + + +def keyboard_diagnostics(device): + """Record only visibility/ownership; app PNGs exclude the Android IME.""" + result = device.shell("dumpsys", "input_method", check=False) + text = result.stdout.decode(errors="replace") + shown = re.findall(r"\b(?:mInputShown|mIsInputViewShown)=(true|false)", text) + owners = re.findall(r"\bpackageName=([A-Za-z0-9_.]+)", text) + visible = bool(shown) and all(value == "true" for value in shown) + owned = device.package in owners + return {"status": "observed" if result.returncode == 0 and visible and owned else "unverified", + "input_shown": visible if shown else None, + "test_package_reported": owned, + "capture_scope": "app texture only; Android keyboard pixels are not captured", + "visual_keyboard_review": "not established by app PNG"} + + +def generation_receipt(path): + raw = path.read_bytes() + data = json.loads(raw) + if data.get("authorship") != "model" or not isinstance(data.get("model"), str) or not data["model"].strip(): + raise ProbeError("generation receipt must identify model authorship and model") + for field in ("bundle_digest", "transcript_sha256"): + if not isinstance(data.get(field), str) or not re.fullmatch(r"[0-9a-f]{64}", data[field]): + raise ProbeError(f"generation receipt needs a 64-character lowercase {field}") + return {key: data[key] for key in ("authorship", "model", "bundle_digest", "transcript_sha256")} | { + "receipt_sha256": hashlib.sha256(raw).hexdigest()} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--serial", required=True) + parser.add_argument("--package", type=_probe.test_package, default=_probe.DEFAULT_PACKAGE) + parser.add_argument("--adb", default="adb") + parser.add_argument("--workspace", required=True, help="absolute authoring workspace inside the test package's files directory") + parser.add_argument("--bundle-path", required=True, help="model-authored bundle, relative to workspace") + parser.add_argument("--generation-receipt", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True, help="new local evidence directory") + parser.add_argument("--timeout", type=float, default=40) + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + if args.timeout < 30: + parser.error("--timeout must be at least 30 seconds") + try: + safe_relative(args.bundle_path) + allowed = [f"/data/user/0/{args.package}/files/", f"/data/data/{args.package}/files/"] + if not any(args.workspace.startswith(prefix) for prefix in allowed): + raise ProbeError("workspace must belong to the studio test package") + safe_relative(args.workspace[1:]) + contract = json.loads(CONTRACT.read_text()) + generation = generation_receipt(args.generation_receipt) + except (OSError, ValueError, ProbeError) as error: + parser.error(str(error)) + plan = {"serial": args.serial, "package": args.package, "workspace": args.workspace, + "bundle_path": args.bundle_path, "app_id": contract["app_id"], "generation": generation, + "contract_sha256": hashlib.sha256(CONTRACT.read_bytes()).hexdigest(), + "direct_source_or_app_storage_writes": False} + if args.dry_run: + print(json.dumps(plan, indent=2)) + return 0 + args.output.mkdir(parents=True, exist_ok=False) + evidence = {**plan, "status": "failed", "started_unix": int(time.time()), "checks": {}, + "visual_review": {"status": "required", "items": ["readability", "clipping", "overlap", "contrast", "orientation", "multilingual glyphs", "keyboard"]}, + "fault_checks": {name: "not run; requires isolated fault injection" for name in contract["fault_checks"]}} + device = Device(args.adb, args.serial, args.package) + studio = Studio(device, args.workspace, args.output, args.timeout) + try: + if device.call("get-state").stdout.strip() != b"device": + raise ProbeError("authorized device unavailable") + state = device.read("files/.octosense/dev-mode.json", 65536) + if not state or json.loads(state).get("scope") != "all": + raise ProbeError("configure developer mode in this test profile before running the flow") + studio.start() + checked = studio.call("studio.bundle_check", {"bundle_path": args.bundle_path}) + evidence["admission"] = checked + if checked.get("digest") != generation["bundle_digest"] or checked.get("app_id") != contract["app_id"]: + raise ProbeError("checked app identity/digest does not match the model generation receipt") + if checked.get("source_modified") is not False or checked.get("publisher_signed") is not False: + raise ProbeError("expected source-preserving unsigned developer preparation") + evidence["checks"]["model_bundle_digest_binding"] = "passed; authorship evidence requires transcript review" + studio.open(bundle_path=args.bundle_path) + evidence["checks"]["preview_interactions"] = exercise(studio, contract, "preview") + studio.close() + studio.open(bundle_path=args.bundle_path) + snap = studio.inspect("preview-reset") + assert_tasks(snap, [], contract["fixture_titles"]) + assert_count(snap, contract["selectors"]["count"], 0) + evidence["checks"]["preview_storage_discarded"] = "passed" + studio.close() + installed = studio.call("studio.install", {"bundle_path": args.bundle_path}) + if installed.get("digest") != checked["digest"] or installed.get("installed") is not True: + raise ProbeError("developer install did not preserve checked bundle identity") + evidence["installation"] = installed + studio.open(app_id=contract["app_id"]) + evidence["checks"]["installed_interactions_and_separate_storage"] = exercise(studio, contract, "installed") + studio.close() + studio.open(app_id=contract["app_id"]) + assert_persisted(studio, contract, "installed-reopen") + evidence["checks"]["installed_close_reopen"] = "passed" + # An abrupt process stop while open proves persistence is app-owned, + # rather than an in-memory preview or a graceful-close-only save. + studio.start() + studio.open(app_id=contract["app_id"]) + assert_persisted(studio, contract, "process-restart") + evidence["checks"]["installed_process_restart"] = "passed" + names = contract["selectors"] + evidence["checks"]["multilingual_native_input_and_restart"] = exercise_multilingual(studio, contract) + studio.add(contract["long_title"], names, "long-title") + # More rows than can fit at the required minimum height makes scroll + # acceptance meaningful; sending an event to a short list proves little. + list_node = unique_node(studio.snapshot, source_id=names["list"]) + required_rows = int(bounds(list_node)[3] / contract["minimum_control_height_logical"]) + 3 + extra_count = max(1, required_rows - 5) + if extra_count > 20: + raise ProbeError("unexpectedly large list viewport; inspect display scale") + extra_titles = [f"Scroll check {index + 1:02d}" for index in range(extra_count)] + for index, title in enumerate(extra_titles): + studio.add(title, names, f"scroll-row-{index + 1:02d}") + def scroll(delta, label): + node = unique_node(studio.snapshot, source_id=names["list"]) + studio.call("studio.input", {"instance_id": studio.instance, + "widget_id": node.get("selector", node["id"]), + "action": "scroll", "delta_y": delta}) + return studio.inspect(label) + top = scroll(-2000, "scroll-top") + unique_node(top, text=contract["fixture_titles"][0], enabled=False) + bottom = scroll(2000, "scroll-bottom") + unique_node(bottom, text=extra_titles[-1], enabled=False) + if any(node_text(node) == contract["fixture_titles"][0] for node in widgets(bottom)): + raise ProbeError("scroll did not move the first row out of the viewport") + assert_count(bottom, names["count"], 4 + extra_count) + evidence["checks"]["native_scroll"] = {"status": "passed", "extra_rows": extra_count, + "first_row_at_top": contract["fixture_titles"][0], "last_row_at_bottom": extra_titles[-1]} + # Typing targets the native editor. Its app-only capture cannot contain + # the OS keyboard, so visibility/owner evidence is explicitly separate. + studio.input(unique_node(bottom, source_id=names["title_input"]), "text", "Keyboard layout check") + studio.inspect("keyboard-layout") + evidence["keyboard"] = keyboard_diagnostics(device) + evidence["checks"]["long_title"] = "native input exercised; review captures for wrapping and clipping" + studio.close() + evidence["status"] = "functional_passed_visual_review_required" + except Exception as error: + evidence["error"] = str(error) + finally: + try: + studio.close() + except Exception as error: + evidence["close_error"] = str(error) + evidence["status"] = "failed" + try: + device.stop() + device.shell("input", "keyevent", "KEYCODE_HOME") + except Exception as error: + evidence["cleanup_error"] = str(error) + evidence["status"] = "failed" + evidence["tool_call_count"] = len(studio.calls) + evidence["finished_unix"] = int(time.time()) + (args.output / "evidence.json").write_text(json.dumps(evidence, indent=2) + "\n") + print(json.dumps({"status": evidence["status"], "evidence": str(args.output / "evidence.json")}, indent=2)) + return 0 if evidence["status"] == "functional_passed_visual_review_required" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/studio-flow/task-planner-acceptance.json b/tools/studio-flow/task-planner-acceptance.json new file mode 100644 index 000000000..ac7fa5495 --- /dev/null +++ b/tools/studio-flow/task-planner-acceptance.json @@ -0,0 +1,70 @@ +{ + "schema": 1, + "app_id": "dev.studio.task-planner", + "app_name": "Task Planner", + "source_requirement": "fresh model-authored bundle; preserve generation transcript and final digest", + "storage_file": "tasks.json", + "capabilities": [ + "storage" + ], + "selectors": { + "title_input": "task_title", + "add": "add_task", + "all": "filter_all", + "active": "filter_active", + "done": "filter_done", + "count": "task_count", + "validation": "task_error", + "list": "task_list" + }, + "minimum_control_height_logical": 44, + "fixture_titles": [ + "Buy oats", + "Call Sam", + "Read ten pages" + ], + "long_title": "Plan a quiet weekend walk and remember to bring water for everyone", + "completed_title": "Call Sam", + "expected_before_restart": { + "active_count": 2, + "all": [ + "Buy oats", + "Call Sam", + "Read ten pages" + ], + "active": [ + "Buy oats", + "Read ten pages" + ], + "done": [ + "Call Sam" + ] + }, + "required_checks": [ + "model_authored_new_bundle", + "real_bundle_admission", + "native_empty_state", + "blank_title_validation", + "add_three_via_native_input", + "complete_via_native_input", + "all_active_done_filters", + "reopen_completed_task", + "preview_close_reopen_resets_disposable_data", + "developer_install", + "installed_storage_is_separate_from_preview", + "installed_app_process_restart_persistence", + "multilingual_native_input_and_restart", + "long_title_scroll_and_keyboard", + "real_native_capture_visual_review" + ], + "fault_checks": [ + "malformed_saved_json_preserved", + "save_failure_reported" + ], + "excluded_claims": [ + "public_store_approval", + "publisher_signing", + "model_image_delivery_from_filename_alone" + ], + "multilingual_title": "测试中文任务:买燕麦" +} diff --git a/tools/studio-model-evidence.py b/tools/studio-model-evidence.py new file mode 100644 index 000000000..45b9b407c --- /dev/null +++ b/tools/studio-model-evidence.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +"""Extract tool inputs/results and committed assistant finals from local octos evidence. + +Accepts UI-ledger snapshot JSON, arrays of ledger entries, ledger JSONL, and +session-message JSONL. Reasoning, streamed deltas, user prompts and provider +metadata are excluded by an allowlist. Credential-like fields are redacted. +A retained ring can be incomplete: this tool records that limitation and +never treats a requested view_image call as proof of image delivery. +""" +import argparse +import hashlib +import json +from pathlib import Path +import re + +SESSION = "_main:api:octosense#system" +SENSITIVE = {"api_key", "apikey", "access_token", "refresh_token", "authorization", + "proxy_authorization", "password", "secret", "credentials", "token"} + + +def clean(value): + if isinstance(value, dict): + return {key: "[redacted]" if key.lower().replace("-", "_") in SENSITIVE else clean(item) + for key, item in value.items() + if "reasoning" not in key.lower() and key.lower() not in ("thinking", "thought_signature")} + if isinstance(value, list): + return [clean(item) for item in value] + if not isinstance(value, str): + return value + text = re.sub(r"(?i)\bBearer\s+[^\s\"']+", "Bearer [redacted]", value) + text = re.sub(r"\bsk-[A-Za-z0-9_-]{16,}\b", "[redacted-api-key]", text) + text = re.sub(r'(?i)(["\'](?:api_key|access_token|refresh_token|password|authorization|secret)["\']\s*:\s*)["\'][^"\']*["\']', + r'\1"[redacted]"', text) + text = re.sub(r"(?i)([?&](?:api_key|access_token|token|key)=)[^&\s]+", r"\1[redacted]", text) + return text + + +def load_records(path): + raw = path.read_bytes() + text = raw.decode("utf-8") + try: + root = json.loads(text) + if isinstance(root, list): + return root, raw + if isinstance(root, dict) and isinstance(root.get("entries"), list): + return root["entries"], raw + return [root], raw + except json.JSONDecodeError: + records = [] + for index, line in enumerate(text.splitlines()): + if not line.strip(): + continue + try: + records.append(json.loads(line)) + except json.JSONDecodeError as error: + raise ValueError(f"{path.name}: invalid JSONL line {index + 1}; copy after writer flush") from error + return records, raw + + +def parse_output(text): + if not isinstance(text, str): + return text if isinstance(text, dict) else None + try: + return json.loads(text) + except json.JSONDecodeError: + return None + + +def extract(records, session=SESSION): + calls, persisted, terminals, finals, errors = {}, {}, {}, [], [] + def call_for(turn, call_id): + key = (turn or "unknown", call_id) + return calls.setdefault(key, {"turn_id": turn, "tool_call_id": call_id}) + for record in records: + if not isinstance(record, dict): + continue + event = record.get("event", {}) + if isinstance(event, dict) and event.get("session_id") == session: + turn = event.get("turn_id") + kind = event.get("kind") + if kind in ("tool_started", "tool_completed"): + item = call_for(turn, event.get("tool_call_id")) + item["name"] = event.get("tool_name") + if kind == "tool_started": + item.update(arguments=clean(event.get("arguments")), input_complete=True, start_seq=record.get("seq")) + else: + item.update(success=event.get("success"), output=clean(event.get("output_preview")), + output_is_preview=True, end_seq=record.get("seq")) + elif kind == "turn_completed": + identity = event.get("session_result") or {} + terminals[turn] = identity.get("message_id") + elif kind == "turn_error": + errors.append({"turn_id": turn, "code": event.get("code"), "message": clean(event.get("message"))}) + partial = event.get("partial_result") or {} + identity = partial.get("session_result") or {} + if identity.get("message_id"): + terminals[turn] = identity["message_id"] + elif kind == "envelope_v2": + envelope = event.get("envelope", {}) + turn = envelope.get("turn_id", turn) + payload = envelope.get("payload", {}) + data = payload.get("data", {}) + payload_type = payload.get("type") + if payload_type == "assistant_persisted": + message_id = (data.get("meta") or {}).get("message_id") + if message_id: + persisted[message_id] = {"turn_id": turn, "message_id": message_id, "text": clean(data.get("text", ""))} + elif payload_type == "tool_start": + item = call_for(turn, data.get("tool_call_id")) + item.setdefault("name", data.get("name")) + if "arguments" not in item: + item.update(arguments=clean(data.get("arguments_preview")), input_complete=False) + elif payload_type == "tool_end": + item = call_for(turn, data.get("tool_call_id")) + item.setdefault("success", data.get("status") == "complete") + item.setdefault("output", clean(data.get("output_preview"))) + item.setdefault("output_is_preview", True) + continue + # Session JSONL stores Message directly; trust only the selected input + # file's scope. Metadata, prompts and reasoning_content are never copied. + role = record.get("role") + turn = record.get("thread_id") + if role == "assistant": + for tool in record.get("tool_calls") or []: + item = call_for(turn, tool.get("id")) + item.update(name=tool.get("name"), arguments=clean(tool.get("arguments")), input_complete=True) + # No terminal identity in legacy Message JSONL: omit its prose. + elif role == "tool" and record.get("tool_call_id"): + item = call_for(turn, record["tool_call_id"]) + item.update(output=clean(record.get("content")), output_is_preview=False) + for turn, message_id in terminals.items(): + if message_id in persisted: + finals.append({**persisted[message_id], "terminal_verified": True, "source": "ledger_terminal_message_id"}) + result = list(calls.values()) + for item in result: + if item.get("name") == "view_image": + output = parse_output(item.get("output")) + shown = output.get("shown_to_model") if isinstance(output, dict) else None + item["image_delivery"] = "reported_shown_to_model" if shown is True else "reported_not_shown" if shown is False else "unverified" + return {"tool_calls": result, "assistant_finals": finals, "turn_errors": errors} + + +def merge_extractions(parts): + calls, finals, errors = {}, {}, {} + for part in parts: + for call in part.get("tool_calls", []): + key = (call.get("turn_id"), call.get("tool_call_id")) + old = calls.setdefault(key, {}) + # Never replace complete input/output with a later ring preview. + incoming = dict(call) + if old.get("input_complete") and not incoming.get("input_complete"): + incoming.pop("arguments", None) + incoming.pop("input_complete", None) + if old.get("output_is_preview") is False and incoming.get("output_is_preview"): + incoming.pop("output", None) + incoming.pop("output_is_preview", None) + old.update(incoming) + for final in part.get("assistant_finals", []): + key = (final.get("turn_id"), final.get("message_id"), final.get("text")) + finals[key] = final + for error in part.get("turn_errors", []): + errors[(error.get("turn_id"), error.get("code"), error.get("message"))] = error + return {"tool_calls": list(calls.values()), "assistant_finals": list(finals.values()), "turn_errors": list(errors.values())} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--input", type=Path, action="append", required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--merge", type=Path, help="previous sanitized output, preserving tool events evicted from a ledger ring") + parser.add_argument("--session", default=SESSION) + args = parser.parse_args() + sources, parts, all_records = [], [], [] + if args.merge: + previous = json.loads(args.merge.read_text()) + parts.append(previous) + sources.extend(previous.get("sources", [])) + for path in args.input: + records, raw = load_records(path) + all_records.extend(records) + sources.append({"name": path.name, "sha256": hashlib.sha256(raw).hexdigest()}) + parts.append(extract(all_records, args.session)) + output = {"schema": 1, "session": args.session, "sources": sources, **merge_extractions(parts), + "limitations": ["Ledger rings and output previews may omit events or truncate results.", + "Legacy assistant prose without a terminal identity is omitted.", + "shown_to_model is runtime delivery evidence, not a visual quality judgment.", + "Reasoning, user prompts and provider metadata are excluded; credential-like fields are redacted."]} + calls = output["tool_calls"] + for call in calls: + if call.get("name") == "view_image": + payload = parse_output(call.get("output")) + shown = payload.get("shown_to_model") if isinstance(payload, dict) else None + call["image_delivery"] = "reported_shown_to_model" if shown is True else "reported_not_shown" if shown is False else "unverified" + output["summary"] = {"tool_calls": len(calls), "assistant_finals": len(output["assistant_finals"]), + "turn_errors": len(output["turn_errors"]), + "authoring_calls_observed": sum(c.get("name") in ("write_file", "edit_file", "apply_patch") for c in calls), + "view_image_calls": sum(c.get("name") == "view_image" for c in calls), + "images_reported_shown": sum(c.get("image_delivery") == "reported_shown_to_model" for c in calls)} + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(clean(output), indent=2, ensure_ascii=False) + "\n") + print(json.dumps(output["summary"], indent=2)) + + +if __name__ == "__main__": + main() From 50639fa2528baa9e6263ba603fb66eb3505e72a1 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Fri, 2 Oct 2026 21:18:19 -0700 Subject: [PATCH 03/15] fix(studio): wait for input redraw and close cleanup --- crates/shell/src/studio/apps.rs | 90 ++++++++++++++++-- .../studio/evidence/task-planner-keyboard.png | Bin 0 -> 204911 bytes docs/studio/evidence/task-planner-titles.png | Bin 0 -> 143763 bytes docs/studio/oneplus6-validation.md | 8 ++ 4 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 docs/studio/evidence/task-planner-keyboard.png create mode 100644 docs/studio/evidence/task-planner-titles.png diff --git a/crates/shell/src/studio/apps.rs b/crates/shell/src/studio/apps.rs index 62fbe2fd1..b1b4c6e0d 100644 --- a/crates/shell/src/studio/apps.rs +++ b/crates/shell/src/studio/apps.rs @@ -111,6 +111,11 @@ fn finish(p: Pending, result: Result) { q.tokens.remove(&id); q.request_instances.remove(&id); } +/// A close acknowledgement is a cleanup barrier for a caller reopening the app. +fn finish_after_cleanup(p: Pending, cleanup: impl FnOnce()) { + cleanup(); + finish(p, Ok(json!({"closed":true}))); +} pub fn stage_open(spec: OpenSpec) -> Result<(), String> { if !FOREGROUND.load(Ordering::Acquire) { return Err("not_foreground".into()); @@ -290,7 +295,7 @@ pub struct StudioApp { #[rust] text_next: Option, #[rust] - last_draw: Option, + input_draw_pending: bool, } pub fn create(vm: &mut ScriptVm, instance_id: &str) -> WidgetRef { let spec = queue() @@ -592,6 +597,12 @@ impl StudioApp { let abs = dvec2(n(0) + n(2) * 0.5, n(1) + n(3) * 0.5); Ok(abs) } + fn redraw_after_input(&mut self, cx: &mut Cx) { + // A redraw request only queues Draw; the old cache can still be clean. + // Inspect must wait until the changed widget state has been recorded. + self.input_draw_pending = true; + self.view.redraw(cx); + } fn scroll(&mut self, cx: &mut Cx, id: &str, dy: f64) -> Result<(), String> { let abs = self.target(cx, id)?; let window_id = cx.windows.id_iter().next().ok_or("studio_no_window")?; @@ -609,7 +620,7 @@ impl StudioApp { phase: Default::default(), }), ); - self.view.redraw(cx); + self.redraw_after_input(cx); Ok(()) } fn tap(&mut self, cx: &mut Cx, id: &str) -> Result<(), String> { @@ -641,7 +652,7 @@ impl StudioApp { ); } cx.fingers = fingers; - self.view.redraw(cx); + self.redraw_after_input(cx); Ok(()) } fn pump(&mut self, cx: &mut Cx) { @@ -679,7 +690,7 @@ impl StudioApp { ..Default::default() }), ); - self.view.redraw(cx); + self.redraw_after_input(cx); finish(p, Ok(json!({"ok":true}))); return; } @@ -723,6 +734,13 @@ impl StudioApp { } } Action::Inspect { .. } => { + if self.input_draw_pending { + // Keep the original request/deadline. The UI remains free + // to draw; valid() above cancels a stalled/background app. + self.pending = Some(p); + self.view.redraw(cx); + return; + } let snapshot = self.instrument(cx); if snapshot.to_string().len() > 1024 * 1024 { finish(p, Err("studio_snapshot_too_large".into())); @@ -755,8 +773,7 @@ impl StudioApp { } } Action::Close => { - finish(p, Ok(json!({"closed":true}))); - self.stop(cx); + finish_after_cleanup(p, || self.stop(cx)); } } } @@ -804,7 +821,7 @@ impl Widget for StudioApp { }; if result.is_done() { self.draws += 1; - self.last_draw = Some(Instant::now()); + self.input_draw_pending = false; } result } @@ -947,6 +964,65 @@ mod tests { assert!(!cache.borrow().unwrap().show_bg); record_frame(&mut cx, &root); } + #[test] + fn input_redraw_cannot_reuse_the_previous_completed_frame() { + let mut cx = Cx::new(Box::new(|_, _| {})); + let root = cx.with_vm(|vm| { + makepad_widgets::script_mod(vm); + super::script_mod(vm); + let value = script_eval!(vm,{use mod.widgets.* StudioApp{}}); + WidgetRef::script_from_value(vm, value) + }); + root.splash(&mut cx, ids!(card)) + .set_text(&mut cx, "Label{text: \"Before input\"}"); + record_frame(&mut cx, &root); + let previous_draws = root.borrow::().unwrap().draws; + { + let mut app = root.borrow_mut::().unwrap(); + // This is the production invalidation used by tap/scroll and the + // second (text delivery) event. A queued redraw is not a frame. + app.redraw_after_input(&mut cx); + assert!(app.input_draw_pending); + assert_eq!(app.draws, previous_draws); + app.redraw_after_input(&mut cx); + assert!(app.input_draw_pending); + } + record_frame(&mut cx, &root); + let app = root.borrow::().unwrap(); + assert!(app.draws > previous_draws); + assert!(!app.input_draw_pending); + // GPU completion is still checked separately by take_texture_snapshot; + // this CPU test does not claim the recorded frame has been painted. + } + #[test] + fn close_acknowledgement_is_sent_only_after_cleanup() { + let (tx, rx) = std::sync::mpsc::channel(); + let pending = Pending { + request: Request { + id: format!("close-order-{}", uuid::Uuid::new_v4()), + instance_id: "test-instance".into(), + owner: "system".into(), + dev_tag: DevTag { + profile_id: "test-only".into(), + since: 0, + }, + action: Action::Close, + reply: tx, + }, + started: Instant::now(), + cancelled: Arc::new(AtomicBool::new(false)), + }; + let mut disposed = false; + finish_after_cleanup(pending, || { + assert!(matches!( + rx.try_recv(), + Err(std::sync::mpsc::TryRecvError::Empty) + )); + disposed = true; + }); + assert!(disposed); + assert_eq!(rx.try_recv().unwrap().unwrap(), json!({"closed":true})); + } fn record_frame(cx: &mut Cx, root: &WidgetRef) { let pass = DrawPass::new(cx); pass.set_size(cx, dvec2(390., 600.)); diff --git a/docs/studio/evidence/task-planner-keyboard.png b/docs/studio/evidence/task-planner-keyboard.png new file mode 100644 index 0000000000000000000000000000000000000000..fa404b30a8054d08a858dc4416d8bf4ed29da286 GIT binary patch literal 204911 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%0A+p&0kteh|-vN1sU4^7r@8kifsp{wZ%PAy80V zG@89g5zlDuzsI!M_e((Z6s_|jQ)JRyr~p?&=9aiuZmd5=Uh8|O>9-W@}hB&%tWCo zricQ<_`I`4+=4w1H81q2Y7#(Azya}q@nBRUsn}0qw(B!h_{s-U{*qON@eoboT9wIC z+pg#By}7fB>g4WXY}iNI+xwfK^`5@IpB|nkehw@N z1MzI0^%*D2r)Av?PfaF+pz3NC%Q-B-6O6=q!)CGIBM^Y1yW#_FvY5}BuUt?(n}8#$ z+u(MXXtZq)Se1|J)d0v~4&r%!mFlh3lwZy~EvpdocrVFFsLeP&76YO(s!**E@qh{h z5R2K_S*L6DYzKUzZ%i$=Pi?M$vX|<|q4TM-jP>-$n-i|XwjPc&8&(H(t?M5YJtw1RdRo(*et6;l-m`sh7rb3=Z0tipR;AeC+%eGKVn9a_@#nqKleYL!< zn7E{nUPd{EOh~Hq76f0#Ol>X&Dj;>N!qfdQn_Y0|%x~L`U-wqjrpiMhemBPnHT+G4 zjTv~u3n;lRSe0wW;VG;pu)9;He{@@|agC0F6{?K`axfv4zT6Es9bH%4 zK3uAR&rwJ;o61)W0kEx~Kchc*$5CtE6}xiAI!Bt?p00IzSaY+|A(N509- zNAF!+gCC&4&nM9vE-A8f`CQFu*Th}YiNEneNYJ!BfR{#*f(KL|Ck>pL@SZhd{a z+Hl};-FwfMe1|?@beW$Y9vB+hQKLQF9%{;bP+bRp`7MDJGo`_~^F+4Y7J%>KuEjSB z2apUS)^NTN=#TD;)%W3oo%^fJj*D_GLOGpL?D>-9Cj$7L-%5KGZ{zlhd@uNG<5pHW zbTSP&jeLb&&+~dbnsBOgdg!qA;4-e>aziGV=)RlWFV)+%Yh-sKst0^I07W5lTX0vY zbKsmRRI_BRW%S%a>PY4;h$oTbdF|;Aq`xoS|8VjJDv-rmyu>Kz&qF4OD88qc$Vg@a4iUT)6!6!3i7+|Jc% z;^J~X_*3Tv_(n1XMsz%fg55KKZ$5nxlJC9zC~+p)CjC?FJ) zgI|vu@GwLh=wCv_5q*G@QdC0XOAy8#2oFaYpP7r18d^82ZJ`+?E!I5@P71NB5n;sx z(-UNNH_~xtJ9me*Ml3((-nBR=%E&MzvRH*uh7szI`0&5@e_LKp;6kki;qrw5d2m}h zYK@?vOYQuXXUZ?OmR3t6K??9_?9DmDHE@ZDLQP2}+kbDv zQEwR<8Low`TI=+LfHuwjK+(N^(tIhV_E=>JpBCc&cjuKD-iLFr9c`S5B;=I2&ogO%{2jhR0JaHW4sj(uA z=SX!&;s%Ij=VWK=t!E<>n4{N;2nxRb8W5|L5(oI>`|QQ@i(OUw(>5DD=#$>2yK^a4 zIB=w-q%2kwD)vw5&nR_{-l@M*R}I@vCvmmYfuIxbZcb)!6X%nL51n@bYP!hA8)=tf zT&#agIiyfSh>;G6k6+E=)@7@XXTN9`%eMFJrLs%oH0DeOV_^XuA)DprVJ+zyBr7B@ zF9x9Q@9{-7;;Wa9nyXdhuQ&J2_uf{!$xX^~`(T!Nrpb9Y1=571wX;~@9Hq2bm~%#0 zeY_C9HJH@ksht`bKAEo^{b0#}$6$A3^WeMzCgQZ)7`o^b_Y%tV4?C&ts-45x*3(DF z#idG^s+FY(2=CNkSjlPy={$iD0iD+})&N43iUxXFFBfldB4r@q>T#7d!k{DC@ zt}Qm4hcm?Hhdc2}SHh|1A3kS7^IcVr1xh#0&yf6c9D21tz(jB>DsoX#iP*a+N{bJd zG~Zyb=7#{!GD1u1Kc?-UEU&ib_e*(sBkVnotrXv^N&Oi9+mL5I#6|$fXhyzJ>vXrW zoVw+9vF1y{C#QNn$dhtWa*2eKpgV#TfRnftwn@RZL;_l#_?LT=0?S&SBX#!1V)ioT zd8T zaJ6Wg*ss;-g3fF_>|k~;h?9D!;SB!|9Xmh26lic7)9KoCEQtC<=rx^Mmp1IUts515 z3_xo|>Q&Ws1FP);r%Qe&EY`EHU{dh|zYU}Od>}j-`26z4v1DL1;eyMbONyJLBv_8>nQq7ufh3>=pze6M^D-fAo+cUKEDA9W&h8zH1-<HdnGS$?)AM&sV;@MP9vm=GuBML2-#EV%hK_Wd}eWP)G6a`BAjv;os<#j~e>y#4n3 zac;+hogo1S)rM+IbnFjBi8trpZ*L4oK`7Sx*QtwmY!ApDC!+y)guGJc>D#@#GfnEw z7rTohOou&D6!A~JREgT!Ni0?wH|31?6`UyG_ozf%fdFnMo;_KZUYP%N!>YBl6^}*d z_twK{G^Nhjy?p6&iq)g^9q5Z2b$7Ry2PH7;lNybu*W3=Qg$^Y2st-~y0B126$9??oi=Sc>^j&I}OE zFM9sT%F#Uwh$iNv>>~vrkzw#XscI>F1py+-3`C4=r)$d z;fi52@bH?NdJg>#ND6q|FFNC0EHFF!W2!gnapKU+MI^f0aSkI%+MGAhU+CQ;bP2gE0dA4sc0q9eEpe)5m zox5_S#W+HQqZblP8h+sL(=|T5mV(n6yYUf`#Z2xJdMQA7iHXqif6*o%#!;aytHyb|euJ z6vPjCdK|fms@@qX=KUH+kL>*n)j`#v|NPCn%x=;;uz~u}?`#zZb-jpviRPfaCQo(T z2#;5fC3`5ICgJI_vE*elIvg({BNMWG>BaaEW!@*WFvyY2;ra?ff3J9YuU2gm1P$|N z5~Ik~jtdSJc0KbNEC_j$CgY_jsmKeGr%abhnac&Bpt2$RVV)2=b#`&C(|PX`sQRm& z@Szm0)mC_tFP&%DE4V8WmFju54x3EX$1~Y?44(EoNgoN(SErT*|9oKh2-Gi^YFojD zU#9+CPtz#D0c5Le^4EvjPS#d%ZSF3pJ0k~*%|e*xIvpLR^R?=7n!6GRv4V-{eaKN$f$C$|K*c*9fp|Hl@KlgL2!4&_2;xD zPjq~I)z2b(e1IR@?dm=;&yXMy|J?7!8`$Q?p@VIXJ21(%zL$mc_vBSF}702+AlmrwRarX%Bb z)V8gG(&60;vuWviKZ33J>K_11{O05l6S2u|#*0FTrcPMY*trI-Fyvul8 zNJ%wTN{t*UjnA!EEM_xkf4QdC^$o87WjvDUNL8_v?(mV|USgP^KYwDGI&KY^dT?#a z4>}462%J7t46jD39yru}6ciR-OBvNa8QNjwu)Fw(Li&UX|IF>jYCiRjf?1%vFs+2N z-eLDU-1VeLliPKAc%e~L&iR1b2M1|pf+ohTL&kVKUj7OJPC6jN;cwl1CtQWeA*};= zNhiDC;;%Q^b2a;K&B6q<34@2X2Ka4kN*4`zd5PVxc7lL>t9bUK^9=(K)yY>OI0FlO z+TM7Kt>4AnwmeLF+{_F&s-KDYhmJgoH;jo!nMMk1Y5P;E{vxh#*L_Xdp0h>Ny%<$x@r-pS~8%S-B8%Y?;W7{R>fTz!W zfked6uLz$uNeI+4l*Ho+1=3E5=C}a;OU~l%fwt1#PDPal&vAc(f0@to6bRd2s0E!5 zXPf=&k7Dv8InJbuu@nI@C@tT=R9et@R0bqx~c_rt-#WdH}_&U{XU z2n0&S9M~2|)^1=RR0{xY%Du}?sHx?p;aB%=_(LUzUr*ntT0J1{idp5u3R z3I+!6jfNA$AT^I+01&Yf0ePCxm;yvf%ug4t-*-48gbCT6jF1ybVrA-vM@ksTZ==?flnD%W<3t z-aC_TB=C!#Jhyd6D4$5{l=#X&7GAt$xNDue($_w9ht zQ(8A`89?i8DYt+@Vys$T2%DVB2nza9EXhe&FO;G;$KS(#2@aUicfD{1W+b}@X87;w15ay!4!{D&H(0Rlr zj7s_gI0O|Kn~bkhT`o73EsEW}qwi0j{!qVp^Cm{3L?I7jYarPdin!So2umE+ahi7Qv|F6AM-~Io=HUhFs|LpGnf)xB8ZqWa} z=hU)y>{bkTD~1=}Yeu1bYj7JIk$hUAeAC}utGNsqosvfPBH0(et>ZL`x07H<8Wi$0 zq1W}=MLZYRj@Q0xdlQAr0u6vXb-n7+UZ_-u>g;ze?epD)OI_|2-$kv$ z-`1(t_4M}9?FrbvIS;wbqWbIN|Fxg2cjx^@(BXQ`!g!udx+nGGU!-mg*b<_m6G9FK zk~8?Q6bC@m|EF92OD}QXdq6d>IT}M;+q;*0LdSG~rm?BM3l_tHADJK)`u~39w#HtY z+%2n^GDyOtz{AizP4B2^aaB!ch1`$^K;4Z0GQ)ql-OJ=_DIgjsHh7IlC&(Y+O57_K zBMT%rDGjm>+bw7o!foQUi~k3C^;&z?VXd5;v9+*|1Snc)yi7GA5mw%6Rl{JQ-}#e`m!>&VgflZM9t8h@v2rjdF2Wt@!ChN|LfZQV`gh+ zUYyt~C%S~>HD^q2tvOI2rmZ;l|49ZvwNY}Kof)mKp46a1`Ib)Tx1Jmfy|9t~{Yv2) zpZBjq1w=qbvtY>Y5y&kQv|$7Op+x@}$p5Zioe7nWA6sMxrhA+OVS82#KQn4yYoZO5 zeDC}prXk_QSs61Lr)Wup?aYjVJESex-i4g3HoUVRb}&FxLj-d!PR-1WnIo0j8Y`|G znVkHpV)iu{41Ns@pKiAqoU2fW(HIt&9D?^dx^Dup9VlCXN=wn4aPFV|U{*e@2v-o$fDxfN&%r>8&6T_q(W z^UiEK&(I!!OL?`gudnUa9@l2Q3;9o076k<_%pi$H@R*^CCpINOJ!*1)8k}Y$CvTH2 zlY>p_XMi9mSxM3B4X21$9Jv1Zv+za9h|*H=q$Zv{gE2D>esAAS6#eH za!8Pnog7nnMFlzvyf_{FG z`A}ZIc%PP>d27htqI+>_#L^cY6Qj=I`tV*VmE(mYcyf8}$kn2I!ql>}9+q6V;6V&n zgWZ#KikYin6a!;J^C8qXZ$1}@kO)`p$)Z?B-zqXNjWL%VrV(>EZPactJs)xVV0;k& zt(X)*+|0-XL68Q_@l?s9Jkuf2$s0u4O&cFypwUojQ0vm-eX?4PgbRKHGzuWCKp-6+ zZrOn5PLGqVG5$Q;a9mnle2=_R0vGs!K}Ubx(jpX^@VJRR{Q7Iss1bXnu%uKv)w%^FjBfkV%y|Rf$nEul6d8IVR_1=!4HPq=O zzSH#GfzPxEK^SLDy$2)=%w9M&!^~&jZE@UoIt#_Zw-Yz|2N`JwR zaVKhGF^exD?ifpxw(W|JTUJTw$mUQ_i?95Ow4_GAVvm(5zt)VPDgNx7j5Cg;vzG+G)v z+8Se=A52i3ju+o#R+;KdmB4dL@? z3N-f8T&ee!Zs{zRN+;86(J?ZFV)*)2pBm3`7ZZzm8eg>Ark%6Mefo|1%geqV^rVX(V+6PF6C7X|njBiMSO(mW(MxC2m@(K>M| zsLz%~(DQ88-B}OcyW9(LXv2v)!)-CU>o0f)`kgJ<2&=7Up_2x)Rni8;RVN3HY%cbf zaTn{I9q;f`q_*vz2gF^uYmU1*8$AN9NW>OLnK|DQV{ikH-Vmjyr(0bQHa2FCn%N9L zIyZPck5Y8oZcVV0)(Z*>zpJY;XGVkG%KEb|Vo&6yd?_W?<=4B8PC)P~vkXO@&C1aQ zV*8+i(iKX0IA+fP+B{fnBE7u4^8R#98vjjJU-)z8=FIvye0pEp7KK^R}7>hP%4BE2pGj z=B66CvZ$gP?@@oqbh4}G`b6(IO6*b@=E3z$VEB~&T_`n5M3d8HegANKV~p+X8FoN* zUV=P92q6^k3`h8`v*Zsc<7eTge8P&{__$*tZaX6O5aMZ7H(yukg6ugjIFae%l+h*; z_MB(&gPGRNr-Zn2`sCsEyy%?6IY3)#@0Wz)IgR1&{$&4_Ou~x@CWJbd`{FV068D5V zPT6%d$OCn87`Mg1Jc_@Sl}sqae$YI%`-RbUPtecb9}23i%^^OK6JM?9al2(YiFXLN zC^Wy1W<$HtV0wzI45llbbn)g|8R%~yKE?DpuenpN^)0(m3=?QGd8q2S345Fp4xkTz z78Py3R(5gWFd9e>2OdmlxKG6KQ6C#KKk^6)CTxJKp){)^NNI3rt9`3~GNQjM)GZYh ze0ex%KGfJO>oC|Xnm%b3HI`G;?#XDWhekNvRa|tNlRU%#GVJK=CS#@BOnT0`j(TWD zc>9+BUhp1Vn_r-j#Du^A($32-w2NoVdJRmjzMd;Uy5(ZjO(+czUbj(&?sF=PlVy}_ zl*+~74^dGYOH~2!__*g}E>{9!-?CHlFkxRW{9G272S(9 zm{wA7SllI{m8`l*Xz7igyO1KUD>`)V<_2jH4qsepPe`Ss~D zaoWTAMYZzoMl4w^ul{t&;-2|tfmg*iTILV+g_TH@(Y;x!R=~N#p7`k=Ql!bf+n7jd zX;q~ntL21fIbYQ{Ya(7!cP_8cIAgOZg@ub7Qd_IC%ooI7!g+4SV!R{~cT}PB=_74b z2}^w}yB45Uh^< zvDp?g+YPg=t!?CLfA@k!S1Xv){VsWN+H$@#Q~kQQR#oGBi0f@`VXn`lnLrF50`JfI zbp4$0Bko#;s% zQG8sfJ0||1IVNe?G5|i#k$KXf|K1)!M`vAWXoM?uhVWX?W?;{oAa1fc@K-~jEOFTI zUt0D#uqa!b-VaY_CF6C*MA?>9mj|n7>j`nsfFXS`&sNGtRcc|a?QnGqz{32>8}ifS zzGm?%8sB9wI}nZe6+<%y1{UKSp9t6s;^0S&&Q5mXqcCg2h^WkfO!o*YE8Z8|Q)PbT zj_4?Q;Nw`pE^5W$c5%Y!u(yCN&rC%@(|0Ne%{kBy4kzL>x^~p-MlsoLWugF7;+rr0 zj3;O^X|K$1z1rWC!y%o4V87J09VcM-3iO=E~ zgE8>6grg&hr5ygNF?W^3Jz_*PIW@Pzl;M?k^}YaL8(?}Nd-w6;D4OUux~%*xJPK=Z z6H9``M-Xid_m{6(a=H(H%7TkTsICv^D@dy(^Dilckp*08?c{gFI0vVblO!m6*WjRl z$?FX&@4p--NF-b%r0toMeP8=!j4QpPU6`n*cl7}kY!3!0>z>x&y!*s65Z6w@j-^#+ z<}61zVsi%rs{~9Hk4-OkaY@z{mwrvo3!0pqLPlMFUuFT1*(%07?NfBdA0sRr&4zH5 zpxLRx>tz_Ig2{?#*&b%2idjIJ&g_dpQPh>do`gM` zY4+|0%jYGSkF^f7@R?-BYI8j@1(RGBGjLGYH8iE}(^}fdAv`vfvj@mcfVw7G7K(gh zwCF&S0CsEfpkCSU{PMo^T1Hm;a`g&!QO*=DZh8neuy>Dm$wndMAQ!$XJfmxVI-!tLB1D%>AMK9X6Cw0rq^&k;Ov1 zB(OMbe@=gTJIDeQvMNk7Gc%i}9k{*yeRRNPuXjc81411!VX3vv#8bVmIAk+{@qxK7 zA1QL5h6<~9WFpAQQJ~7XXbX&LM*|vUb72Bn7c4LhR?h+IUMzCo||+WLCrNHcM;wFmQhDPUyGo7DmUYsODn zB%*4Hf7OHK%*!jB)Y0_{zKRRJ0+k|Xc6NpsV_v^);}n_zT@0Ve!bYEU$$%-nJfpl! z&zdR0=HvYvgc6O;1z2Fva1_hj9y=caZ6bh39mo0mU1`u@4ZIwN(q;&N2V#Y@ESNOI z=4AcA<@B`!XmYySEpAiLTYSkLA}*t)M9)ba@6m2xry)Xuj{j|!Ih)OkI3KYyP_|uG zUek9xXNawO-hxeoBQD^Z@2#o*Z`Nh^7mL+IYD^3V17r&~wLJ{4-I0KEv>go}KPW$+ zreTmyng|^9Gbt;+MyI;nwtT}qvn#Zt&#>TNjviMdD!FVlnSOvhM@!B||C_H`EM~Vf zxw&?=rn-j4C!@iX-UKLqqmEo%JVrAWph&HwW6zQ|T6aW>iA95!1I+yuQOiw`J`7`r zQr?;pEDHPW1P2cf5x`w~QAuK&ScJESnD0ySCe4rtc%bI14@%7;PDO#RDBeHgGq^2o z(0bM3k%^t$!$dLS=qadhC?P#we9gx&?N{@zx6=zb7P2z0eh6)xlkVaZjX zQ7KX);&Yfddej5<$H79`ysj$Zt=2Yu%8O+?m~UDx>i*$pf=*m?V7s%CJlBtJX7=L= z=Vf|f04r60I-J+Z^3H^Ji#~pko%q$rt^*#oE>d5=^{nEdbP7)xVE^n1GSUuas{6e# zsj*AlHq{#3i*Xk}+XD`HTf#W9ci^Bp2fP}c_v7o#YK-o8Y~XJ{JjBg}6BkCC%E~O! z0Jqrlk$pLumF{4bzuEcV#8!pw%Kf#0iyg}+>f-$Tx%8U}c8WzSRBFINr?T&%ZVKj>tASE)v4G?egdrj`ww2!GLO?MAG@BSSw!xb^+> zKezy+6nPCBO>)*xNu+tA&FB~yufl}aOWS#1YtGKpIa1sKSI+rs`9@TLT-sBBzny;) zxU-X+9-aoG2%QIREmSHHFp>jP^mx>UMFQd+vZ8$`N8q)SRlx=Xqnq@}xt?tRVkyzjey>;0Z@|Ms8T zH7>^)hPm%6&+|Bs)YoZ6AR(^9rA zgp0a-UcRF8MOY){-4|V4?yU$Fk5%fa5^Za)9PMHi{c9~nLWX8+mq0Xk~Xf>d(0(Hk^uE?t{_?EUPjYe&+^CU7)LDjo&pg>yk<)#6`7U|(P$S^ zsq`<;5V@`*1l1$AzL_YgC~;P6)w?_c1IMPe=Fga6Ln5HYGy4+WBZN$viOR)#W@rKI~QFR0C$pV74{^7tu$4190awB<>YvlPorj^`Sm?Xm4sRMe{ER0TB` zoV)7_$J&~skh=m{01LRM?+u6;ZAOR-i;9Q}RZ8A^RuFH~$PjdM5x&jUYN&RCkI<;0 zkFvj|e3f+frszk}uig~R22K#+`ajv0#gcS&UaI)n>5f#2t+!r2>12sT9bTcaDszv8 zM~a>*XUrx0`eMx#>}=u=G7kD0j*c95@@eJnMPk`+!En3dR56SCx`>FFWMSj7Z-&8~ z(l1=PxFi$Kl|1NlF+Ww2aO&U=Z30t0vwkKlX``i^_U9}W_W`@Z+6dd}Uc7;7BP&5T z_vl`S03?QeiI!C+*DZzH3lp&dZN@XI;O07x4k`p@rjOJ^M$tULM{WUrz;^UPs&92Nq_}y@RbY!E+)EH3sW!ivbCc`uEi2=%=0NwkT zMJ}8KDDvK{ljTx4Ljp3$pHxk%)Z-QzMXe4@1J`Lm?Kmz)OiBdxH37%ib?%ButUkl; z&d$%%3d61B=!n`&wF#x^KbK`#`IelX4PYzb`h@1=b$3I)`CD>=Gm)vvv>g{@20?(J z_RHioITsC{q2any47L5%oy;D-{u zOd97ix3n}&*2jovH4HXjMP}5f$Gk%{ddbO*ThrHXyJr%ypg&>8aZRs=VgzQfL;{P} zS50XEvfG>w)Zvm^SAYBX=O~**!2Ko23{r%g+e!q4SV*s2ugW7~IsLb?Y!3l`@?}&~ z9&CUMf&93#Y|!NO%KP(AGuT|D zaY-S5-zkDcf$Ls@0jpo>M10{MKYPm$8NuA;iBm!5B~mbDPw<9?xg*Uf%%W8aJ8wnpM+@x56Am^pW__otH1Gz#acJ2M`)t5s zISuZ>JU{r6==SrQd9b5dF%x{Rci0aDJAS>U#~8RLHgjHHO1r~q-AS@Cfo8`;@-!PP z$}ZU#pVOLRH0!xInBXbI85+I^v2zE}(&kT^$)xdO(0RJ8{*GP+^7@d4JcgPoX_L^? zW>bmXWTdJj4uA`M`LfOGJ*t@^V2{SrE%&GEOfS%#oPuJMh6(<2AlipLemAIQmGRxqnF2^X{WIGUPaLvdevh2W*3T1x!Eu_6G!Gygh25CkW?I{5dpFTnML1OH z<^U98gPNMS4@w;C2+8xEL7cZnX#TSrU0b*6UG;NQz;v{tV^a?K+;wQp$47#JbLtOnZ6ipt92&~m$p z>1pA-aWYQksa-6^l=V)0A`$Gk*975fWtJk}!+x?73!PyGiDiqud&lR~-HJ(9B5Ofd z8ElEQ^(hLAAeZ`>83N*-xcZTBej3qe2_1f{iodZYn9}0;~Q*j zxm4b8d7Ad^h(a)pvYej$;2<;nY?E!H^!yI9E1Cb{&?lg$Hpe*N$l@l4$go}%N@YhfN zbstDk&P%T7>0Z!8;hTYv&dbF$ODL_N*a%0%ZqI8czagbf$l>-qU0hg%d)w zt;V2V3}5n#iu^}SMAXz?WMyZA6^w?G>g>(ZsRLMa;RNRSzD~Hq8w-`v)6?5PMcUo3 zL4=oC{sRd&6ds5@LEZn&hY#qdq*1`Tixz(4^ z!Zjon_M6NzGB<{&?X!-B*e~$$ z!$AVdhLDqEh&G*-MIP;Wo2|PfM>KdLz99N>zBhya!Xc5Fc)*mzs8x)z7bt(^s!Hy_j5?SRRM~yXlsn+^o;6 zY6aXD?3Nf*J*YG+ETr{M2pp2qR$+A?2jc|M3H9y8s_;Kb6<9>J+9Z;Eiy9>7bGKpi+Ay| zUk8Yj4LmN}ygzYrdBiyFPBH1Aokg(fzVY5VbL%zljvzj%U5ZJjLhA%B4?n%K_eix% zxTEdgl{YEoMQp{=-*v_jWI@rL~a<`RK}6TGLCu#qdOh|0p%^=twwn1(3y>iAq-d>1@;( zac{5Wz1xJ*1%Y-JF7i6Rq@>MS+XZ7-@I)9)=Iq8s&1Ho}*I(`JY?jwxD9wCC6gY5x zxPKh855S&6FLYvl%9%7CirD@LP2Gx_zebl|^Fjv36YevH-@b?K?9itOdLkfiu>6V{PH>wahV{1d zxyMY)f?xD=cr<`E!Up!kWy7X4UXpr~8ETr>^~bgKb-J;oRIB;RvY3618jDyh#rc?G z4Ps;K`D)J_w8-m9P$;XGy)MfZkA&tAPjy&uuGX|%5o=$bg)*x;Ye3iW`j0*z3Wd+A zr#s$glWX)BKH9xaVA85r5rCCW`>e(5POSI*V4)%N!RL|o znXJf~GzUkO%XuQ{GPpt6QLiPpU1I*Lj*F(GD%l}o?kKPw-k~vTK`r#&D1w>ia@2s1 zthn}E4?O~1=;A08gQ zr{!B@T9aG-q>iI9$5iRuOLx!xB*)9a&=x0m_JZ46>GBG91a$m06`kVnNwZFC9gk-Z zcU$^-7gQH=Vl7^tqo6Y&Z(Ln@03zk5m?zgSXtev)(q-GZ^v{L2i|i;{thv{5g-*R= z3zBA5R^`v1tTTi4B|3nb%w5;Lm0kNWIi^w_6_-UjXj|6WyPr4pr+jEIa33RvlEpdFIfIK3^Tk03yVme=yY z__g#F4%ombyVZsuUBihle&KIC|FJ5TAyhyfoeJuPn)h|gGuAKhUDAxVS=FsYAbirQ_g7+fOV-Qc^zcQBT_J6jDL&)xG zeEMw^gaCoRf2#pp1L$5?uFrNy#7|@c#y|vt=W=kRBcXk>&XU%hm+rAKB4|IQ<+8ia zrlE9d?edtY@ph~*X8P1kVA1oz^7e|wyQ9_jv4Qs$<%L|Ad1;HEcheR6jJ}tS z=M~36H-;`?g2S3=d5H*>r*ZSyeYVP6JN&{Y<=3BF+ zW`X*6Q2Io?PES65e>TsL?Z!asa*f4dMdBge)LWLF;eU+&EV2<$H{X);fDuc(kS% znGL3eFA18$NMegkvw^##BP!aSbIUg!6_p5CLM~}-jh{AN@4c@@MT%9Gm2qI>qWAP(M`)#)o)!x2+B*!;1omqDqdPUp#VQVBUZsKv1o1`^~ zcx!#tu3;W(Gjz89g@94zoF_Awj7GzWRIZl2F)S_)7LZdA1EULG%a4>MQX+ltjiu?+ zj!h0;9`Jn(?N_9hjqzqC&w6vMx1CV#J}Us-YInJe4&>jt@I`F-+@0 z=_sL{BCwe$*`befs)JljPXcQ z!)f5aVatW}L45vCQIh5aM4!bb_tkCVW)^qPc7$!LUw&^#?$>KfV^1sPAmAf;3xc1r z>qrFZ-OnzkO!j`W#q7IZ?AdQ0gC)Uys>t3%=ckSD8qNLg6z?Jf4c|?M0bgWbyz|z` zr?s_|sK)cX$TVA5`sBFN=Is}+O)}Q1iZyE7=UH5Hb;MfUp!-TZwS!0d-1cYcReVyC zdQtqPCnDrzWANC-LPVsw+I^a2X1vMkdYshVb5;^mD}`>Z}>pQ5fPwCX6UuZ9P}LkTXaz zoWc`|yvf2{8A-$g7fbb(kXR_~GuShk&dtlzetIUak`98D>M_E(_3?4>BoO{k&G9*G z{W*We`61U>F4jVbyn-^oL%+?>)t3Y=nzbu}nBoRuaR!lPkeJ6q#HkCBtC>1Wx5_sA zbngnR`vyZ%X}$X;+lk`_4<_2XU4hAKnqm(qr8gtHfP0`3N>v)4kg)pe3x8>PJDy*8 ztAe6pXp?)6G5kOpJ92b%iKUrvA8g$KvVrXpZB^CHvGUJN?nDVGVZ>Yr!NJ6=9v!S( z4!nW8Hy8WXWlN#1cvQnyX}p$5!;@b>RjHSOC8^gO8IS46EdU#WLd+{te)p&Cvz4Wc z^df%do3fDnDsUlcwGwU>qsltPPOgc4C0b*7U&2X+(6s8Ej$J%JXM9jqrDjK_JDbgZ10@h}v`ms?=DhWP`5!Y0R#(+~Tp@vIm)h0xPwS%) ze(`R(RLZ%+LUWtCKjq}ZZ<$Vv3NRGR<9H|(%NKN6bsq$;!iB+Md`#teUj`HT<_gO& zFs`Ju7e$qnlq9qXh2GvrYRYrpWM7352I55>o{^4_E-x>~OwaR}{7MHcP3B0@Whvn1 za52p);Wuwv*5JfrMKU`9)H}9Yf5-yQbKMPO5_vn9uYqjS_GCjg&$EBOh0Wpj`@Bf= zL1>B$-R?|b9{F%|)QFs&3L0d-)>b%r^CkI**^x|P1LKc9vP)Jw9l==T@N2fK-EJI! z4X9&#zW7sB-f)p{abvG(ha)29R5+J{6QG_iAJ zgb=TjU!j8D_d5gtO`&y>P39CWZv~>4pVo(7GqQ1(oXLCJbnM29Ia@D+gW@R9ya*um z7h4Eb>Q|BvUnr+N2MfJ|=TodO$ruEEBUIA0IF1DhSmCNS=Ya zsVV+|QE0mOczFh+&BU~{HS26{_$e3M)$|`SXm_U+kIl>e%wp+jW(jF&dlS-01Nn$K z6okiUBr(is68kNg)4PUC-#&ms>Wh)hO*(oJ>;Zrn18q62tL5NMRk?NRx|~lkhdZ^N zeolf7xK!|ywvud+^ue?Q1{+YoPv-|mOUyjuMfiN$;r|Ay4-OH&25?ejQn=eP1uhJj zOSJ6g=RWODS)y6mZwv&e6xy1|%j~_ZtAGv>M~ELJ4jNe`(EgAaPN8m?W8wIz=2to^ zC4w!J+mk|I1Ug23*2u!tUoi2F^ob%7y$XJ=;*PugcP;lS6`U^Ah(y37;~kf5o?1;%So~5;!x=2O!qDP$@82@=nQPwKF6oJv;Wc9 zI8QT7qx$EMFJ_y#)nPLTL!^d!&?_@Ev&fw%OdK7Nc|~ zPOm2OO{+k6qrtRbSkl(q;CvX6*-jLS-QYY&+LTbWWNRG>P@_!6O*y%4IUhmw>!9{s zxrj(Aax=>=`4==FpL4!IL4!vvc=r?{lg{k9f`p;K8qGJb3>YH-^jH~o21~JlyPh6m zCz$q%>huD=FEh3Hgd#gQ-hTWeVZ!3A-nZVbddrO&ldV$7wpy}{_OE;uJ>wHaFGt}t z?>vQboQ2|t?(O1_71hUxMZE?w^AirUrv*&svTyXVPF*)ZW=vbxoA^OQewC!#+M4GqN+s;>f7J%H53Hg=)Ye;zJS%%hsa z;*)w1z%T*VwKba)x>w~V4ev$tj0c?f@&pp$>FHoTbWEh#V)gJ?0Zi53N106LOpYmw zU`;0Uev!uKE@x*pcZAWdv6t)vES&qa`8=<~NX6fYV9y^U(KWr@N}4n$^hP%{CKl03 zU48Z>6qm_{S3i25=C`)9$S;YVL`rw2!*^*}!|O#YYm2=hf_Sdo7S(^YqV;0{J$ZL` zbF`Kk3^K6A`f9TXe9^Yrw1c*Qu6bMNIN!DIB-TL5sgazUhqIpu02uHZ(^*lhiT}F<#w8*oj%sN0ep;!aTa0K2<97`{5stXi(3g4Tm zTL$fM7!xkn?EoHZ>CH<_h2b>G^21T*{g`|FO*-Sdv+dZdE&FI^aBvY_gQ!UQgrFBm zxH;G%0UxQFfsx4+h3RO!bwPR;LNAkxeIFIfM0=CF802Qf9sQ^SBNqJ=`>)ShlR`72w0CppM zL@ySwLh#hwTe`n?{w70RdM)(Lv!}3I@$+)~_lxn%_2G1T6G9;`B3=g@Pvfeel^|8J6BHUwA87RE&n=odCNaDbZ{4`tf`NPVA%3h_ki2 z`#biin6bIcrjio0NTKuZYx-+~`DBMCRRlAROTa8l`^@tol z_x(7bcDm6=5Te2mh+cEIrp5Kd#fft7yA#>c8?`bIcqF9n7cGq-uKf%RQhOPMOa%74 za>aMBpc`OZJ*&ZAuQPz2QOGzDOEO_k8!2sai^M!`t6?O8;h&vTCGC?MBd}DpesiTRJ%yPq29J2l5Af+##}JX%Ff}M5cEFq z)7wpz#8=F%7o^Z{`vw2L@bfj(*O7p}0TOs0t|bp-mBPv5doUki7`4fKPf9)qNbVoG zQAr;bA06-=F5g|;?<=&V-ySO7o{)}tv(ivf{<*(7Jj>UKxd=@c2%L|tvl0R{AEaUa z6NOx}6b_cpQBy`q-pWK!)j~ zz>LlO>eaO*Gnc94!=pRHygm*NzwUOo@8XdMFgPKh>3$jUp%Xp*4g(Ms9SfHL7^r`+ zcLnUn^R;dwi206h?OV=uzl%8Mf*PV^bwWade*Z=>n0h?p6~>rik+znlc_31_c6nVq z*P}pTXe`8}Qw{fUv7zx(O_O10b6u;00$qr@VJIcP3Y4!pLR;b=STBLUWoJK2(+)JZ z8PLzc;MF;rXYrnDYT0+E0ykySP;u%_g^~LLCg1Gj@D;HMG*_y`?khHCI1jmxd4w6la ziiy2sx33=$r|b^HU$=b#M7nMb08qLM*ozsBSlLnd0fW-p?P2eZc67lN*4342u*5e) zq2{oOIKq-m3I-z?Gcy{!ay$AEK#{l|wtkKe=E3kw0LHxo^*)`u!spLun3!-t2K`=X zjT^NxD+^BO@}7OftZHkjm_Y{AV)wQ&j&sl7*;h(+N-P6r+Mty3WZJaz10ESL{M)92Sj)WE;Sxy zjo*}LHjiCylv4FJ#R@D2g%H7zsOr|yVhwQGW}fT(MaJ957^I{$t|+9CFk9O`)?N@( zb+$a5kTgHs8LMl4_8{MUBDa=eQ}_UQ35^%`d@nd=Pu}5;rb^_EFGq`$b}_KDP`gLY zSU7PXgtK$QWm`5>f8#KxRw=oaI!}9s*_{^)h2c>N+V{j>w-lR-Ik2Su@(3g0dT%82 zF{o7dSD8=Iv5AmpehCOzx$yQNCIb)y^ys)>Wqq^#&Kr?tdHMU4m7`d9mOP8(?*3FL1fxjHfKFs1m@IC3bPJaLb)EWjVU?C4|Xc1|ZS$ zLN_nkQ!I3)CT0Qp4|Zc0)E3v{>GI?;_mvq^!Eo=pC7}$E^Hs_&CWGd>ziC0o;W2zw z=ltsa<~XoCc^HB46A}^R=jDOurQOvB6cYZP?C$QauD@Ey`)-bKq<>FSK5cups&J3+ zv~0D@W#OSia*VKAHjx*vnGXKG9W*|u`_%#`- zb}XWT5)}2ZCa($juYXghHF#C^)6n$cbGTuHBP$}(Z%?}c2SM4ypVAnZp|*}L5}YhCQmEsz zHh(%`+GASnKn4O{fNUStA7%;%lGD?VePUVy$V%fuG7&01?~`=Tdm#fuLw!&KgW>h# zcN|GlAnj^ssd{m;IgF!6K&Ze9CN<$#k7FKS8KiZ4xGJxH>t%oOLgqt6#r6aiwT-LP zOMvy@^Ep|zLk$^@?r$D!P3!DH(W&`tzoE?J_#MEN$9@mNAx2jMhK0*TQ;qFfk3x}p z9a4WnDPZx4hDIWTqAvjC*{m0dK!GJj+~j6vKZgpKJVCxPU%@e`miV>l+FXAN5GFBN z=38na{TzsLJ}wcT{ezCH#|s5}lzJdt|5#!<{$0wzU}w%PTwFptC>W75GNM@O>5t|} z8-M?P3X7NmsW5SHaJCYd?*cE9A1>tcq{%&s&%c6_RdCdFYCgxBA)t8Q&%3UHO>wet z+6Gj?UxbT+zVZ(Ygw+JL#&XzW+%Mh%3oJjkiu@_?q3;|U${?l#M@ne+wfGZqg@AqN zISEOGzrWMgViO@?gJlaSfTfS#e7z%bRaMnfWMp9(nb*8dJ3(oZl7OBwc#-tRK zwG+qlKz&pYY{BmUeb9jmm;k61q#%q0-7L^ITp$Unw_lL1}G%KBoSDz7reO|vi0=3lyyp&J(H{nMUY~c|& z{lt+X=>qP!7ni_#0157Is!yFR04QRTD+a$M;`@vOEt`V<-)~g4g37cvsrSkV2@X#1 zs5VKVEZ{Kp>H5Ns$;yWZC6(aSZhNBH!@3KSt+Q)L7iL5w@4?e>6WMgG= zTH9AF`*<04J3UyMe>rRvCrM30Hw8e`3f=djA2)TA3Ik(*QZZ4`A_rzw@DTvPWyyXM>VBtF*kpLRWjnyY`Yl22?}x~6As~PQZ#7{@WgPPCtdU$<6keyjps1** zH|T>j;O(kUFp?hZVf$6JFtrWl0x_Wkc8`vlaP(7F*sTc98^pox3H(z=bit00+T7RD3qz;5TjHLBm?c`73m2k@;^PKfoct0V&_0eT#M^T z5o$-w+ z=~-~CdgZj7FYV-lr;}W-(EsU5!R3cClsbmOqgC|65jqE+@gz?oRKP6}0nyig-Tr_6 zt2tQM35N*zwyPEo<)G5h-&lA5$JPfgmNa)U<8bfn4irgUMGcw>d!~LYyZ@G8&Bjv3 z`cIGA*aT^KGzcqx+zq}gSOrHG@kCh?wT|3|Q57r6>;g~b*$&5cI7#`|h zu`K;X1O3+^`#+o8mWw3;WG1>liZ?&DAeAQ-vrbOk6nSstFm6!c{^LU)YF7wSR>!I{ zO`Y$M`GAM({_a3N;2^arqxc_?6=!&Wg z7_pJsuJP*dS|U#;)N9Nsi<2fTP$iTjU0nnIsbfKJcg{8PGuzDL5@5ht=65ACtD{!* zu8ec&l;~=g{BOfdft;lj+l0ZEmfTVIlw34i!fD?OHF(6TmQ)A+;| zY>)QpL{FRi|Lm3jJU=$n7NoOz?NsvNZLzX&HXuO|iN>KsE7Yy z_G9k3!eilbr;PZUJSQkw=lY8B*KkIW_OwyLPE4@>#}%m!d>inZ5Hk^ZTA7*j23`26 zQ7nA!?6psXuWMbzKaFLo7f>V>Mw{2!9||)`la2(KWjs^3cLZy4 zTjOo%ni1Vsk83K}Ul>TWTlPPjytz;Mb#XI*#Y<36pC70-yI2CpkpCV8rLA=zJ+O3` zKSmHvhiZADoIs0ZcWZPH5AJ?`V+uL#p019^<9_F$UUCB^i&y{hJk@T=V4%O|zo)#a zt|oVSprN$tirXk^3XkX1dN;5yVIv5ywIM?nM<;CurW{$-Q-2)Or8cRuRPL-xCvA}; zOHCaKCZ-c{c0>1YKv4W?nFI2s#!4${MDP&=*iyWJhgXF_0L@ke_y;hbbvJq(8rePr z(D(8{f%T%M#bPZuhz?!cPU;o#HDJEzY;05o{uu0eLo0pj?>k&WfkMNK6E_vA55d=8|T-*YeC%E(lz0)LXfH^zs)7MEu*$C1G)o4EW@ zU5DM{0dTaOIVucM`6xr%tPlnczxp(~aDwpz`Rs=d)d^=_J&CDs zJaI}H5_k;x#>0mt;C2py?9Db4@^s6?L2_l1{RK_wm@j%8J+gtc2FQLp;`_gpU-XidPyH9Y3&g*rkpqR=alFT&CA)EsvxrfIKj>gUoC_^Z`^#yJAB+!7U5ZJ+d+WOfR zbbd6>Dx2T6YyER7NmW`(x{u(2r`htBYd>dfA8ep~g+=)tc+y|nM}AGTU%RoN9}d1V z6a|tZd|>1<&~KkSWo^o61>`&(9U|HF^$124_yAD^uXcGZH6da|V>m??7IVD8BR~qD zx|&3M2*I8iplod_w@9r~{ZOex3VG?}h^u-OTauuG>*&Jzg9zdH7?%eEFJr7xQDXe)bbxazNOaVU%dkybT z$8l_}YsC}LOAXEkqK1Yzz+Ghj{G{+-o;1~6jhYGy3Nvca*6JOs69l@fuW#9iiLp?_ zyY9?*fOW!hd?77!v+48{n4d{{tH_uGP#NYLd8;<@46f<5gnD?kS9wt?Bf4bYp% zO6ui_z`PTH(+a+C<*8tgpv_YVOjvken`b}#XJ`6m5x5M5R5lk_32~i3hqe{ZlUKip z`|HEzG0?m#D^IE&ud4!y-jk8{--m}A)}~%HZTz*;9V#g+ufD5TaW`}Oq2Vsh0hS_Q zII?_{nH{b}Ls@CA*|$fxvCbzPh}yPOIt2&Wkv-$KyQJ+3vwyHI$!+YFHYhG{%^a;R zF;hAB6$Tgf^LTe{v5EB)lsnJe9Jt`VEViZx%0%+bOGzNGCIxNQ9k~Ilao_;Y*TEbG z#0Ov9hP#$#p?1pKgJWIZ5uuUhx0^9B*0Zb0l?}lEWb>S~T{b+O8yB<^1U20R1)uYU zDst(}{;-0&dO%-@RKnK)HkOM-8Bm#y2nnlfaB@dLgfz%MfCE6z4`$FI0Ny3RaeuV2 zUR+KseFVvP@bL;V#d-`G+0ACv)kSdH|2^)~R9@a!xcOJG+N@~1i+g^sXgxhuEY4Mm z1X32~q4WlyAJjq*japC$@M_ta%4V3Y3~mFSI}ie+C2&QQx=T9b=J7eN<8#k$%{PJT zjOsivdZ5i+;+QNB7iPb{=nVTfPG}5gK-}YWsZ>D)Qo>?77Uv$ES5gK@qz7P$C@3j$ zGHLLfl~n|yP6re7q#X8veot9r&dKSyhjXsUM9Rat3VWT^p_A;ch&L`Gwc^9TSZMq; zX>0RK_>AK?pE(EZMxH0%5={Q?*c1WJlH+t>#T4U4<+?xvw!gJFYomjy>15mcJ=fA} z^Wsw1!j!g5D=K3a!f-K*ncCvxT_6qB`yE6^ayD7)u8Kk|2pf=XW@Rg_%@zYm?g{;o z7aJVbOs-W1Kvtdq<2o19yJ;(#Bi`LS^~q zduBV&G;hNCd*AUf1Xy*VeL+_p4mQ?4Xt)a&f?#Vkpk=oO{XAlYpx)qcEMAiGg zwcT)N9}7YYCXQO<8eH8p(q0T66JIupC3jz5FWicq{rMyZ1R6B*Ddi0<<~7XP0{>2$ zq2brpZeTjhUc!t;o}!-JpJHyTYb>}sbhj0?hwE%=)m}dAEjYU<8WJKL@nxHi3z*Om z?|m?ud0CEugQ$UFLsnbk#jKe@`$t8@7Ma#6jw*u|sV9mq`%5`;W!zX(vXO^3_Pg=4 z%9-Be`IcR1LyxQcqM%ON_d(YozL6Yr6y^&tkvnmAg-SpXG9OaGHlai|v{_ z7yialNX02HqMJdmjyF|wwH>H4@z{#aOI7qk6Zp27Xc|v|$gES~yoLwyvp}CU$l6zjKw;w1*9viD>9}3Kos657Ru=YR={3 z;yHdQSKxeO*`>p_Lus(fKW0}{dmkjwaFJB>d92BptK)Te+t7YP*j%sE;ppT>fes#< z&bg#jy2UI1C_k5fa{-t{OQ%}2v{ zq-Dda;7`^yUnLG0oFD><(+STM6SD4XIz<*{q;@ zkwn6CPL`lwk@WPk7O*o9&1QbUcP+3Y0FG{J;&|f7uDO@77)S8CnghD6kpclEB&4#N zU-YM$pE9?`Yijs}sVA`W5Ku{cCJU#M1oC7*m}@$o+0e#K9g>T#?E?K{??rnkJw0Va zylTcfVqH5Kghbmu(mHYNl5GO*580Bv?4YES6xKJy0b7V z*8JWfdfB~(ewm2z8XZ!gN&f^qY^b{(d5Oo+NyM((+JoeSgj?=CG3Ex|woCMqmklQA zP5j;s*V$60_og)q_rG=rApz?WSv$h9-;8jj#lkO(xs1%m(R$q}hU7Y`V0#fw_ck1@ zYq_m#Tm&k!07R0y>CsQoI3YVSrf8^!zQWjKcuq47`RA3=XRO)D(CmXMI3R1YqhEE zTfJon=)^((9WEA&l{lTEooBP`!v@QFhu&}Vn2Fg;jv(3+4STXGH*H~`A%qAXP-Nr8Q|k`H;&8{RdjhHA|jG;|8_ubn$Y8R+Rb@tdM2N|<(j(6%W3 ziDJ~AP%jfJou0I&34HYerHe_IrH|XS@AB~W_FY_D9Gp+DEBqCF*AHvpH@dRI<2{Vk zvu@|G^_zs4w_1FtjtG?R5xXOZxOg@#raoF+88aZD>{#dG*;Nk`)?`3`d7OCNv3v&O znfT$LBf{Gme|o2OD?MDpW_?oi1xtP}50KudFzHx+n14?nzct&0d_UJT%pJeA@_OJH zt1Cm&0SkK)pH11_nU(y9JhgCB=YVh0@YmC;+|6?CL$6un1{=tqE!4KlNjeE-Rn-PM zOxv>7@KO3)X*;b@@CY1*L{CE%3*LKv!VXmFhYOT2TiSzz#J990lVji^6D{UX zJ$+X<@8B=$`myeK$&0?ltsRMhfnGM?<&Vd^c&t((=FN-<`H#6Lrs3nH-~&(y4Lnku z{=BraGn=w{kN9KsqCClx0X>iOt;)s>^00zlYes+ga6tn(d!1NUuj20y<_AKM`PWxs zBHgbGoUJrH*kb_%zdAu-mzp}~ocXfFsBjOsnSfd^Cj8ZxuBBAGP!caMFL0^{uyXA$ z1NJ45H!8(vkv+#!(z4SQPmz#b;Ug_IShIk;CKO+SoT;WKFszw$7Kn(ftt-*TA0K{| z%lq7>8b4-rzKWo%pW(U3h1ENQm^8<%~ZsV;e;#2$Hlunx7K!qv8F|Wr{DJd!2jb8^`j#M?)GAl6X^y8&hiA9SlqJd@$_~r}M@xm)q zyK3d1rLdjfA3qHq#M)V4o2l3piZ#{6%gH4va%OBC!hU5}(lTxRddQQQ-yy^H6KCSy zSbASQa@drY<_*7HV-M}P>phr7)QW*VNq$E`yCs}+-G&Jlj#5CgllEP*a@+0whq>`s zV|r%JLFKRxSvIp5;x;asvpBOWyc*l0)3eIp6gAz4W~I}u8B|DAeBzhAiW%ztb;I?p zFJj10fatsEy^vdxBC=?G0`t~z%M^*?{!1*W|r&KK)fVX}cmi)Aq@m>x}+ z8s8oE{d)4Bg7P+zjDV8$DWDMUvMQCk(gN%%AQFb(nV@IwyY&poa5H^IEy^axlQuc^qt2r$-R>5K$=1f;}Ux8Z3 zQy10iLlG!jLk{mFG=2Alq^Uj|Xe@!}E0>)7_FSoR(cyLhhh-9{oK z3el_9?@W}GFBD|6q7pPJK6m%67mnsxv%r>#OXs{}{*wJanzH)(BhA9E(1>NYc)V_Z z2fHChC7sOeIKFURebH;GCo=J~we<}pRr2CIbW=`;gif%n@|!Iu1EE$!UM(E~FwwUS z?sZcH_3}nny~nV%50K2(ph^3g^b8Eqo}7ebgJ#vAQBjaYBDEL7b^1_&QX!9&O|a&p zCi`pa#31h)3&i&o5_(_-puTh78N&edXQ$Bz!XL%wxWQF{<~of>aIO2Z{4ypcPrJfM zzv!rzye)ta4WP$HLLuOVfUwlT%fXuMF1M9-_DL15ue+9?xw1$s{UliEMR76TZKuWqnT96I_=@#kk?(X;A=RD^Z z&-?uGj`98XG0yRvA#1Na*IaYn^N#DfZah_lZCYi+easIZu2Iv`jg%Q`zBD8tB*y=_ z+-D?5UTQHqA|K)#c;{pF=Qac~=xAy_JtTiN5!V6# z-bUXYA=kr)>N$5ak5NSohVI|0)+?~R#QCiL1M{AeQe=A4A6B;BYxP zSPd*Fc-CsGe8E^Ot5Bb+NtgIyF^DzjGixZL1~?9Iwd6~`J$~2Wms!jROTy4Fygj`JSP{;eJPf^P?9e$ZjW~DC`ErUXsFVN0%Z!c ziWnFe#Km)eoV3`kjTAPkjiJ2Nad)X;CoTj#kY>PfFR#qzhR3ln8mz{h-JL?WWf?62 zlWXt2yht|R`aZo6K-A_Saxsk&(O7I04Ex$w8(piP>~|3YPC1@n}Y2e)s(>x+5x-D)v#Doj|uxO+dWwdcEvY^HM1>Fqm8 zfkz*Bf6@Oc7r2#VK3s*?Ep&<+L{{V`6_|KG=oJ7#CVRQFuw3@mK8{yjTtzV(KVyUR zFn`9=$pXqj(DMHEZ9ZE`?b!_!2#_20Q}XAN=N!qqlu{64)Z_ij9&u zxE$hs5W8J?7rZsw;lbUE#U%+Z{Ikd3VlY0dwmMaP@CF9nV-jRGG!01#Te8P zdB6Id4+WD{I2ru(F{?h_*v_`5x<6tqf?9!6|5>adp)!do%JiHp=wu_IBO+3}q(sBojOc>ma>7kO> z0-*xMwvN2l?sIUU!l{wiw`|?-%T)HZ+^gW{wEow|#$zUj?{SF5XKN*HAKiBiKF6o0 z)chZ9t9+l5PP(671@$NMlJl>ol2Zo{N|PNECiV%gWM^htIXH=nvswehw8%Uw%ebYR z?XiKu2TW=zr4JvhSQr)Ynn>EQ|H9(j6iw-U)mtxNIGW(L;t2@n(~i8Nq?g%o_{6v& zvRuV?$Wd6IKNT02-Pk~Z5<0W>)MG}DM@2NFd6gF$f?HGPTH;@P-JSsW6O(#5@f^0y_u9#EVtuiXLpAX zI85*p51OWcJbDr?N$spf_H$h6FFe72L6Ua<1yVy|xVFk#GOzc&;e0CcC8)UA3pts( zx|IGHn3Q4dxOeY<7#d1PE;*oz_9jc%wis|Y`mFUqjSAVR=g-xwmey8LT~SI5Ow6Qb zjJQNZzOZ{~AVp|n!=drRq}8q54`e{L^_vSVFFj%ft?`bJ(ZCt{ph5N)rmy%-FDV(n z14uOCeUV|+TE-?0M0csKnb-~u#cSPdZ);alS2r#ZDc8aVfLXy&iy#;ZeW_sG%Xi8K zu~mD=7|_u|T%1><_|3ZmvJq;&a_#DMmMf2;j9F1pF=3tdJ2*j#u(hM3zQ+9&DK27Q zlpqEN6374oaJHw|_yv?dZnn0r-@bi&YU7!EeEDQJhBqoiA{M^Bq~OXXe*4yDCSWUP z02m5#i0Nl1Jxga#H$5PSboubqyC!dL&Y{eB=GrGxd!et~<|}gyStVjVzqk8>WefK* z(puWvOPBb}!$$Oer^Y-q*?YRsE&+#afysRS(N2K(P^qP7%3`T;A93lJ%Oj+nfcmP| zZAxuU=ie+t7n|4t-H56sCg3lG4<7)@cxL=?!a@9vfh0(~M6hVSYs4g^W?6p)w|LWw z$}`Dv?~X+UE_iQEv!;V*A5S;j6DN*~7OUQCT2v1wvG>txOjD!tohYtM zh(ClzL23Iw36EL*Q17gDUtrkQ?r#J$E4VW*C%T{j!bHS>gu#5YrT?abxk)N0EZm*| zM|kUD9>E2`eturz(-()0AwTw%=D+NzlJ!*-pp67Y8g|u( ztA6^q(r*eq3S&uWL!!7Zi|$e9Q+OH0f3-kf#=*hy@U)+g$fHpO2@c-gRZ-Sj>v$$z zf8l=0KHcP3wwQOy41N|@O4bBudfg)7AO)p4N1G4gH4V`4d4F(%Q*-PFHgSyFVxDLHlA>UHoIg>mMT#Gy#w*d-35iy#$!Z+E_5~K~ zb;n(i80*F2%I(JbZuyUwq3192)V!~6c(x?DTU5ZSxa-#MPm$YQu9l*#i?7I}jt(c+ z0||J*f9mo4IS+l`KY)K_ZOsndbPA0Q$qft*quq5RCDF;tHbvboXG@WKd3H;}M*AyV z1o%9#^?@`V6t(n5*)$s&(?|uAizz9c3$(&{Q~l^k8vlE*_qZe^nww4z4kg2jPvbm8 zpmoVpExXq5uiIG|E!1|8HICBuKAYK8@f9J&fa`9c2|MHY^XJGaD%dT9*KrajNp^33 zU^c2ZH1xSg&aIvFo87p+^??DOYU9}pvF1ab^xZzie^Nti9uCxYpPB3F zL95fka0WY)21iB$eNIX&#ycfH=HkNI?2ZXujQ1Q+(mhQb+bwt6K90tU zx1CpMV7Fhn3%J>$3GG>s=3bkqzTO_0`;$ku#$GR#=JoUp8%?lc)v{r0uf^fGBTl-= z;isa8WK0kh@`*I88k^z^bgwyBJH}y($p1BnV6isLH2A1&Jqu^`^hc8B6yTEXJLI%> zv;BbqM&Czir1-t$NrXjQinOa=*X=hyxpq+H{7D!iMcbz!hU9f#8ejTLS zyob^DK$p@lA(ExBKv%5OLMjWWt4>?@nKjPP$X(BribNJ?gM}OY?$DFre(!cUU3cu9 zh=T-D)P9@n(Oyw-uP00*<@UwJ#nRv=M~AyPw}<+{3$Fw*D#UKr=w`IGvU-;C%)EM_ zXANs*4x3@OGg`aq#ruQxusAXfu0+t8Q#;uWM&1qBq!Km)6D=(_v_y9J&-Qk4K+@*R za7_(pk-YiAZ}d@iPG#V-oIF_YENb|mncVa4i;VHURx_dz ztWn5u($e8%aQtx0TZ-KM22`pVL*ONX{5k_8V|{-1OWBg~%b(RSBaJJNVs?QNm#1^@NzDb)E_O|UmeIh?pz_tYEl$oFe)bOwalK zIYpTZ^q}}QO7QLe3A5(#_Xk**jYhS)Zd^N|!xK&&} zId`z$QG$<8pf1GmYw)~g1E9$&19{NJ!-gR#$SA5C4b*DB{})Y_w^(n zinq13wc6(h*YO`eui}jMUsvz?ZT#T*6EQK!K3TSg1gfhqIAT)o*u%p%7Yl4~y3U#n z6(l*W@0UAxA}DuWBWpTO^xpTqyk|);D7RDgC&JC39CWFR%rm-UMMqOhxc3FXa;**h zkpIpCkwdz$`|#xIWrQF(a6NQYZ~dtB3~#H!iLm9#=;IeuhPIz7y6)l`7-^WDNgSSJP?(Wz(b6O{`FJYAJ2D@|df*>XO<0)A_Zj zB8Wj9$H2w_<}f25*%XbtU%P%?mK^ltN-Hg?EW_XkcT}hxUAvpd`arv2Dg_5A#yZpD zTZJj!fST5JYX-$|6&*HiG@wKugX;_; z+o6}%x-yH=yxVj|=96x_WO?N${BGOFk-V1A!L%dR;-Az2$Ot*x^K zezG@so@ES>J@|?0bG^sJUhRCC^7MI)p=J8I3B;Q?}`*J@c~`y z$b5Yh`7g>_5Dq~e($tb#-Wx&{Vo6h7SAw1?QC1gd%gKETpr+grUKl6^sU6)9?dSsk zU;*kPe4jqi2dCKS1X@ai8k9!KoU#U$)HE@vMT9fmoze*#0dlt~T>_qKAjJSwUPZnB z6?`H=r_cS}-CbQxNQVvl&0kSUvjuL$7?3Te7hloj{A$uVJ3S38HCl8Q2!cNO``cO5 zz+9$)1w!gLGcuKrQih~hUGo8rEDzMuNf#dp@4zo#Sl>jBVf*>rym9N+dkr#_v|rjq z;B9YwdM5ouMuw32!iDl+eKa^tgr$*9giQ)gXBGFg@k$(||N8uS#j6Ebwq(^!9R>dg zWXvhp35q|!Qs3R`*vl9r!w69Y{ItBkzrWRGH4dlR7!i-fEy-ZAKsbu1I5_H)J#4$? zr`Ag+td*nw+C0F`z8JDQ49}@IX-843o?WwAALx#jO>BwHcpuQSvj`V4vS@zFxRV+tqW$_6g zIH#fyC)|oobq|+%$!v9E0q7B^|Jja{Mc(naif;?YyLZ16^1%}XHNPUL3{s)$c^ltJy|mzby+8<<>6e5p1=bG7^-wk2uR)|Ul>3I$s93b>8yYh${p5_kcGh! z24)63U3lMm(cq0l@h;P${QM%2J~gtM7b3`Z=*AlFV!{u>sMbC5kU>bjV7)HN1< zOcw^Wp<$~5XYy<3;mN+|^qlm@H~m0R(}^(`aNbE>MWx(%f`xCO7|Xj{@z~e*n$4k* zW52)vO~p;5nrKnCv*(0c4&pNMQ3KVE2^=_4kcEQKl}b1Z%>-lqnm75Fru)Le3g69FBXU`TKKi!ocDlO<4VO%~ zyG(l%COjIw_;aWG{;pj5qk5_x7x*GIYo)^Ba(k>!>ykfBfiY_vUUDqw%SM7HEwlR< zG21h;4u?VpORtK|hmDHXHzp$)!ewq%>SY>?`r|Y4WrVk2+mw@v`AB=#cxdl4wY8~f z(NleIB73pzh5t%Gp;g08IEId3o zB5dbr6P^f`i;<0h1@O{}`jkNodu?TuP}@0e@4&>_`561&hWhj8W{wo%$;AA2zH{5Z ze{-B2Hzzn8b~nEDYk_fLRPnnKXcT{j{0gWZ9=*EoVGZ(s>V}+54YF4y>pe+!l_wJg zx+&V-^u}P7Lk08tO{`yCUa348Slu_Q{c|1Nx=`H|i=1O<&4LkPMa#pu&k!iq=Yh{- z_Vee3ub*FtSeg#-^rf@^OZpe^Jmdnqh3@v6lNh1JQinaumD<1;CyX@1X4jo zH|VqrZr5h4VFf;Q(r=EwbnA>3BCNeE=~@xSAs776O!268iUT*KGzJ{{Pej%~3^bu9 zYr4oe^Wg0L@3%2YEv)wYRin){kLL9{;?BQHV3r`$vIp6=4~=J7ATwvYr}cfjSRmu8 zx$9c*rEZ-M1$1XpA433IaG}eY4D!QX2B)yWdm$B;Jxc=}5(k{twxv2sCT$PhkKiC? z?iz%V{`^2fH?x=32SLG8HJDVBtzG*~p+n96V~yGiHn}ogs?tU}s)Tf9#;uiDb;H5e zr>pt3+r94zD*?p8%vd9nf9-D@kk_vW;({#SH+kkl{P% zNrUKm43VH!RAcSh&d!dKMw=38$x3gEK>C<<%iv0jCpr!NJv#a*Z;AyYOB9rw&G9i?VP7MJNebz;L_gTy!c z;XYp!ukQEZI16s`v+FV3P|SD=nPsrQNqx&9VPRg>KEY)iT2x8kkm_!X4VM|$)4AlZ8uTdwUJY350B2d0jw0w22s8b72;*Np(!nak7lp7eQdjqk+;f zJ~G*uM@}geC-mqMX19Xpgqd};FD#gBP0LC&$X(8SuA!S0yopKZ{tn!!7$N%o$KS*} zBG^pv#;*m`>i9~Br8?pjAg6!ND50*XTj*-q^J!!b>&Kvumv=zzV>Pv?ad)AEqZFr= zW%?c(##ZcFdb9dYl&aAeX@^Siif$(t!wFXr+r#Ie9x z=|rYvn9q-;Qd5UY^d%vC`0&!pIqK1&3iNtNeBDl9n2~gLvPzuo_$KKDxCsy8b*b(3 zM=lK&dX1lB-Qyt29?YwEhn{=ou@ur$B-~~U5xGZ55%9w0WAJBw);A)i3Y#h3JxK!i z^#}c{GXY+p#39k7@F`-X z*f@}lz~dtup+^v158#xdCK79GZdK2MZeIvo+ zps>+92%s3cP#W1D-88j=Xppu+M+Y04f`jY8fAx?|sPbTf3- zb=>|i&S|=FaqM=8xf?BF;{7pRK<+W#Jxf2Q#G@nLU(L->+Wa00=}q8xfu4)1qtBB- z-vHB(Wo3p?{Apoor2j=NOgXW#2v+~A`7AcF8H&Np?iz17_#feE;XhQR6Ed|3p5Zy8sWuRZwleuhwY9`h;PBG1BtPvhG0rC1=~X)Z0-fBL$?+?$K)+q!0BT* zU(p6%ILxf}G+qG4scTyWX+|ia0TnIOykt!eAY&|l$r?}vTS%e%VdlXXo0Mc5cmNoh%LVSF~-k?h0 zOn_Xe`Y&(XdHTxfZKc?j^t^XCw@~o04bB2E{Ug=HJ0le`8g{#@eQ6P%eN)a}*KwZI zwzG+WH31bq3<$bIf8)ir@dkqY&up;0C-;#XM6sKmX9VLF_Vxxx+;_MTHEo(Z$N+qK zc^RCkcd#ss)$d`G3;HSwUvwDURWV2Q*T^@0`Gl2lkvzS0u3hyZLa&euLG_|S0IB?d z=k+A%C!A7=fKXym&EasJfXzxP<)Zck4$TI%U7_?c(K*%(g@h)kOQ`L~f(e$$oI%G8 zN!jMPk9_0I_wS=@cDfR>b$-2>_Pg*<8mis}bEElIja$0d>ya&lb|B6JV)YJ=MB?HW zK)OeMNFqylDoDVhxlN`0q0OIt>}A_CEQnI)*^miVpFJrWelPsWJ6wJi?@K4$G2gv= zrvPddExUh1X5B@Wn`WVT0B?+zXR%o<7C1iQqQlWBh__=hkpxTVS!B3tR)dh zBi?)W7KL@{sra?O_ZD{Y^DJc26i`C#b7gg}-pbM0zEBp|TltekU1C1@X>H9GSuRyK ze((jrj?k<5$rC}yq>YnN;f6H-t>O{u--tyu3P96eja*_{T907Mi_=`o`%8ENHnn*D ze`aNO11*GLqe24Ppxd;|t(+W5Aoy$HMFB$-p{HB(tKQA85wK7R%vMKcCF6dzxQ*~P ze~~W!_odste{oD)sx9e8x7~t-pvUpN3#|12 z`Gx=b2*SP79oSrbCx%?@|1YSWqJ6wh}vHy@b{`WhWSH1Y%d1 zG}q<|; zO6!@5PUrC}>{mS`&*tCb5bn+s{pZV7=VkB0<;d$)(x-hou=7A%g6)+3pSAeEhI2$b zK+nXKIhCyxAjj(EnMg4wGvJYvG`KSU-1~Kn*?)dwYtFqMj!zKNutXc)OT5 zZgkO&9TD-L7p&8h8bN$aQmki34-%g<0lHy~%wLLQ39Co>jga9ForP@O|NJP5?UD3= z3qtj{kYPB-KTFhxhf2)-omA2G=uq9TM(#g96F4C8AJDLw7*sGAlrb3G>^!0rvV9b& zyK4G72zP1m{_pK6QS9c7O2BIdAgBAPZAs7C?!RcEjBH1VzQw`z;34rXuFI4`l2<*; zNqxkLgDbiZeUD%My|4e*T>iC44gRH11n2%!ry9NiA7m;HeyTA3|Dpx)|6LX%{&!Y2 zi9u!_`X2vPJN|pO_#a!Myn2Z8nQvd}g*p0TIxj8smb-7C=)TpW^h%&WiLM*3ZtRRfNesyUW7Wn0m|SFs*0?e!RA^_cEIB>L6C@_$+2Dx!Q*p%1Qk zqOV`0;bU)BS72o3MTn}dFU7a^-t~B!kZ{H0Vk79+_7_I9yYP?9Bxz=9-%3*7QeyuS ztW=38wgp+5|G7e`G-`6{OrKfv?h^k=Lc^9%yY*G(>Mit_P2$vH_YyHai{jt+P#Nq- z+)S%Q<=~t2LHTFh%B*fYqhUv+|C>Dv$XIMAu0qF;e3U0j_IxUHpd~R2WzR+GgZKtb z(*U~Xf5>1$09B6cI0Wfdm2LD+OlMPgVn+nk{o5pJg|nG%5&tw++h#WoAAd*B7j=(h8B8R&B*+}FO4C4OWaT(hl!etR z*7X;}Js2~h?Z#?p@4Sj)r?C9tfGm_{CE(a57X&gMXJj9X4FPks zSl&dxe>eJn9Kwyge?8_hm1&+R3$qmw$`;}E{vKuGL=3C*Zj}?;C0#KjYpm@XBMLw! z*KzNX8rW0FczQQnyk$r1zfKS`I&I?B57QX^ zbN!!fY`QjHg!6U9Burn()!t{Ut(r6%DuxeO>$dgjtyl%F1BmG%(Jjyl@crcfc$GW1 z7jVH8J{({+>#`u~HR<^pOLA+Ss6nqBrc zbnQjpxK{=Me7ne~)hh(!Ep$JDajCcKqYlX`s}V#tjrXsisUK+fD*ZKQS*)`8q9o_s z+S;8w1pSLEhu-forMP6~^Fz-{e~@l#JVFFw-cr`I%VF<`G$pcH4$ymByWX$;&ud?N z!63gG*fwv|V#a*Jfe1?LTi#yv=NhU#d~0W&5&=!y^@e}r10|7pW3oS=D_>OFpLR3g zBPJ>OJ-dFacu`>)Rji*9Dk_kVKr1~r`66SVl{syx_)g6DG!p;q@pt7{&k`k?i5uL? zb>5B~@cox#*#$IRVmC7tL`S69`tUwQS8P{~qgIykS2wAu%=@*sM@uY9%RF6oU*qd| zh2D$M^5QESB)bH3e1L4w{Kb# zoZs^PebC0Y+h3OWcsYbvv~D1H3MGn774t?x{?C7G$h?+6KVb_-ntLBX6k9XHg9j4o z>d~!*pst@NJJ{9KjAT|;&e4#{$nub9fBT)7ntB&1(gf$Et~;->{u*i}x_JS3?c0G8 z3;k6hv52|P57d(K*FodH$)xgSc4J+!9jTlw5B8n@WxJ!9K8gJW6>KoBQ&sXmwqb>(wJ&Kk z#4dsMs)kfTQ#){hB_@VcR>nkgv#O=V*QL3wy^V#{s%(X8k&A&Vwa~N&9hTD|%NJQx zoBmU=uKq%K8pk(2KNBAt#0q-9xR;^yTniPYiG_LJrCQpW8mJ(hnl#Dsn(d_}#fw~U zWDyc6U!o2V%%J~tE3a>Spkw1r%BXU_EHUt^Cf2m?W9QJ}q{r-Rid2y9Z7Zg;8xE3lTyP4S7@*O+A+NWvlf88biLi~H^G-dh$V_x*T$M0>1 zPa9M)iEq{em>tRH0poyz0u0b2+i^rw76Zy{QoK*VF-TU)Cg3(FG#!NaEqZ?{<*;AC zp+SCjZ4)@~6{i$6*P-&jj+w)bP_UodQVMGZ>O%{kBRK%o%~wAk+b|+WmXXS^9-Cb6 zRvrWeuxYXMgwoHSAt&@fbHXp+zt`XQ0E|iPKPZINB1ebWx)KtNypeJGHqdvT{fXj}IR?;SJV7q2yK18rW3VItT zM)2zFHv7lFd1xK{Jp(PLDItE!XPu&0)29vN7?>E_rz0Yt93#NoUjsZZWIXImFCQ!E zhE$5tP}yYFiCw?{u)we_Qi~PIIFrcq-feH)2M^^B%bTIf01Una$e-jhy`-Ln1|YL6 z8r)KmUlc6-zp+;}C4S^QyO^@1r4lvf>_4)f*O+x7c`#m)2qei zO|2V#S%+$d;^8`{lpXQPmG5%j#mA>+VCXRzHQNyc*J?#Uy3i2|+opyq+oK`tQy*P3 zbeGosWv|L-I6@oZGriLw{{F;v+y%s1P*8yN*PHK&KzTgr;SLmRR1^RrGz0gwl1ZG3 zs`PXFysz(##m4XVqu8wfj7l_4aPhWeAq_UYqCpgCfTC|hX5rxO0--Yt0WbSOVaz{R z0N-o39K5H)FF~c@*v}83ZA!vz0MVpRu#|mKpB^@;cAZ0QppIM5EXQ$c?O0Hm45Hu8-85 zGeCN#S0ZSFE+Urx(9NP+Lc&Lf{R1^m%j$M1UdYe87`ZkP4r>elkHn!KTLrDNGs$`eujB1saEj+O{FN(n4*dD^r%Utu z_bU|C-M?C~m zWxK~;-KtN^79lk9w(dMhd7#58velq$ZKp=aKLN-Eu!N32;#dqUTyYb(iHT>@pSIwwhlcf3Nl<}!$8BN) ze~=h~D-1aycQ=|o@g4{&nf+I`4vi@N-LC!>XU?h2+3-7Bd<=LbVew78rRHVc zo5`WSg2Gn2^)Uu=%2ZS)YSPj-`R$*En7J;Fmp;7sXw4eb@&)(?6XULd!435dJ<#u- zM-m=x;w>6BrrYj0RnD-KH$5H_wn~%r6iHF={H0Dylh=~@bc}nS0BB+)?DG1z6K*eVeD?K? z)$6X&ekwm20F4J6ee?=@K0niP>{R=3EueJk5hNd4_+$JoavzZ4o zk~&YliUcWMONAE+uuoQsp6+qo+qisR{HSjWa=I1`v6fcA2e>Q`^F+KF4t~e-*W< z&sNr7nzA0zL#cA5qtzuv067a_QyjzZ5>5|C2UiN*@;TPqmO8TBRB-gsWhdaJsp7fh zS{tdZ?%j1*p9nf!s9kvf_lJC2`Z`_Koxj{Agj$8qiN*5Z26mkuG`lKXIK)@mX?Ox0 z2KW=E-Fp}Hufb7K1(#Z!o1Fcyj;n02Ds#W?P$_UNz&fN?;~-vLcBrslz5A}^9Ag$TjJtzNGv;aT>KUr&(+5EwjZsbx28JLKf0O-9LISXOp7 z;vRD+uvxj)EQy(oRq1@s)r=%}nh9B4A@khnUF^-3#c5rHUyvuTo%AHJQ87UP|F=kHiyfIFobtx zKMIfdGV|DkF^HVczb~U3oa}2bCMa@10SC!-@8zOJ+rGKIf!)s(?Yen-dMZX2CSCH{ z&`=kO_Q-?#dr@~%ax$vP58~-*-8Un}5BTjjJ^<+ms*_fROFBtOo45-)wQX%OscB@t zrZ0;e6Q-4v&57P7uq+4OzHfKqzRI`2WCrbur%I2FQ87?|yXL=Vcz*Xs#_e!GT+KE6R|KI$g2LyPBNxVpC1wPA}O6mjU? z_yz?@*0`}VsDGy+&plzl!;=wkM;L)B{mR^fSYB(qNH&C|&5kxm_EU0j;6Z~R2DQxl z-;}dDj$A^jvmQJbDb+hzpY+nzKWQ-F(A)AIWcTcPE_gBYg0+-oUkBslmK%N~YeI8c z`{EUpl#hxI<-%My%dYvfn$`D&gKd5tD(cvoI3LJ+-hB9%4xHp zo%jjY=(i?A*Gtrm=cj?R0V4H4+`$)GT-fAJmJD3+kMEDRv42@h4D8`uDu?~P<(3>M|+xuO`^kt%M1G+_mR}K{Ch*yQx zee!Eghz~b2hTT1@Gggx{4^?|orSH(i8H7eh`|Zp#`bC@{4_>*;toHEmN2@^-AT76- z=~oL@2ge;&gZ%xU!CVz%a~X%x@rj*b9nEMd(< zwCB%rD6A-6COvW9TOK80(I`PuBs&l3Xz8YRyK4b!nA7mX^l~Iu06GwiSJb{|)vmlx znN&P}7$!Iu`}Mo!CHXC~aU8lZTu6!fXsOe$GD(N53Yw?*Xn#F6Io)`6hqV;3*!hBY31qa3aBk?Dl+$%C_xgQt59Oxim3D@I{jVbR-@EkO|DCBJBn+;&Ouz^3Iv zxzELgzdMQWg>P>HRG(nKTy4ao??9x%FEXqOl*EeBYn@4h3*WBM%xvjW_m;>DoZ0SC z^d5qSlG|~kD(q9fg^u{uXEs+`F5HiMLtjJFc=V%Jy*>}4JA%$LT3$Q82xXVm8J+h_r$7f;HxR@=m+@3#CjLnPmjeSx4 z23IatQ1rXo!k#*-JkQ9^>`SxBgRnR1g?bX~dUIA8t2D+?BA1GcMAYR(TT*lN?>Ck} zyXuANLt*}s%@4Ll<#eHy-&FHI0&vkcVtt6RyMAQS;zD812?*){bf;_QV;J3E<>^`P zCs(UFqK;ezXez}>iLp;Y$>!(6*LwxASxl?K=eNv9t9(VUDg zv^uZn1uDP2({OG=H1NI>&jaELW;3cLqk-(wXexC2>Zl;(sf?UW`Ngm$Gb# z2KY(rrsu8?q#SVZqSDoEFD6}ea;%f<{a(;x)sawnD+E`Gb&%p7uJ^HRBqcA6c=6dd z+9Tvutb>Z;LB^vcSQ;fJlo}6&kWa#6k*FFxsO9m@qv!n8cxiMIEBiWksG^}E9RbT33eW+=U&|LDXCc9J;(=H@ zYV)MerQ~8{#ne#Yti@nB>>San*JuA^t<@IL)6yj!nz|R-F0xq+Vxh!vTQt2t4lu|H z$Ql0n_4nbAcM6+d@kmKO9d9xh#my1@Znp@Xh3T3g`lD?(hyJ-*v}p%tFiE-fTE<9* zx}f z#w2RMaR=I{?T=IsM2n1SXa69iBA9jjnIiLjBdYgo=sAr?hSD5De<0tvYWGB${c1$# zp-S#c{QRmBe6I;&WNYUXqwWo-HTF?kWy{GLuZilDnR@R4juPH2P7~%D$^P|G$F1f% zk@0V?hZ~dP!I3PQ=>4XDtg7AyfTDP0XsMpP!Ry;t@e33ex(1VNrcqvWu3i*XXY| zle$gBG2|j2m->r_S*zg2i*)%C&F(_aAZhG*mglJtp)vKe#f#-jfWEW8nR?Ya7p9Zn z9CP&~Nv!#RLFL<*C0FPO{i%Dvo$_80tld`~OV(;d4rw`+9@j!SUgK@aoo5t6ho|iC zWC9*decgX-%QrS_Pj&X3G9vkHC}nX10s|##FDw&98y26{oKe5*vkwakLwcp7uA9hn!0+;YmfCz<`5cZ!BTL2~-lXvw--@s?Pt5)W)liEx>I@%EE_ zS2ARNF0Cy*#{o@NUcPa<_M#~g;o%K?Rkpc%`slC zzCi;mZEfPUr7n8gXW^O8k81;Okjh(pVRC_$HFJ}nNd#OLFj3X-c-TF(fQl` zKq8bsS?9M1s){ZNUg`T~nWZ0zS*=_Aa;J^NEXr_4w2#r?m8R8DT-8 zC2ztJy|EAMAm{9u*>$haNOwFVLAu7M^*2s$viFe!uZ)s@mDzgoP9mcUqy9?q1EBad zuh;N^OF7#t|HbV_-m@e@kFgW4!A(9lS{=s?8rhBH&=e=XnJ7L}{&-h~v(pt_B*$z@ zRE*}1JWe)TPQd&%SSFm2fA1o{RwFn`EOqe8sdc+!=FS^@pB0-g+1oZB*@V3=ig%?x zMKfGnZ@X~!DHC*=)jdd0{ows^k&cgp<9DyoPp0qmJd*NGm?D>Y?w4*>hte4<+ccWp zi5_u!hytfP1Dg~BuyOJHllqJCLiWNkGIFlR=Re7r)pFKKQszir=4((#kHJ>BYuNF1 zVV?xcGpXN%-TJrFuV24>Bf4XCUF&+0)}D@M#L+3l6t!jPX!}lRYv2s6|LiWCh%Ik9 zwCq^<(=?*^QntzCLU1EzCTKC<^`)Kf9+)<(mc}114%UX5T+UiE;7h2PFBI3y+Y4r%RW z&E#~&)34yW3+XH~jg|w*wQ#FCn6}iXvqeq{*8R-Sgwq~NE1cFjNdxh_5v*!=o>BAI z9GSn@Z|-7It5k6NA`X8~(4oE1YfJ^UN$r*X^zR?55y)SY3y2)Yi(DSv>C5nAv%^H$ ztT|MOci(7rZl#i)(6ZUqUvlyjx^+LR@^!v5pfCZ_a~!>^FO{ z&@c#Yvk2HNv1P1Mo5sg}rK02HycMvJ`#8L9CS{9*qi=g*DxTLt%T0Hq#g~Cur+|3A z&41HvVe`xeE9ee=u-?p`Qs}(yv~CoK=_i9@l4|s%m!0&a{DTQT*i$Ltm}%Yk1pcSS zGoz#mvBDS<0e8jH{0|7XsO&4_>_{3u27M-Ff9O%KCV2cs-#sX{FG=6kY_vq%E@qI= zb|G15_Tx*xG&D>CX;wQv=Qi=XjLI$4=2+sYtcVaf&i$p~&goiZf8&9i2P*@)Ue&IL z>fz?2-#b?umilw-S6?0(o}*{a0;u^{i&6tFRbfh0)asz+)F7hqjsg~m@S?`m*1Y9M z>3nfxEy#AmxYRz=r9v|SL>`ytI8NyoeH)y?cZH{~SW zTk6*?UK>O>)*1Yt_TDnAs;&zcMx>;>OG+B)22oN#6r@vH>FyRbjf8YaNQabkcY`3I zba!{2x$${?&w0;}@A`gyah>&p&E9LRnPbi|=7@W6)G2#W7ARGHUq9`Z36gnbbx?Q4 zd$7R(%ocWF5M zpjsuOTQ6=C!@_h!d41T&R-^1yxw+`Iq44UJq(KGap!??PcbF6#KD5Q_o$G5h^*TdZJpOQrzk^7rcl5y4IdS zE=}Zff&&H<*yB272<)o*v*?6k>kUk+{nCrx4=b4m>!gG)J(84Y@pJ71co$k zAP+Q2 z`$3vTI(5fYE`pL6*`N6lwc1jKhFR+e$Mz!E0?#RyZwqe$8buF>6#nB%T4t@wqmB1k zZ+}w+vh2z)UemRdZbpC2*Qv0B?xZI@idMXShA}bmhIU)UAY*Pd_i{{f=ET<1_r2o4 z0bT7gnb1BWe!p%YI3G}7PJL&q5CiDB!ZYpOcBk9?JshfQRHAa3BlA@rZq~c4?+d(s z=G)v*JH0J=^TIx607UkXre;%p7eR)pa4St&ui!Xj0l4b9>g55ZQwK|%SANl=C{fOQ zWtB;UJ+ga?iM-*%8(C7YCN`?nabYAP@NY)O1O&qx<+# zw8fN@9tN?<1TJkzoa3h5vKv^6oe^n)Ap~qeu~i>tuxH!?OVFOBMt^!u4E&Bet4nbT zdsmy-8r7_|v^+z}cmtG_gyy5#hY(?KnRMj79~mkhpTz*q{YoAc4*)GD;^GA-R|HHDm6}?gLTf4mNC3(C|(vlySbBnjO(Q z@6zZ14KjlH^SFjFwt&V4gfBJ^i-#$40UApX1X9QGoK6_pP}2tzK*HM(`_TfYQ_qT3~&idu~4jmaqH%?aQ7SW!orHlOgz3 z{UJGP%%1MqS`r+(us92g2e#T%wnps6+onl-L1AGX)dvRr+#P-(0zo`N%5Dc<`TbeO z`72_cqd>0fRV}2@6Fg%#%~kVJVl}i zk_=(4?s5HJGFG*B8Z`A)@1G1c>lqGX&6Sgv=>X0WH zemE_PiYy(!;@;OUL(QrWS$kUG5F_P8bQ)nrZ|NJO6{gCJ?(rrHw4%!ZMoWZJPFB;t z)9y9AF(*V7XG-ato>+zX&(`}{u|6+pk`OHidW;ds96H(W7X>RtK~3|pvEs`LS=ssV zEXH6Fj}=`>0z-Y3d19 zTo+^)efqxD5a@q_{QrA=NA2!}7x_EmzVyyUYUE(kWxhN>p%Ra{yDmTH!pmC%%GRhN z`7E$u%Jq~1U-M2TL5OsL}G;@+E?_>KDGU zJ4z&Ue1X$3@$rF=Uj)d+DkKU$0BV%i-y{r&HkHl30itu9;{_C~RpTzy#*lRlC=~f@VlsK_QMA1ta41 zH(FqiF)_>U?JB?*6jtsSS(XgT|30zbCu0jvc{&wBKyB@Mwug-=gAQ$w9!CPZWj|aG zWZrQ{r#uy12DhaH6AL1vSpqI~m|a_sVhA?+POdvx|Ub6$9sk7}5xs7QAN zLu2pA|Mdl^H+8tabl7^&=tX*s!@$6xvS%^z%Dq(a(4xv}4ojGvATI59I!>f6F{eIq zMn+-P*+4@6VyKeID``4jfZh4cdY7ZFCyW#*wbW{@(a0H8-&9;%zU1(ntnINB)(ERr zg4?tdjszN$T2g@Hg2%!H)~#G3N92~}IVPC*prwwSY+MFKwPQ}rD)Xhcco;!!q-?I& zzbd^XD7<^BgzwxrN0u-Yq_C1wGbXkUw#!e1f3O}F+N})+k+UfWXW9gIi*~H`MX>W^ z=QC@Ub}!WCtK@~l+r_;o!RWOU;^Tv(=+_KN12VCyBl+kNL-33-NnnWWEVB+O%~}li7jvRXi5I%uiBzEHu)ewZM{FQTObnW!?pn zm$c+1h%a{~a+yEoS2pU{?BKII&1!S_>9YC0{_J#dnnudn8fkS%eRID%3?d=Xr8m`D z=@NC{&^OpJL|$!R8KPLu)cU8t2#}3cNZ}#+S)i%}r9uF(v42{~pJ9|D*!0m=VWRCz z6+(vfyRAslLD*Ac!va+-a<3^Bm~sS38e}L@^#S zu=i{VvuIWxJ6}nAs+MSVlp1tN^f9UyXIYyC2FB2?6*D@dx)SdVUSeV`L^|K4MC_j% zDAfG?4#c6nC?ir?#Fn|DdqQmtzD~?ImwE^Sm`n8ql}HxJOe`E4M|tQj`sv+N z+sPtw`@7@GHyIA*Gi7LVg>&;GjtGhI@c=;hk)YEp)o+*7qB$5NoL4W@cAlJiR}cTB!rY^5Cl^MNfF#+wm#wbEs~oM;j)KR^0rkj;V=P#_Kq z^wbY{4^mOAs7B**bY+_Uo|T(K9{`Z}Y%(U9#8X5|QJeEBA-q7f*flH6CtpVC0CCYm zvuX)w3@s<__L?SWqa*b*9lC|_7-PC{6htf?+yQFHR9%kw_gUeu9uNX7tdUsG+7hd=f@1#3rn46lSNS3`415c%bN1tw;eNUXUrT zfEK4_S5jIUX*PwI;Noya3A*m5I8A7CvY~21GQsadc5)>X@3zm`fv=y+o&r!F$1sfY zDN6t?8+Qz|X1Qz8?-Q%vwc5wdMH<{StA3D*z2Vu-$)8NBrRE!58IPXlb;$ITqf^}e zzD+U*$J?sqL`k-Wu}A6Y>6Skg#Ix`fm6YMcnkRc+yyGI05Rpi21+?jBIa?p}?uC?e ztU>~y&3t8LK${UDTX2>P6teEIh6p$vj)+TseTm0zJ)D%0HdSjx!sA~4X50~>Our)} zT{bRGq7Udf{6x`j7i?#EEvK7(;ZY>|fEUk>ZG)j^rX-qp#E{3Dc7Xw~(mXHf($W+U zo95J?l#X*^s&(2UUmYwBHwj-$;(RY5?nBKefM5_HkIho&ydf0JUs`}%69ITDBCwtS z-YRD6`W%?oMrw{^k*k8YFAR484t}LnMd4l2&Fi0%&-0zqCTIv+k~v(6bMm`$RrgXe zy5#9_W?jwbZ*R_OM;&HsqC;DT%%`*G(CyvXE4shDs4lvo62z(lBpSNx9o$+^ZIq zk~mEA`1}H8ZQrE-0c5k<`k>x<2D2n-?EQvR9}T2dwNULrG@ZhyZG~mBwZYWr_$zcmw|ZBY zoNJU!>YMb)Km5$!c*WOC#k4%{@M&l;PR*J6VMZ76kV5vw z0x$+*%YiO?jaKDa<~lW`Jmwk^UE~J7ZFp&(W$1ODGb|e8^%qB`v)3+=%@L3vkNX3h z=j;LJ?wmq7V;Z2!4-=lqKzHR-&;SQqFAZ*6&qgl37FXM}T|VeFE1?GT|Glv+ zew*C+)U2%NptYT0Bp|98cQBRT?1=O;lWMErUcV&BRFQK_a_j|?)T|FLNB|3_#-Z?z zV10L_l?W9R6H17vmTw0Is~FgC7~j%I0MHC@YE2gP5<50Idx@kk(wfZYJ|T6l`U{OHhlhIC|W*-x2M;oEZq`Zr}CP1Q$@<+tg|y6u3kg277u zO0H&A_-}{L4AI_|X~ocOZ>BgTu$w|0u=ep7ms!gIbJzMG1Pa2ea~=sf>>&2ibE(`R zZ2{Mv;n{~Y?axtJOSdh0fClrJAptFU)nm+ z<){%8KmL?MUJ??4Unp^qu^8mZ4dCoL_p3S=1}2Ioa1V?gG$2SNe3Tl_S%VoG)4s%t z7?TxW987DXtT97|k&EXdfA{Vk>%<*U1ZPSOS*lgqhAFE9Ox|qy6ku8RXr6g!2(|o? z3)iIsL)PTs5KK-7C|v>UXIf*fK7_%-&LGH&-rjyKQ&UFiZ^V z4V2ewmIEX}qX&>Z7W^G84psYz$G|$0MQb8$c@~-2-_g|lJy&gL4<9F$ zZER_c(rm+tQ~Q#1X@$%OvjnPNT<(dkw!J{c#=(h9O??22I>6Z$I89(0_0oK?U3r6F#h3X9`BM}2uZ>5721js-j;v&f;xX#rw!a$GQCn?DO=iur+O)bbWzY~mm9vB0-b z2KMJt6b46tKbQ1L{NJq3KcN);=lXxk!$YrpU%bx7%TnB?xOeoTc_L!)iRJqhWCso+ zNo^wAJx4z#3g_9yFqmRC=*$ptphy6Qhmg(TUHzCp_|j3#pC;CWFL={t?E9jxQG$nl zT=bko&ZU0o_5b=Hcni07)g6IWbNd7%=@EL^tHJC~O>gqO)R?c~v$>p#`Io*5{Vzp> zFERh~8!6$sq%`t*w~s-J-DJq^(jd1mA|Cp{>4UIeePt*TY_GUBjh$zOqbVAgej?dTE&;sRx=<6k< zqaC&3|MHFP?F_1CUFecj_F^zx$N=CQF8)GRjvXXu_9Y3CX4mP}|1(H`yVWt{%^u7JVL2G*Uge}0?jE_F*YkbhJ)Z6q)inB{{K}7) zuw1VHhubGTEWLh?9u8(s#}6Smq+%_wzAv=jqhEx~4{v+U%Nu8^AMkto zG#fr`Gmp8e<o_bBQ_t`h3ZpHaH zf+bpv|80hXq1y$qvz7;fLv502`axW%^fi<g+ESd&enfF*ar%0F(n;gfy|6XwsoCmQVO#noWnBKSidzp>_cCUbS#=YROPM z48MU%nhlXGIB-;Qj{+pn{nW*SdmFUK;=hOUT;orR9*jKb@vGWK{hjIsCr|Kl+e;=g8yx($e~pAwn69smC?+X z8p4k9l$iTzfk})~^AGq3Vq&nq&+BKfFE;!fhT*rqpbTHcfpuu?=X&aIQcuXn{m*{& zXSm*I-Z$=Kf0;8^3Y)YB4lPu&Ss!1I$BZ)mK?uw@Vw}}fj|&#R9Hw0TVGF7n9E>l_v!xNG=mNNBnLa8HP3^s3J$Y6z zN=c98ExGTwq_s40GkmxBbp%YES^B8imy!3t5)TuGKwJq425$ZxAMfS_)tO=8%HfTp zVM2Of#a^%7Anaktyz+&AYWV(L_gHhxSDz7>csb(uFz=~CpD9!%<1iAB?Us1t;TzH7 zTFPf71emy}>o4HoAjnAQrO$Ahfz#gm9-^`{TLXMOE@Q-)YX~b65;8U>?&3}?AKma( z`me>W7~^6Dg?_Inn&-N~%rP49sO#~>zvGB+n8AhY-@ev9gETU7%7=t

d3Ib@z6^ zGCz-+fBoR`JN%E5NJt@=n8fc69HVim&3xNF<@r2T@}@jj@s`mc4g=dcn(qq_&WHV; z@o^(zIRa?FL&F%06A%{M?Uz>Kc%FfKp_wM z^~)%a8e1~gz=XKRREr)5fX-RY4L@e_Kl?fu7+6YK{7oHCNj3SUwRJkVZOjt_Mt#wk zv?og)@Dd#^<8kVt{#`KQEO-Ev3v zcHLrA0a)i)`$s~qw7tRC^pB$UlSLi0Eky_0Q`mvUK z@r$^e>-@|e(^6-gv*s~%h`L`=+zIptz%!h*$zEC$a@43{@DJ(t>jLtqdayc%=!fb3MJp}YSK9Nfaur775S@upO2H@Bf}?QO95I$n1x*euj&$boE8wrV37I z^(`X+*2CYN;)XCw!8k-v;_`!=47=oV@Dd}2=s3kAE=V@t6F_!N~1^H1%X^rC_U zJ%VmtyX{n~t5v~zD)~gq zd(#`T+mK$nwM4TTH?S7}(@@poi!fQ&>AHdC0FCLoBJj>BF5Vomzc}OSpT22MOsBxN zftu*8t8IpwuU@QNv~o8&B=n$h*E(tDOxoiK*5#f*W23v9|MLRLMDhh1VAi%DHP2AN zWTeL3k44Vc+{qc56e^U6UkT@(Pw=8b{&b~&6^!|EpL0ou(4O|uvZeql{U##lwwpVO zdNIO3Q9dFJ-0FQrdNCf&ejf~&iLnGOxR#AYV!*=J{Y#E_nwJjQ>;l}J_E zn*zTM<~a@2ELQw-1ev?qw0Zr1J6%r)7-8KsH|_k4{Ex?+0}Z(Fe{18evU*x0HZ{G2 z@}IJ@W=5%4{3E``u&_vkRJawNA)eCw5vLA=my-@HuwLG_q=@|O2LtO)D*i^EmRii= zONF1+LcxCjj+&y@p?qdR9yW##B4u(xjYzm1U z9@R@EH@ts->eT-+KWiZp9A$B4Y4(ntze*D2?>)MeaC{g70$Mk&yFI4xzls3d=*{I9 zOz^BGSIBPo{}e;T^dh9+UL+vOfMc|>yVttD>6^))Tai?|PmZu+l9%Mt-tGM(GD=J< zEKt+)cO{6l%>teey~s_v;mSCwtLgclFui#ABHLik=-H`wqg%hgZ_Cw zF15Ey!8R<@10oERD9}rY6%31n`L8lqilT(=9pmoak;cq3Yr$!Ko+O*iej8HJ3yj@q znbrn3hQRPaNcA=R>vVUcxB0K(1ybDGl7abQ>L@1?_@RG@7`Lu#WSU@IY7fe+c0a-1 z{hHlu$njW+8P3nl_ zzTyYtv+SPU{n4LvlO?>@P-C98^uPOE??5Y}!z+AtvNc2JR>ICvrdD?K+VtbjtL5lN z=s+n{Bkfndi)l+z-^@Fn*R46%k1MUuEXfP0 z907OHC-?IaB`ZlxKyXqqpsA{Vkuz$m>vcCuOehb4{JBR%ilP)G6A{v}zRzXK$PQv= zAuC0H-urkqs>p8hOM3g(BBw3QrF2J@klm2#_*LlYQ(`ne`*RHQy9-2+!$vZ`KYO~& zQrp!)uW8{`B?*k2F%G~Gg<%?J2F44ARCnSfsi9}-z6a>ZZJ`cnCGD-Wb}0f?O){&tFWzB8%^eXgyXq9Xl6a!*jw9LEmBQ7HZe-gR}fvoJ61y^{7jNE ztlhDYe7)P-HP4OC6o5lt{8cLVRxAjeg>9%kiC*xMy(jGD>O$1wrUi!YQ6#tRks`}V zkHY2WO1g&XzOOWnZygg3TM=pq&!MxkU)V;>RlAV&sj#-L?oF(bYgQYwrsiFdVq^EJ z|4fQa4bXMkYpxP?2ez-9-N^5E)i;6#%bm@<0h!vrUj+t-4!YIPSr^Xj6X7#^J~^AD zfSekvBu4D7ZoQxxDqWf=a)}~wO(FL~41NAvg$%?|$F4QJKnU*)7|>n3ktny5%3;>w zq4-%K(z(HS-|#HL7Fa?K{>@IDZQ4Td0WTOB@(USpvA4X}M#l)BW*~4VUrHm__7k`g zd(1o6bc+Y=%r`&suU{$-3H6{+y6ps)QfFtpyUBJ5zdP>3AE{MH6$p?Sl?tZgefe4L zSx9!#%m+7DNdXaO)86iwM|;ruQt3g1LBc@kD$v$x>wb)LNUD3b!bmeGk1q_3MZ&;u zN?My-KCoK)ie91t4UYnLdo~AWyql|4`FQt(6Aj3%!k^8=_Nn1Tzd?@fqpW2~bT^M4 z6W3oqnFx-U={0snh)u`lU3$*8>;>$1ra6a7ZxGJ}?2B`A3GH{s$ofRoAVU-G5Rh}S zZfuqG3VnN!b0y2BZPj!(>+{S|rP&n+qiSK|)i1s7);XzLT#vm4YKZm9ua)VElMkXH z#$h{YnINVc_@?xFG}uizW@SxRUV+GS=*4e+9B>WGUh`mbPgaCeVnyXgkx)s2Y7u}*k#qNie#Ku*)=@Jk@IpZQ7S^~KV(Wg3f^n3zix z#zitA8TdJB%|X2SygN!ncuqVm)v9*98Dd&FoeJGCfUm(j4jVWKDXj3H@r<6g?wBfn za#UY@X)0e*VQ>N2(z-QYe4%6)jK{WNX;-LKZO=2bMdcszvL1Ra0LO40*;swjOPJ2;47{PcN5Lu1x2xUStfNBx`5Av@hT#su%2E$)5&kYuNe&96csUOZ;g z$o|NkeJV{Orp<04#w+~e1)8D|{AaX(it?M<-oba4l*~TA46^-Iy<(8m6Ba99izs(L zc5f5r`3{HP$tCk}43&!ANOsq92IoBT^``AxTjL+kyJ0H(>2bL&gGw?((X@TZaCR1EXB_&k84APflidTw{yj)hQmIE5Mooz(mzm#(2mw)Hqq~*{ zKF>oWm}bLX9s85XpiDj+#=Sw-6hVhB?+J+QF;sNN!eZ#9a;}3v?CpQqHJPAoefs>= zZZcWoA-iq!b5riaFpBvKTpLh#J~k-WTmafQCtqH5$5^)=8NcpAr%x z-OP^z83NhfPExPcft7K~?@2S{00l+{l`=kCYvn z>%E(tlo319R|Vhu!)17Wd@03t5;c zYJP->bzledSp1>!$510)JpAF>ge=@->pMJ54^3Ce70&~hfRm)Pw++d$i-bif^^uI( zxnNZl2$jsO-{2pvMI0%bC7S?umRrQ~BWA|Sb_0i}w%y|E4gym{Am#yJVxt>K$#vwW z-x-|)*&=(02qSZZ@{0e@nr}C?_l%l3ZvyoVkbiw#THFn4eAjaL27RJyIbdn~gf>mV zdRjrYV-L}@&w~t$+{e{n)2&ouP_xwdQKQz~``xWxyaN5sEGN7KozZk{0<3(~erCzjDO?=$8~!3HGK0xXEBYYsqq()kc{k*-S4q-L+RUwAt!*wJ*21m5YeyMLlBu>9pDZ2npYAcMEx?fUlmz z%30hH)bu3men=P)YSN1 z;xcXQ81t?4*C+uT1sK!-W|(KDri80gyqSd<(T37khM+}v49G1I-H3g8h*%$UAWO2!1ly=J{eadY8 zr*b`d8DM52E$KW!x@*g|c^K)=%YQc6-?Nvl682;|A0#BjcEKi%E7p2e8J~#maSABf zMF;Bov6%8lR5<(rIX^=RCmkTR4|tUmN7_^Rz7IJm&1$NeB$~u=a!XdE2?3G_gpkhM zM^+2Hm3=i`*`aBECY#*Wb-Pc<3!>LfoJ8H1hS!+!Q5t`ju@9AYnf-LE6aG_rI1)Nq zC>-J^;i$IpL{ddQmJW8X!7<)PJFKxLw^VwSIM*>m{owoo#Bb|lz7vJr(gmxq7a_(L zhIvgoJC}bXiG|-K-wx_ZEIcJK;oNUeh_`P%RCh~}SC}$?WpW$72g(g_MGCh(#<$`X zdlRj2j?oQ(zPA8btI1m?)ixBROkHI+n^hAgaE6Rjw7fT!c^M z8W3`ID(PN{eA>5`tg-$4#Y-=G4lw?7uep|Z&b=mz7^3p7rSl+a=qcnavo5@bg=1r{ zInPr66wbNp(%R(lYiz@Lk~Zba0oMBlU9jx~{IwOX@ZJSL? zIxLrCfa$rr@6wr{n=N#Uw(oduZ#n^AdZFJinO~1i{wsn-)B^~7P>yF%;x7(3^=Xu#K>oyr$(bzs1H{z@k^DdI0NPpvt!3sgbXo(uNq(1>HAm z?zQzdramnryqUX_NmPAholtfEa&H9IeFp$GAKP8&qdE81eLQ6e*PQ6{S zvUv0;{FlPmixdz_+4#^6#!#WOdu{$S_g1=OG`$Ta7Ko)TKS2XJ54H1p z8rc!_1J-9!a#o`q$5prcoim0&lx;65bMMBsCW?kXMK$kO?H*(_3dPb;#e8 z7JKAfyc9*{D(++w(#`E>f#Mi>efH!X{W^4TJ1nTxAlh$pYK??Te7zgZW1;TzyN7qA z;E2$QSHht}LIh^OBqDSE#s%Zxjku#sGRl#0KYmeN-414rl1h=hm-Mml0sx%QsL! z^h8aZ#tK0i|h#)apWWKY>plMYJe$QM1cdNvL^Z&f1N zIKEMnfBh>CKgp`HzKjL2sFW)@!M0%s4X^iXG>H6kT+_avo&whmpn{?X?w4;3D_Za8hRVupq%h1-{mM}r z_Cw?d>6?p$dCD%n->RVQDD}-7w~Q_nJjOz6S?G@4{hOHAJRAtl6#}z`HcZExrvU^S z%O30(EHKrR^i$N=O(lHc2CG4aj=QbuBTGhoF{;(Tw;ae879ddG!wpS5DVEIowv{M; zz!;ZFh1BS!=;BVcOPC59rNE%CTb0ACCGy3$bv~2|*EQ^Spldw<`E0R!D9;#5wcJ)=g(HsG#$^{(n?i7sY|5(DEkfSMbPbDXD%{jtE2ByBu<278#pr>`)I5 zp7fZi!~am?p+)HdofWT-d#IoXv~Hs0CT9dQQO$P_^?ocSYhq%x zRr1kmiidWrjF5m1ikGQ1I5MCH6bf}63kY|jr3L49o3d2`s~_}*4!l(j(wKpn#%4gv zsFJ8DfpG%$iEESbSW2&<^!(v|+l&_T1SXo)hgJ7MxvXH0P$0A+;*#R;&NY{w zTAgC!G7?6Nw9#eC_nppK-uE+-5tsf$fJzs<)WgpzPrf{Ra~7?YSh0-SqZ5-@XmbT8 z&Yz*LebuU{DJnN4E#<6MU6r)Jqq>Bb%Jz0O7Ha8gdfAk;@ndZan9kV1+P8oEG723i zaD`pb59JSN0k3_H5y1rxl={a)jQ!0A##yo|;?+pb8MO*d4UKGc} z8n?Ii2+{`qyY>zlKpT+I)?7(%8NmWMk2;i>hen;;4;a=(a_iR`a){`U59J11k+s5# zOn-ilB=LO#-yXLJ-lA)a!D>lQowAMpTcpgJQhZ$I-Grvo=tE?D-p7pNc1jW3G<-tQ z%IVLR?^W&OCH$bI3y#?W`XRRoq3@rFE|wCOU)Nn9(6!?qZZDR8#~~#SlTvu~pgpCr z-7{B+x45SH>RvbR{U6=DkG}Ep(ja60=Mrxs_+S?iPaYgn4Nt32a{t&2wh}qppjyyy z>!m;T34b%4+^k0&GtDqj8fMpVtPt3Hql?k^wPp+x=q;`D&(9 z9TPOF{FYMpfl)MxcLM{X00kjL+_U9RkDQ8Yrj`YpuAv0%^lwGKxY^$RX!n=$P`h=G z)*n?QH{@)=!p(Zi**#bDZ%tM4eD@09zcqE3>3oS#(Coha;@cq~%uLVNImpTgYcDbx zDWeT_Ub4JaPP=*iX17xo6D~es8G67b>8C@1bHb+s)~bx@# z{qhr50y~s^4td;|WV_)peK)TlM;?UKBk<-mzpa*8xsM$cI5tQp{>NgKtA_GYg^Q9a z*~{=Kc6fiAP`tyH7vU6#^s#rRRJp_Q)xAU0?nIksE^5u95l8%EYhFCi4D83ivuRn{ z8W_p3Y-cE3HLP>JqwvB47Zx*vp{t-LSP~B6dbc)`?jzKa-dOnsH4l>P0l67IS_Wv3 z%(u2s-fCt$OAU;&M`Mm*OR|ZWy1_~+F`x?vUqb`WTJ&0^;;i=>BsFu@j0BhJi)q642)sthWfWKA z%K{us-5(z_j6#u+4;r@efnwOYTKV38M|g_#kO0aP<4pvFuF|iCh)V9=cpS*{k^o7J zwE<_SPnO5hGk0ctyN*lwgD|LGZbOsg3fQ-_-56A7&$9Am7$5VAe`uG$e2ZO;(f;UD zR?IO1azz+sKI7--{L-**7s!3P+2EgJN0^uoZO=S3_$V9ot5leC?%ZXNqs%e3GOj9C zr=-E$qx#~ztp}gEa#NsH3D+XTsz`>5oY$20kVI5Tk6FOI>x|woO z{lYNc$>PP}9eTQ}YPMz{BY!3JK(8+UQu%llzv}qzo5-|Kn;J#<^Ae>_rwQVbn)lkZ zO8uULASf zVvAIMsfdt$5f3RJ6;&*Lh}w2Qk=}1Pb=JF$+`Ns0bFrrwdlu8Xss{O3qvi4?g)q%< zc?AOabvJ~}VO^(>w%XiCs!kU8)YoI#oiamfl8l(`pO`fWaB6l9A{Im8;n z%FP>$mGD9gRg$xlg=+O}342SHw4Wml+Sw?^eI^BOM`o2f!VwYJXNXS%zYk_3gyYGg zV9Cl#pi0&;g~^Y{2gfJo8+*DOt6rCLRql9 z0S@8;s$fYXBXx0H3EU|wJQy04yK2Y$e1T0dJU>q|<)+};!J64`B8HqlmD?9vZP(Y? za7!x)NA}nt6)k!f&A(k=o9l(&T&IqYoF6puWK$&$2xMndCvrN-Rc*$n=4DKFHnC0r z0*Pc`V4lMLuHFHkFwxKFp&vhR_`qN9V>op1K`9Ipx4^(iE6jtx|L4~K8IAwDv?0B@ zT9l+|JEK`;WzpjR=uMC55$U6wi~g&FOOlcf-n*K{yorX}flB9_u!+TnrbZG6ZL7`7 ze;y1Pb0FKhT~V;vJt`mBuwNXQYr;}3)|rhl)4f~ONOoTn?;jZGFwsqpI2)?3Vwivj zg!>MY5Bp+0+N*u-?shxJYiMYwD{u4(FDbt#Ij@TQX~)I+T9e4H;rq6z!G(*X-(B*S znw-rlr&(~>YSp;%gFL?j$~M$g`a`=7Hv6iOUAIC&`%mRyrKols1;fdN$?MW4%Qufc zciWu&`s|>+w^_+#vw7STpO&V3ahZJ&785ca_)z|hYBmj0e!FO_~rJ@QC|w_ zlR5s<)%$n(E(U{i+rxzG&N>g`bg+2AF5NsJdnr15!x94MSRM zn#!_hgNDeZ-k*Jrme8VY;KJK?@t_SZ0zt|@@`u>88*!i9>a8JwyU|kf(z}+C0I{JH z^xrF(G>#PrBsmI_B@@~w4hp+&96C2M1&h!}^YBtgW?aYo^p;#3EU#}Pau&B_bxSVm zek%y?5ImW+$j0`rf?>RasI29_)JdSo1tDlRA+^MsWEJ@dB@7{ki3lG~ zn)~HJF66;Cx*?Gxt$OQa|2G&9z1bJ=@L;I)UDg7s9~d6?%-lDJ^KO? zBf=F4W@~i{z8U#Xf&0fk04{P(QW({n+YS7)*gT2|zQA;fkJOA5NeLstbVA$EUUOu! zw5UpuDY_ov$_G=xMKUmXH+IWszyGcJK{Mk&^{T!F)&GB$Z(Om_+G8w^XBb$3PQb`W Myb>>duIKwd0JS|}1ONa4 literal 0 HcmV?d00001 diff --git a/docs/studio/evidence/task-planner-titles.png b/docs/studio/evidence/task-planner-titles.png new file mode 100644 index 0000000000000000000000000000000000000000..b40fc08bfccfa5cc67f8e6929382e09bd6b3577c GIT binary patch literal 143763 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 {-+4is#hrByWz4?;akd%(VXE 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 Wc5?1Z!`vKn4X7z;DVE4T3H%?VF~~*$ literal 0 HcmV?d00001 diff --git a/docs/studio/oneplus6-validation.md b/docs/studio/oneplus6-validation.md index 9b6e85077..ff71208bd 100644 --- a/docs/studio/oneplus6-validation.md +++ b/docs/studio/oneplus6-validation.md @@ -35,6 +35,14 @@ The UI is usable, with generous spacing that leaves only about one task row abov The harness's machine-readable status remains `functional_passed_visual_review_required`; the subsequent manual review recorded here completes the observed portrait visual checks. Rotation/landscape behavior was not established. +## Retained review captures + +These unmodified screenshots are from the pre-rebase device run described above. + +![Installed Task Planner with Chinese and wrapped long titles](evidence/task-planner-titles.png) + +![Android keyboard with Task Planner input and Add still visible](evidence/task-planner-keyboard.png) + ## Remaining limits Malformed saved JSON preservation and save-failure reporting were not fault-injected. This run does not claim a full-app developer-revocation fault test; earlier L0 denial/background tests cover a narrower path. The app remains a local `dev.studio.*` developer install, with no publisher signature or public catalog admission. From 88550095ec519f1c75b48903a9291f4557c452b2 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Fri, 2 Oct 2026 21:24:31 -0700 Subject: [PATCH 04/15] docs(studio): record final OnePlus 6 acceptance and review --- .../studio/evidence/task-planner-keyboard.png | Bin 204911 -> 205201 bytes docs/studio/evidence/task-planner-titles.png | Bin 143763 -> 143748 bytes docs/studio/oneplus6-validation.md | 16 ++++++++++++---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/studio/evidence/task-planner-keyboard.png b/docs/studio/evidence/task-planner-keyboard.png index fa404b30a8054d08a858dc4416d8bf4ed29da286..881eb4dee53c3c1715824be032bd5ea812c10c32 100644 GIT binary patch literal 205201 zcmeFY_dlC&_%@CX9Y$MKo3^TIYf+oFR#DX6QY|%O&mcN!t7y%lcI^>0Vhgp3+FL~I zk=QGQ@V)Z>Jm3G|d3}C*{L-7=x$kS7*LfbtaUS{nTw9Iy66+-j3JO~Fr^TjO8bVM=|ImEdIWPhW zis=`xeI@_9qDU*a0LBY`-ppNf`=39xb5H-zMTA--<$qV~k#ElYcOk_|N&Vl2n;aG0 ze;3n!XW9R|Xx#eWc+k@NI(cC!DEgABFxeKu+%_yIY+SD$Pp(Snc`7=u1T@EK#3MLL z2#uU;;2__K=@q4639%{fxZo^%&|E=Dr`q^dTT}a?ZMpnhi@GSu*=3*CCV+hBwPw*f zgW@*KZN|Z>`*P?wK>;^z@lCrx^yiewH{6u(sO<~stNe!94)+-tT-d9+*U^x80k=O| zXS+%l2%aHeT#q)c(@0ndXAX`shFq3Ag9HOrbN08?qS zP7Ik`@8P%}m-Qnuz%eKdiOq66eLKp4TjNKJemQ!0wCk=OWg%Q<-j>J1TYFnUz4gg- zJ#u*9&ks2;(p};2|8)9ULVlmti0bZ`|Hw}N^{a<=+_(Dcd#ljtaP4;yJu8fqYA>9P zmguewe#KC?i&9ZL<<=fIArrH2bzNfjni**I8xH$>_DNQY>!>IduN=$+JonKhi)Yk` ziy4P-);g96OtTaf)9&jh4d&D^NbUSiE8(?s;d|CW@qOC*$Gb4~#(rAk)V1*WGFI0{l~!`H;TncG8&o zxn3Kmj(vM()cA8k(iu#q@mcn@j})KT%Z98f?;u7UB~3OD1zFmfFlAJjZ*+p>sRtJY zj6L02sR5DjMoE7Y*8XK$MqZRjWj3Immxl-bRO1fSTTOzX#m#e5l_>``yEp3ZOiOKI zKd=7oo092mE;X>_C+>WtHftA`Wn1$IX9tO74qvv6^Tj>o5AtByw@bJk{nT&$6Fbzuak} z4j#u2Geg8(i<}`e4Kux$pE0K|Qau(Gzk9;YH8xZiN2U57&USB6ss+50 z=P=XB9rV%u;5H%4j5d!L*@#rog(}8KmqdWbJ8)uqwl~Be3&vA}ZE9Yt&Ee2~v*-lGO5(u8ZCSml;s-k9=G$KgJXhxkZ+KeqPpMf;k&Nce>tLYD zJyJJK?B&I?WY41eVep9J3m@3!bC#aGQ0Z)Ot*jnTJqYK_fLYQ!)0d0wyy{jdDUb)p2~))3|<`D3nosjMc;S5_X!&;^!E%?zvVT1%t%t;_ar z>DO;uHTFyE7kK*0<{MALR3yv6qAgdf!$zj`P~aJj>1`X8wuzWZIY&98jL!x-xF(N# zZh?(gtG>lQ6BD)I`EiA_?q>nW3-4>q&OM1Z2p&V5UzWL1MrbRrQbs`2ls9O>V{SI$ zD`I|5o)}PV*`R$YKhmsO+MRDsxmnEek~}20Z8p9>{me}5%WvtCL4D5}nrc_KR#&h5 z+}&;5_X@g;0fXubRKOQ748GO8SeYC%vQ##|Qvp%)*nXR;gplmB?OA)6P)V*G^--fq zW3h%c0d(IYkQ~Rs@%U-~>-`cDsW#;mZnr){D^oLCca#=9Sg6A#FLT;kUNoFF6rmg0 z-=pzybwUpp?UlBqms^@`V9W^r$9=j6_Q!c*C{*p9G8X7WwPj!Fokq4z6mwSu*ty|_%gJvjN z4F+uwoeRxX7SQF|ejh4w_H5|w-Evs}{jO4aDawS!6Myq#xcRz~n(j2o-TTU~3aojq z_c$^#+7&!0t@7gjhlo)XIG%F=lcmEo+JK@Ct*TVr2tSaTr;6BbsW}sB?KRn)AwW$Y zk3AHEMluW{<)ztF=RZ5687$FDTMZzB(`zw+24MHiPvxzs1PWSQ2Kv`i48-+zr#^A1QL~4*Z}Q)cV}y;5?$f21 z(3)TTaD|#H4IVr?Vi{pv|H%UXM(NVe7_(HldaGaqu_u+1`z33iBM3LZD}2$Y3-9VT zu$b{1pcY%-K??Q5BiEnaoZM{B)^%3~OW@(?} zqqQ^dLcP^*=o4e(*X@m=4bY->{_pIS8iB^jA3@0fmv+%k=uC-?^Toqh7@ z{gTeIBU2vv6deZcQwpR3_~49|u~(q$+U^pkw;HTjD=mGzCUl73k)B?q0lB2gp5wvU zlX9Nclb&Fmi)Y_Sy2=J@ZNx}lT&cer96=}b>(O%G{v)?qEf5;A(;?P>#nGw3nVHrU zP-6l2>O0M%XxbZ5Q8Fp)IrWS>G6N6mzZ%#79_h#3tk()Cljr40pzeNuZFP#N<4W{? zGi}@vfM!xL6>HsWcti*i+O-kCPanQ=vXRm>bH6`?LYoz-16C4$jxtneu$;c8(Pe`U z7SOwPZ^db&dhOgDj#%2gQw|4o8~`XzpXHvDPS)3|2d12Rn;r=zy10j24YabUCu0Y< zep~EwE@2wDJ4T=nIceD|gq|CyG z>llz2u@IFsyHcpR6{6m?HQB!Hvg$ze1@Pf-`#Lt**f$wDG=~w&Y61mPwyoE_BCXim zZu$s11?x@b=qwk%{5}7RQ?Mw2{#@J*x;m>SE9Im3!>R5ZweG% zpUtD5)#lZr|A(OT@x^`rbA|DLrWg3X%1HjNP4E9b=)djz-{Fz}Z?XT+Vozc)6cp*> zNRDlHz@Ub%dGRWc8U7>tv+R%PNBa8iAw~uANMYuK;<4aoO!d}GVq)U(XyZqvHer6s%1to2h=;c?Lu zU%!cV5d3=AI0pc0oHH^_B zPqGH^q8MlN$W*=IHn9$aiTMFWqpR)X<12aQQz8_dpPwH!ieGz?9aVharc+!iL0Cz6 zFlNPruVc+9RUwEfGMLe4x22Da7o}pZxGCplXB(aRFPa(}tj9u4AJELbscla_tSmpJ z^Qzh|QaPG@ewrfZA5~n9+kOf;pVppRR5V!Di~2PnA7FEcPW@0O5ET^_lbD!MUM`-M zlf^G2Z+RGWMBRZ<7l={Vr(!ooiLG!1d`Ij5mQ-33Z{6PuQdHinI z7t+~Wa0|tLCbe1&ad#yXERTQC$^PZB{kIpN-Z_oC)BLS>3Gdb#bF*1=-lz5s%i1WD z#wX;jtSl8H>gneIfUS9`BtM@(2ECB7Mni<6yaI;+SWb>GP#ghGm>qD^&KuZ*Q))!yrBOr7^5 zaTj7wrFYM`ofob;&NeUow{AFX_(Z99(nIJ}AuZcO2MqD0;X0-uF>#_q>A6v{Ri+vz zDH=|ksp?#Qogfy3{Y+g}H-gQrvI5lS#)= zEFkx$g+_stC>x z{k(6=!Z{`MMuT_Eva+*7gpNkcH}0mXfEyaq-ve>-<#MT}$Usa7Lvx(@ZfjJD0D}DJ zPwYUhY#GbrO=b4^p<&%GBk}Oz-CXjg9&M;KzWq@GH3vw$w2&_Noh>Xs=;(^0VgvtR zG~o>!}c8=na`C0Z)8k=FZ-rnJkc*o(f<^JL*%tR@)z$boP7Ya?rv9S)VIz)|b=zDl@ z{g*r?v{({YSY>R!sYWoT!$~`_-&2Zzt~%S=*xZr3QzEDdM`ht~j$mo?8`7<0eO8A; zw2}->P4n8$N3zO?J38D1HqL4tG* zYh}_lkE+a4p$!i&Es8+>s-1KJukT!26dy=wdQTlnD+S8PwV}k~i=2e;@bLV+JVPa= zO#TA?3jZA=C{{OPBGGYpSTg_jZ(S>dvARnoF_4ieJ15I&`VA$kv}08W9ZO+(xq+>% zt$-x2d5CqV_^aT_069(x3CYUSLkXG%8@rEkM(>ZzArM63L`~3o~o^ftZxI?5|(FKydmvrLnDcKL>A_r~${uuRPnW$Y*V; z2&9}w9cYo97AFrcuK=%r=D75@hNjl=8%mOd|G=Gv#n%F2-1-To2^tZD(zVsqD&Z*+ z*qq&6gT0l(91O|H!9my5^kZjdSH)~s>_iQ;mS2F7|2zG$dPfH?J~ijvySGGQR=oZ5 zrp6zYK)=tK>pXk)BK*Fv2;%v|RJ~crw3v`(!xJ`17d^|MprJi5WygV*i!$&wFAo3t zna&NganO#(+{wZ?5(<_sx3KV8z|SDJ7(S+lwJ@IhTQ_F~;kwOk0iK~GQbC5*D(1p^Z@ZOFjP4T#cc z{S<#JX7|k17v+pL@>j{qFDR&RvHaa&Qflz=qm!YoY2M;uoKIzZ%=}2z{a4~H$5I^d zF&nL)QdXpGaD={p?-Q-u+eqF1a!JUl^cfhh!Bzl zzb;+61d8!X;-LUP|3ImTvCn}5hr*sHN5E=7^)brUlpk!|R$F)?X`4NWIG*thElQ(b zIkk=XQNcp-X6oIWGuEbYMK~OP>^k3|69QF4uRyfNCO7ot1JslOcC4RCa|i)x=#f5p zmK-pWz$#bu$iczE{o(~G$W4I?WbRYMKYzhGIpQ?6w1!)mEOR}3WVqiJdB)Y%)q%Y& zD6mEQMAEc4qvP5Ke+>-CC!(hHyMs=Hpkr0ln%=}fIAf83QGd-#`5i2i*F+vKejv=w zq2<#|XIFRl_y7ivA)fr_KFbA#QZ!_mmP;md_*Dd8TL5VIC z)HF04p3pbSmc+u;c9`Hd+@%)whz2I*+8?wU;tj(}ZV)(2jnqF%B; zt7tD393C3Uch;%4g0Fc89n8pqjObOYy;TUr#hjs{>nbgG8g(HpB`ocA{v5qM5dx5yhb$$gQ{OT-~um+P1?8!ReR&%&Q5Ye7I&XqQ{{qC%H z@2fGzCANYnp5_vsy}~!mNGO;QIa+qHOI8N`HGB85ApqJQaRvS&C>}f=wG5~bu0D7| z%YX0bQ&AIaU&+QslQpHRrq9YpCdfzuqP~AYot;Uq{fL|wxK^#;`(%3?=anIO48SI0iEE2Qd} zBb|pid{0%`9=d#atf>@mluRgxd8fmP(OkH{%{d{KTA93?uzI4$0zW1Id&Y6sPew34 zCg#_x2n9Xg<88Z$F~qt7S5sfn*RQ*rk_xRz=o-d6iyU8(rz?CsFY7~2<#q~v)+Vyd zT@NG9)3RPCSW$Xccpv?FS5qTXWLB@LLt2CI^P4dZt_2duZySpdKZNpBsM}r+_a#Gv9WL5BAywSa`qdLLcXOdyt>KZyakq)G&d4f3rx&`o^ zTCCF^PU1cSRWUBo+fUqM^Ewha(cxBYQJl{$D8Ld91^C9ZLQghL#%}Qe?(v`puN3*A zM9^GTQ)73Niz`fOQj|t1+u^Tx_nF{~pT=`KIwV6x{mE-(rcW~@mzuP% z?^Fs<{66-(Oqp=J2p2LfwGa|oi*()KJBFh!T@LeF!k{jWE48or;7j)?M?93!Tf4>m@;!H8hO1N+jiThKWvI;AEoaAm|nwZ8fRw?2a+$n1qKNoVk z2!~FUN~~|_D=EFtFK{Z)DYOXoo4@!6^nG4_;$)Zl1Vv9+n&pR{9{jigXLZRaYh?PX z5EK=gl2WYD(cw`&=}}F^B&h&#JA915;qXHPv+^%Kqax%^a!~g~M2wk!`nu$7WRD|@ zENgx7#enN%cbngypl=q9NlD3`pSP)g7XQrn1v(BJh1~{{u5?MMM!|xute%xs`VnW~ zQvxQSk}g#yORcBug=yTEFv;cp+6_Y$72D7S3*q>?+RQlt>ou1C{?!+q)5XABIAnc7 zgyQ4TWma7v>gsp53y5gNQ;t%LO%wO(XSToW?(t+rPO7gr>UDw^cj-LIv7;v>MAe^_ z%~r*2GI+C}7ev@7!L?px+%)0$WW|}GkrBt{Y%P8>^Fr+DOg|l(5l1B*wPW(!)UE6Aw{P&OHH9%{b^0-5@mQEE=hHj z8k85C_)dBwd{$|KY1U3ThSY+Hce>WJCnVYbq6ac=AdI`ag|=bMr|YKrRYqj;2M<~j zk$?^5Rwb9Q$#VuZ*#B;@sMY{^ofUc9wDwdP#ZN);^YfcC)OmS%{i&%$r!lu?ajk`g z#e7#dgqB6_X*93)^V1VjY(hd_`Ox(Qmv~kjtAV9?W_k?1q3uf=?A()-qu_j8C47n=*1=`-erku zAIxnFxw|%{pAXJVUU7pqBV#NG%L~<93k!_IIc}Y00{yY-;nl+Y?@!d0om5CpjSMws zJC1x@YFnf7)G@6-Iqc1ma6S3sLjcAPGDZ~>Q*y}5d)qk3_(Q*SiO?}R8=W(rnV2Y3 zQC_a>)gIg&qD34`O&zF5HF!cSw>F7vuK4J~%V=83XM-_a3wlOI*#m=Ak6qlxz5>!R zx@73-S%!1O13;~OeHH}KN@V1*dZgm^w}HXInjs%FvceYs5CdOARFv5m|1E8@QaU^g zij7V#vThE|e=$w5V6#L%KY-U~l1ZBP9^HWH*A+sy!7i znD&Tk(Q-U6A)|>NxjAtVCik2%qZ<5ECAzMtofL&c!ei_6K-)_92H z##B8}@hpVHIT%iM;_mU(`rtcI$Cl~EcC^jUu?^KSGY1JZ)9*aJT85f$=Lc*|@dwGd zHoQhXP)mB~dXeoy-Ge3XWUtw+&hGBdubFNy5{~}cE(0^id?NiaVx~C+_olQ0anz*T847+M)gYd`HsBjN1R4FIiblC2IPS>ZO#dm3FPmXtC@Pw<3k(1BRge z!St}(9MW0B+?GJFG1fMUBmT<% zi{HynZ+ILB1!~fky91CPw1K1nvf40y!Px5ZIOT{a4LJ%C2kmFTe8M!}~CHOWM zV7Qo$L>b%C8vr3;6_0a~&KOn?bJzO4;^OLU55TsSE4a2m9!Ema>DWoan~@@HpSAR$ zxP-^N8-|?Y0uM^gBKyVz6;c-)2-ag^f8L%UI<5ZDz36CYkA&hAP89&J?JEFUz#?uN z0m*PB^?K_Lr^d$@Xo?&r-qlIWt=*{BNkTfu*?J!nuNPYu_+P$(n>A%pu1GEx)zW`Y z&9wZtnSQj;bSae9HJeLPD<0zW84NeyE+t48ke=oSc+~Dd7Xn9(t>v?}&T&Y@VNt0- zJjv8-@Ud22S~V@}8Ljbdg}Ltwn3enGb#`?%!&B@)$caU!POVJw0p55#~3@jOkeM@q}s%I7mtb*$^<_Plx`(0zO4NaFIFeOv~0*3pSdg*Wyl*j#ZR7+%|V z{`#!Dre>Frop;>ek5f!TfjbR2yj!j&idtJlUTmeLaJzJurIk{F-B_X^ZCXux!2?|o-V$e0xfIkmUZMowx?wjrQw#Qyq>Zy3XTV|0% zJ7>}9Q3w5SrK{Cnk6NF2N82C!ciw?|udA1F6nt7@RK&MhLY~9@z$%y1RwygeW5kWK zW5}Xz^Ge*D&SI!g!133)Yo&K=?>IW)#R(*Or8sqMZ8CP1!ntKuTx4il*@s}VPp;C) z9xcxux~Dpe{{=~hOThqJdstgDXRH`jW%uO{S-l5kR9GFGeMI(KEed9#7 z`-9?!&qbe)LVSxj;hPP)J>v{_zYcPshzZ5@#7nkzcA-M2<4cg-($f4vs1Qh8Kyk1B zW=qrD2ZRRi9W6;*Tv7vvdsY8L%jY@oUX1VZa%RK@|Kr$rd}tBIH7 ziJ{cq-G;{Yvn^X6*g%{0N$-JAIOp7)j>1JWex?)wDP++8(Z2w-JlWHf*el_)?h>N& zR71$8iV$x*OCDVARpiZB3-$a51n+k{&Am*b!83LjYhO4;Lecz~GPU5q`HMo9$c$zW zkTRG1ZV_9diX$-{(bvYF*p)%efs{-fMV%C6XGd(%XzF?BMQQ&SKYSxPqCh&nEFmub zK~M~SY!Y-7;_BmX9(`B4PqMh-_Ue|Ni3zhu$E?YHE&WW@h`wGp@l`S$*O8ph4L}b3 zxOoE)rb|-`$;t{Sx9u9yhVrt4*Z-Yv0`%xK2b>tQ9DFsgv&(Dda7p=kn-KA(dk(T6 z=!Bc>sJv}3pt_NJn{%v{56WV>>n%;Y%b%K9gZ4UhyhytWE-5VcPTVCY$ zZex(((x6LbW~C42B&&rfC_<^I=vH5u#u)`xKd?yv>RGqU>EsXS4o*OX{;1V|A>VKt zR}1Q_F=9lk_ofv_>O=0<=d6Rwz1mSrJbTMSf5;2fyLd2Uef`*_Nh2is3M-xQOw_4TtgV2qj~_O)E>c6X`_nCjaCKRrB}MjLSKS zC+QxgWl!P)=DI8Ae5M}KD3KEnH5R3wlR8n+2b_OrueiP70@^QUq*}Iv&w-fj_AhPk z%jIfkT-|?3M!?0j z`}v$=aw<^((J65ffL0>2K9>0kSw9qWy<6_2s$}7zO35`XV;bf@h~gA?t>6;Q)s&5;Z@<+#ExA4nYL~8NrVuj49NAuWTsV(cb>Kifz^nA+roM&eO8vlL+J2->48fEuR@O)?adMq$r-beBOTO@71V@ zQ$bp;hj$h=Uvv7m7}S$`0&WqC3Izk7kDIzwYD!w1YH_&xEiFwu^s4jr4B=fHI7{W?C!Ep?z0lu&x~oU*zi06;723bYj4wl~}X= zMKd$SS>_s3(AtywlSMR#wv?G^wJ}$axDEuhJNv_BRFuZj@1$CWJ4F5>=;csSn{{+| z4GT$P?v=ea-L!;+8%9>7U)}xnX`jyQPC;y;MTII5;$QxwN-989S%8G^b>Qcyk?4%p zUT$%xrKJS|0yXSdN5|WwO_XL~(FUJbAUw*$)T?2FzSccBf06BxS4*j&X&esI1*dsQ80MCuQ2CwuOjav%j{#l#k&oH91K zG|d-S+zbpyTfCBr$kc?XTSjqlPg~yk%dH19sCRB8F-6Z% zwf8QYHp{TG$rC}zf-Ofz{qcD7{pfX5XG>%72Ub8Y<++u8N`gsz0gT&TNy+fUBX z*;%in_!=XV>i}`~W$fw%hb~8m^*u>Rb7MP}umgTgFXzY?;l zyW?EF(<^H%ef1x7z?3&xRLrJP*T6s!^j#`teIN-DKuiXW0`@W@XkdY;sIh$g`e5ot z%Ds3To4ljJSZ#AyykP%jk)Dyl%e{jjquVm8MkX?)3xNLQ-ilo+{%z>V*bGJ&Q1pcF z=|FEYubeziK3gfeb#gQr2HUHUlx6ss7h`J4GW6Rsf2fzfclKP8uLdg4GCQ#F3!stQml%&v z`ZODm5~QkEG2c@-_q+B{sc<7Sa%0FFTcsD|xf27B;rzS;$3;vgU85oZXlpNSdRJXSU~h8lU`VTg~<5z|&Ql?+-!U&Q#hP7{u5YA+^7 zalE#rQwPErd*vPbJ4cKzd7~qj!oK{W(*gafkr?$Y%VHTtMMdX?+1c>>w7}H}5wfe&y4@SX=Wudq5gWHHBM*bxYI?*ZpsE3q&$%h#51$cArc zZV(c!1mr(kGvfwxBF{5kUtTTK9;F?HjAFFDB#g8N;$L-bRN9zAEDI-yxKH%^4Hu4C z4$P;&cXn#zCBd9AF(;ef0V!^z*gU6py;@{GsvInb%*nwD@er!0>*3)+Wj=P4kbO8D$R?dA?w(Ox zcdVxsHnW|NX)P55($M1KYyZT=d57J;ykZSS9PUw{Ggw@TyN5+MBBn&ua{J~#A^ zbsMTD1tNnKYiain`_lr@k4ckY1!(Uh0S4uU7^|ZB*@2Y;SY;@<4Fgk^kE}Shp}w)PIV#27zyLx>N{K;~g88nBMXw?9S3WMk zWFdnE1{&-&^pw;x*J&Q|^9vXn85K)7eJQu!x+Y{Ym&e0N|W-m0+39O<|fWeb{y z7plEp1!w@RqN5l?+|2Zk`URM_!$6C>%DgnT3WHIZjg*}gY0PY(arK;W3ET_LcDN@muLQ74=TjYhI2@AfgHx9w z4e7_Ml%!J^jU#Q)aX38Q(9A4b$h>l8N92Z6$AIlbSxzBl>55C5*#0^#m!LDYLFx3k zSL~v_mVN)T6kxO`Pi?~1G;SX_B}%t1bq8&n0sa;0e3~-37dqtpfrb;g<+tV`<$mhZ zR6=8z1~DWQTMR{4;*+bnav`WdpwT}I)scTDW~XiJqFqcRbj73BwC($TZz;*=J)znx*p2{8J68{^M79Ba*WuctsK z`v?#xS+S|#+qWQNV)Dw0+bq8z*JzhY*;~Fyh~L&iZf9@Mh7u6olutK8T-$6DI#X~T z62!#elViEfykPJ^p(AgPnOO~(UQBXq)-9#;?Ri-kP`V6_N0-ZbZ>$vgVh3k=G;3?s zms`OhWM#cpo3Cp;G%D%k%K%czS-Ru`Hf4Y`K$wEP8WjT#vFfYxykoY(*eh@DiQsBk z#g%XK@|rV2ywW2Sh!?t3RUetw{VxgdUQig5ykUPUEqD&Z)FGvK(xCz~3Y z>fY!sq9R6dPwFh)cf6L;TObJ0)+#&e%{(hi@2110<*)iW!@0WLw_!rp@`{VK=@pJY z{YT6n*!11H>d_*r?8%Qt8UpEjTm+RLd@OrxLETjSI_jOg`M|(}wnvv&f=BJ-krC^q z9MI$k8Q@c^aN}Dez-apKkLy3-=-~wBwKJ5|cg_iW_^j~{HwXI}Km@IBlomQ90y_t5 z9|}d>?;Zq=anQ78217RihY{G(49#F>%3@eam>g^-D_-JgLHhSZ>?ihzwxx7Q4@^Nh z?)&pG6%Q1Hf`$gG3hxR~vZ6=vvM)?0?bpWjvr(sWie{x=AK|;r8xFGv?iXpL-@R4I zm7N!8u*l0fAxdIkU0SSCZjUk&LpZaGi>-R=H=|NcHg;}xoqHeN5OkCefkXB-;t{`J zwUZG#urfVECdS3(mk*VHqAFW}6c2FtVg{j2(F|uqc#+lIgE<@m&7yzS^;Kbs#9zDW z&kJN;@}pd_E-oO|eL4#kKz(?4n21+AWz?;@@Av#I*)<~8E2Tv~;YLixUzx>V2JtCa z#^zAk;mv|j-0@D&NLzRkxxeakX*9nOKy7bt&?43k3TmdbRugApDKIJ;ktdvJ`%ayB zYB4Jc-;(EfHaPV6dozzFFs_*Y{q}18h>K5A5mGtnl^GQsJva4mS1`_YWzK(R?_Zh7 z2SGdL5D!mJ39r8|k^=`W$}G$?NH{Nl6NUy%+*ecadK*T9X}E@%{O#Qki0@+bS#Ae@ zt--&#%?v~N`w1a=P4CZpI{{tek-Fl^?GnqV7P)Yqanqj80J9?+;xjGKe*>;gM0YwUINDH^1c7;Gz(5ub!|% zAe=s2XV6c~&I3JZe7jd@`#g>Y**q8CNTF3ZH>) zAlEI$_7H1w3QZ1PAFsiE=>qwdEgG7Hk&_H(Xnr?!<2*2qI=UJzXT20btzP5sZy7pU z0bKM_9F)|6@%@sV^3zOjkYx)=pJ@(V3B{sR#9lwsw3w^u_g zHnxK20fvn`TR{{0W%cK331z5*jL0Y8D8z&cu-MPFb@L0FF_k{EvMPqV;k81QtNP=B z%%hpBzgrp)3${N-W@7T z*IaH2dw6>MiQ^MMJbeVdCiTemaU&(LFT4j*jLeRBvfJ_3v*p=KY~s0(mVSDNtT)Ut z)y%~!j*i=} z>r~x2u(rJ&b6YKS;miIb2$7M9WLc#+J|N^^kI&DXpXF9mJZ?$ey7Z9}*zJ@XDTOTe zZU44w4G#G){bZWk1XbSrfb5S9XTN{j`NGb?shbT%-QDEQY;(FMwwh3-5iHDEfvT{*H0gNa+_U!x%hR3I3kHK zgu#{%rO4xZr6ZSTS*uy?IT03X{2>*c_G0=?6*RaxyF0Lf&YfG0#u#|J{ywlm=xD|j zJ^h~{{QlB;tGHO$tLm0I3k!=o8V(N22$Sn4hh#uWI_Dg4n_Iv-#_`Zr?^z`uaHyH* zq{5;`ip;r6C#UKIxbsB@Yy~s@gI`(wl?zaSFRkOBv3O0i$fnDZ}QFfuGq9Vpl;;co|bB< z{uZKvW zE>(b0zp@hfxpctN?s)j6&w7*zBG`{yLrd_#ivWext|VOet_>Mfhyo>70f;3>s|>(7bi-6eE5A;v+geRtHI-@N2KWhNi}h9O6>d9lo571+~ZIM-7H z&&d7(?f>5kps8tr1|HN3>%qGqJ0z>SfhNH{stBc@=rtp4Bcs0$BqS;(N^be=*Z@mI zX{m=i;g6Uis6P|8O75fIw!D zlkA~4wz=3$zvQr1ZyT_=Cx5qhb{-nA?RpXi zI{UQDR}|i;e=ueGi2$-1J^KApSgR(Gj!)GpDhh4-l9UH9!6)x}Uk(iZ0hYgquyVgG zJmUU#Sz(*psX^S)VrXd63$HccvBCK*+t;d_yPk z;F%|+^|zshCMLgt!vI!7Nilu%EPLPmc6LVOzsYJY`{t1FtB@b%?HdFqUxz%apQJA* zJG-8`@|8SaiaW&Y*qMBeci(+X%_}Ln_ZssJEDAU$`HYa_OQymiB05STG@m;jzM2V; z3|0f8{)Ho0{oY6|(4usfk(e-cpg8jI@O%_3^F}`euCFA^ORR!jdRkiHH-dhf(97*U zZHZph0La@pv`yO5bxljuebx{3SC&hb(m{847;<_dvNlm6dw1z}SLsk+U)pfNW$)AX z!F@Hevq6zXLS{L&8}&M#*wg1(0iCclCwfpAlo~0?We|W8r8ozB=b^b^xE>k_4a-LZ z6106p0z7x3#63C%z9>yPQtVa5v_0irjQN!ps%7NjvV0%oy|+gsHAe!r@)GpYy@+(l zwx-*DiRDQ9=$)`jD(Ab~K1yuCTPUGTWl_ShuP1tu?`YJ%DQ&qN7+KWRC1u+VRB&UO#qT=MxyL)44&mBlH9F26ZgD z;Z@Fh@FqR%q&0r%dh5nb@ZFK13sPx((RUp|C(;^aB5YM^p>upFVpic8&d<-kum=OF zV1Zy>T1ZlacSV8rfA5mMp?CO0*YS*VsX@imTrlM_i2qs|j60#Ip{C~UEUDw~-@b&L zR957acPra&EeNe`MuCc;BW>X1?OLJ_v_?V$u#wk)K)4TF@dYF&N0$-cx32(%(yP~X zeH3_Tp16qc@Tl;n`n`iq@N|9!QV~fKb=60DEI*%L{}Rt>OV+Ylx6ME*x0?)UXfGd_D;(72CPUb77-G1er>W^AAIp93q_h!ge}uY=H=(( z-oUg5X##rk@`@IE4sX2#OqAF*Y|IZL(0LrR8`9fT{PaFLg&6=zATBO0D7BrmwEk!u zxda8`U}@>4BGWRXUU@}xRKTWn?D~q7pBRl2=tDd=ypDPgAP2!Z!Q?6U1`JVywC{^D z4hjnT>yavWU{G**6c>f)9kM*xSesle4VupHjiJ?lYAy0KD@>z#1srj012hX6miuubc7pF^@+jU{ilR;`QU{N?e(Ik# zHhwv(_avm8bSRSSWV~@76bLJ$hGs_j8Goo2=I8G*)9}AAEiG(3{+J{r>mcwzwg3?S z@UIao{}Oz67ZWOtppg!VGVaGL$qGR)wbrnX5aR5T_hF|Y2}3_+@f%Ob>N4KtJ>AFR zE8%lo`K6^VFPner-fR^K)IMF13%pv*wz78)vG{BU*IDKI-RdCVS zWiA~UU?#U>-8Jh$XI>VN=$42H*cXh2EP zc=y)yn>#C>ss5H5s9+;g6iqG6w+#H%odiG1bEqvq}YuFZw#+m7<^A)@%Hg4sHnGuLInt8 zCNG#n`1w2coUn(~ofkt#%e#FJrVv4Dz8e*}72XTh_Y?znzUCM3gBwUlDoX8Ja3k+0s#y#x?C7ON>K9?fR=M zn2P}(py4>HfIUXiN-9aso3JZbdotZua{K$IOGXI;`;Q^V$80t5oot1J>1GKgfmPhP zkuhPL$qsMJ9P%aX^4?zR>6Zu&`BLj63=JdW0x=m&)FSDe&O-8QbL?7O!%k-vNm*7w zd$t@helN6?zLt0fE!d%c&GhS%LQnChx`H>%^+n#Ur&xhk7ay4bGMSe42%HlpAh*I* zg3gc%&a(>@LC>=9q|CFjU*2CIFT$SIV@FPjBB#EX`q7$2s>*JH+0g&T-d8?U6*c`L z3MdGubW2EwbVw*64bmM-cXx>j2uOEJ9^lYOw{$m$Za8#z-i6Qm+&|!cxS!thMMciu zYp*@CX684)nei+(5}5|Xl%8Jc(@Sna$iC;bR`Lo0!okZsbIQW}GR3CrcTI)gQ7~v< zJhhw-*!&90ts3PPuC5ox8vqMEgJMp;RsB*j-lk{s{ zV!97EPt1AvVTXxOX0g{PgO>Y_`033taI{_od5qu%mBs?E&uyssS6hHX6e>)2$K0x(I{Px;H|7d$Q&*gHcvuYC^c z_&hIu3#SjorqltB6fOQxY7X`7Uzm^X!u9F?PHx5!0)oEN6XGu0)u9i!E(`ZMDS}SI zpm1Q*u8*h%6nq&OZ7#MvzS=9kd+fE=D-`9ulVBjODlpJ49S8I31N0HE`*R;Pmd1l* zdIlG|7n9ZSa^lvv*iWA*t7$<8!B*UE6?+0?w;Vg);SJSS+0%o%j zcHe)W?%R^iWKO^pY?gRsa({w(`miNewVFtXzUXzi+817?rn#`e+nvFUbDw>(>~pV! zMPC&Y+gkhG#8=%F@>sW29wQdE{2K=?->+JIZoE?X4}ay;Gr$+3k{tFxsvY3v;OwCZ z70SH7s%nHZ-AgZI!9mJhue&^>BiN3_DoLw((Yy{_Xpqp zI5Hjs9g2$d%4VP*%hogMNV*O4a!x-!q2M# zVnlXR)s#urcxiCErgv9(b#=|b#uuCF`$Yf`hPKNotJE&$2davOS-e*)u1>a#jS66& z^or+X(Q7@5ou+PG93AtTd_IMspKc4@IcqTCdyiEX8m%Yvd(LZl-Q;v2-!2K=H45^0 z$O*X~cM=lgk4;{Pc39Gd87v7TMnz>#Z_xu|qfNqxI+Hd<3$o$ZfL_8++MD7}#fS&7A-3TTFmFV|G@S z`ohJPn$uC+vs-x64MWAB3JXJ5WlBm)d$Raf&V^)&Wc9ha7iY!w^~y%sMtsgYCEk0z zdWv3NbG0*SZSSD!O=jp<79Cf`+VJB+HOI>#a+@eN zcj_iWHxerbc?;K5QySgJ>plYe@~w1qoTrC3NJ*dZQB6#sB_;0x=a$cF>qq0kB{$HY z^3}t}8+N_1rh7~u`cBa~TmQ3A2!@ZWAkBTpv-N%dKK+vffPZ?rpE0n| zh`-5xxh@ zDY8Q~9vwKU!Yf!YE~OH=>-MK zO*j2b5SIEwUY$mFSy+*-nuj=KeSJMzf;vWmI$CDhUPmp~EXN;HJ+`d89Ke~7{2nP1 zzG^)duVpcnLbir=h@OvZ`EYIqgAATl*XrZS0Z#v9d;uv}_1Ebi!`( z86RKW*bEGEcjtS8{cYG(xhRV8@0WL>h_S#&H$q|rb`<(2OTD6kGe56L)ygU)k&uWh z+p0UOu%)HNh-@L~T!fz#c5u*DS^1fhk}{J)_mkAS%#^T96F&{<%HE7cM68+jw!|zl|3g|J$Cz^S_N3 zFaFzj@j&2zFZ6Fm|96l6r|;(f$`Q!zfzSWDij;=%4+3z=W+vj0hkr0?rP^M{r7j7m zG6KRoDg8R4gFkjUYN0DVb|i0>pW$Gy+H4+@bVjUHg9sZ{9ULAzOag+WDONKzWD~az{1GKD0CwU~a(NT6nlC+`Kw&>-+AG#iESV`WVFsHTZ zBI23hi;Eu}9WakzyGRd z@UXtc9#br)AB3f@+8|0MBuV;Yv;Vc$;~J#gLFse2uJDzB>@q9y&=pf>XH|oh zC_)kvH4~RGcXwnC9*%008nOl3CN-i#G6cJM=wV;K9-5fePkK!o-aXZ$tkYB$nEG@Oa zi?o7YQZrF21~kOtcieb$B`aSXDk@w|eh@`+a0%u~t69JJ*KzjiQy#81;U)1-I}UcN z;fuDc4jSt5`T6XkJzW5r*lZo@aH}|uRFcTb$eG!Hk-8NT+1lDsagk5UF`-Cu_bgye zkm>RIom)UI(xU&~|8s%awJRg@CzP$j(XcOQxVe)|SCp_2Cu(eO7>LS08Lq=o)8=1&+-MPDN zL4D%5v8am#Qy0o?EYfUm7fVb{jNNu)doBWE@Uz{S1`SydkRTxFk%OCg-F`PW+x?{G zlIHGy%_$&IZc5Ty1{ywe9E69A%uf}1oKKI09osplgxoN?8ga`^3wEN+XiwsYQ!!~=y zwXPOhkL`6>3@(LBUOZK&XJLpkBHY^9n;SBisVsDWGjCn>zY8V>wOO%7fO(b`bYw3p zKWJ*`XekmFRzZZ1ugITDAlPnTXGSvm#Aho;KDV+~F`}#BFYnZJU0no$^25#aN!7%| z%-oHsDM1S$CE&4vr|*%LR@T(bxb;ZnzzeZ*ZH|zj=H%r}DYYb5rt2D1(=aPFH_sm% zQ!gf}N_@ss83A{L7XLxMKI5|L^8WV#k|NEA@r=FEW`xHA)QI%V^q<7sYVx5jARzyE zdRhTI>%58zhfNax4yWSJS)p~d(xsxkDByX&z#fmz5>qQ0_p_NUsbo=FB|~ z%`24?6Z0e3E^?|2O=&)^KVj;BYuFWTX1#apdN7$9=RWt`VCkO7V<%*5d0e`M=F%>e?_RGs+^mb zH&De%pv}$MU$jyi)cGo?6RT`D%d#^91QJgk_n`V?4|K;GX_gfzheV(YRIbpI%F{;3 z&_+n~$nl~2e?vuP^7}>(e5>rRI>kw_;p}*fScakohhp1=k%+$8kgy688iPdj(YLs_ z^#X%YRx<=7-yd8u&y%nHcG|o}<;!+)1~Z|=oeFFT%8U`8C{m+WCQ+Xt_MrMQp#ggbe+wgzcB&JOSx{jdixpnnR~--wCPv#==eBsZ*zH3B}j)pnn!cc9_Nx2216 z#{&IgnFPIzt;$wbF@Q#aTeu+fHmgg9%1bC3XCyVp|JPe8GtfA9C)QvP<&}SWP#IfH z3{EjTyZbF0U(Vqn=$wi+sO0Qi30`|~b%@oB6txvPMu@4Ur*M>#QeskwIWwUWn;8QG z_9vd?%-PuzR5Vm&1%>>P$S(j}5vF>OGj$fkQ1lX=rH-iA5fZuOQ28orZVP=|^~c{^ z6H`*OoV{A#Dr;z@Z}p&(2ztH;0#CyIQ_J+xut zeqqJA4VerifVaJ;$+{>(74>kk>+S!Z=Bow%d~tX8rV)&VnO@nzAcc@<>yL}ecTgCG z{d6el!RR($0mD!QslItn5izv8j+^q%MUu&igdi*-hgUDLC49BpX&=^skB>jn+#Fu( ze#IehoGd5kAd0jdZ#{0O9U?EYF|tNf>$z6@v6GsORF9|VGd@AZ`<$E{PEO9kcm3y% z7YE(~_dCx>W}CPvSF63?skL?26Jw$3jE$?cD_Ba?4Q*F0iCP@&>`A4T&dywvi%*fi zg9t&v=l+xX)QPfan(N_Mt!^gDjQ!yerx?Y#2xKHO(g-F`6=`m^Jv3F02LM8=;T5zB z8B$Lj8fplKos(3R_1t(jmiXy18bn!1XLDu=kna!>DpAy4OyU9?@vgrR073J^2VJ+D z7LR?uB0iD^I0q{7V>Wc9A~-#+G%X*V3R^-nyU?tGqocNv`>~E&5j0NlF!`Ish9tld z{lz~z-Q5^@x!y$r(VTN~NaBF(k~v zAe~Iwvjg~anQ(evx7~WcUw%gXr(LeOT>j)tS}c?aGK`vMi`eav(Kx7^UFo-k`1n$B zck#)Iiqaksxip^29%%XG?z?lgzv!bJP6vzgM$>U)a~@Xi+bQSca}GWeqyX!C{=`rt zR#+o1ktlS99yi#iz6b<7aG}Nwb;cq!JvW6@C(baHcMpd_^Qhm~$65o&`~YU&4zMxy zCjzFof>R|wzu*%H&q3e~xH-V%0Km@DlEUf9iG`~n2Z?>#7rTbzD2M#~>FmeXX9ey% z5{9b#KCZg(LA~zyb}DStyo`F~n3y$s#x6Ag($iuqGvdN_c9N;7smEbLx!rjLymIE% zlK0Kw^ZduDs7J@jdU}+S`NhzQrPgcqvlN-hS||8kM&;PJ3d@$tXZ#!~DbfaAe<~86 z^^*DPMauS-bCDRRn$1>QY3nxq>LyQUkxo-eX+dtTem88&PP?MgX-nK>zJEjD)l>DJ z!9g_@qZ%oxpqRL%On@x$hGuSV{shhm=i9enEoQuOTPE(i9ac4|P?q~Xxbjte@q?rB z6@u~zrBWM&vsyMy#pz4KCv2^6$0k1&$LYE2dYp67zhkf%c8*B+X+7HrIiG(ANsqXm z8R!!|8K{H*agmQIz-c^mAr|Mh=G^{ET(&&$UoL=OKx@cv43b$W;B}eqry_%hm3g|- zM_|y=xjNhB|j%e5pc}9mLBKl(#D;?R~T03PoL>O$CX9924bzE zrfAHbX>DFT~DR=YZYE3YOdc8RKGOh`-$+^qE%iLEH-dB0!V z4A+9l{OV9^AGmNiW)I&1n1T$9(XFJ?e}#t-pIB8|I)GWXrYJoq45 z$_&g&o~577O@RN$lc;mS9{h>Vi8&ZMGnw3i*C30f!dPeJG|sG|wyQ|XkkxeEq6HFO zo1J^JX2XjIx#s!Kl94^VxUk$3Mg{|(U(t!akcvf5VwR*o!ZaUSRb~Zk%M8_4?^gldqs;(zz?Ogr1n%l?;=5sm*hG@H) z^19FMwksbX@+3;1Kd%pkW?4bE)6sTN+)^QEc*W+1j*JFwW|9$t*9mH>&$&q*k1k&B zPL0NwG6Fj}4&_#xwaz1uQQfjSs>UFjhTPorrQ!k{wf`xj>Z>psr|W@+Uq&%?^kbj& z>P+V%hV8sO4>dd3df86oYv>s03^JM%AkdXQK<1z!v@ zzTcDqN>!Nwm>ZXHxgxh~08EXKt1@W{3JO;2a5*?A%vC42m0IHa%cb(qgtW`g@3^hncy4Ju3d5vS`LL&|1cG}+v@9=5O99zWw7Qi`_ldUi!a=*)}E%uN5lyJQ6T>YE8agHX{XDp(#?C{KonVOjHuHmj^`C^D?3h+*(} z1c|r7$p7F;Z2T*(v-di`0_JLFYE3m{Wn-kIVM5a4A3mh&dR>b3_lv#Bwoja$O?KKh zDd*&x$jiu3zJ1)fX$0Qp_;&Gq^xvuA;A-ahq~QFTnl}a-N+KyevAIlaz|CrG^!;Vz zA3^`|%2r{#B6n6L;-zid$}{xm&;NrXspEi`nXAq46e4a!OLG(!d1!iO!;% zZpTR(CbOR0z6jn@r`Ye0oAW9;t}jo!;CM=sj#LS+_)|6>aD5`)*3WmY0_3A_1IO z`8UJc`K0SZ!|`4KL3yKggR`_pBB3F(!s?ilj!uL3I|B`%9cTMmwFo<_kIPdTL5~*` zq1xQ#3`ByC>E0Vq!koSoA=Yb>LuoPp(8u&E1n*I&^Nj!W^LHQE%zzbAe*Cz#7B-!8 zcSnvM_KPV|%88OEG_z)^Mywa@=r~UOx?8aZs_o>&SqmG9)t&IHQUS3KDk`qf{pKdY zTpcX6(FT8{G&2AjBOq?}er&8S!>Dq1<8P-L*Y8t0hH=%0){9?}o)BvpzVoN$AN#gz z{a%yJ3|bD=!eg2HWZ?k;wrn(PE-j@EWa-%q8Bu-t2Z4iG?A9|XNS%6>?}k0ZJ}&2>43ep=@>zs{pc)6|TWMQnOR^l)l!-3Q?6Zy=b zU{tkPo4Z$jnv{UPF)-lWhx>m$@|W(mS;npSuBPs4y=5m`y=m&_jgj4_{gsNKW2vbI zz%**Z>h!7Epm})-Crqz2+;YNp2ZTTla#N|3Z7bpA48zpaR1}0Kwzld54UacFL01pp z5Ub7ENJ<`N5YlC48bBKXjQ}7+!%ulCyPWkF_kNHPuV9S^Yre!VVIdGKXgjX|m7q3} zk!NQ|5)fM%>V)z}F~4iGT^e?}B#t*)Wx>5y;31^7yRr0`p!FkYUa|(|=EMrz_?rzS zX!rz56kv@FA<(^Yd$k&S(nMh{++b)!rbp}a!_W!@ix2-^A^DrAZn0kBmc`lt7a3W3 zC=}|VGegY#M=v&p*P>@=DEI0LIdLBI8x zz*9dJ;K`b`bse_#@7hT=Hm0zc7w3-{0#&}89Bj24r7?)#dwNiTwCnx3t2vHiL$xUC zaMG#x<4g-2Yep7EMs>TofL0v3@$X-tDg53XnM|#e2H?y|W%UDrjD$GGzTUB57>Pjj z#wB1gfnLRoQ+cZ`n%}vD_v#tm zOR*rI51m2U(vGEo%T={hP_U}!yh=>n*yX3W@a?KZ&?7ZHGV%-3_ea2k5X-bvQBlp! zGF-bA2nD+{Qn#O`RR)s*9wWd!%lEue1y6rj&ox3ln05&&xCI*>b%tGqug|&iG74ne zE^ELUFA1R75mgS{(YA{$fQC9%W$QeCd~P!J4EZrwPq-Dk%tP$dy!Di)BpU=dnRVx9 z`IpufAG?MphO_Tl$RC`1UF%Tc^l%{8IXRSsBq^T^xl|yO_|J2TDnF{ktbu|=X#yv1 zz)*ViOiM$fMXXcDR)4`d1uTtOr=|#;b&eqc^Nk-uLYm%@F=K;Taq&mJiohuVu=ryU zN`Ust?1PF2Kj9A`t1HjaXzM->_tL*7)v5Os$Q;_**r@h)T-Q-i*^?4lNa#uyyq%U# zBD7UjQ_Ih(Wy_@L`6dFQq}M_&bJx$25wH3n-cqHm!gF(8WmfNQ%WK@qH3|z!@{0=< z6zqKM|MHf}hyc(&Vu}|X1H;0Ur#wdr*c%bA5M7_$Hy}5xz3^bm5vUMDttzzTztdb^ z_#q)(?QFC3MvaY(2ntbA;;@NkvI01rsRpoaQJ*l5PpEE>r@1ScJ@NYk(YvI%K{!14#27EOLUWLOqhkU(&vP-c*b7{58)YpwcxyneYKuD0_(8}lYy(=c(vyt7r zG5CV3EZ`{S)j{L|CKl8;K>t#{$4*BhHt!Q{?#@n05E>c)N-}Gw&4Ct4?XN_oxvEZS zOdUB2{n0S5jLiPgnm0`Vmaw(ArvwrrAH_QXf=~A{j3J_Atvkvr9@Pjb1_w0{5Dkrt z+8$bprCAHkW!g>dMY*mVSY&Nbzlc~uJKvB^GNX|7lJh1xUw2O zvF`mH83zc=9srAeQ4VgrnC)s*zi)s+s-mlq59)KVpE6<)0wV^pUJ;FY06os{(FvZ= zg0`f8Mn}&!*TcqMh346>1D3waLnI!=aL4;?Ak?R4ng^{OctFmBEK;HqGnyA(sBR2)$E5 zwjv`+F6lNJCnFEvEIF+H_a_3v|0dD@H;Mir5&xfgCWte04|hd4KpT{v)0RAVS%iX=5mK0TXH6>^kP?y)qKZwQKIbbMJ);GrV;w^SrR)PMOF=1YkkyH+y z#_>J%%>fM}hS1TklQfgT(Z35@m#dT2P1&{OR+%y!Vu5(oqg0j$@;i+CkJ=rrJSMsT8bc%+&pF(A%*T|DJ*H zUmGy&dO5Pb(jg%#I)6e9$eLxOW6|ota6?5w_Y+Z+i&cKXr5qonIasz{Rjzp5JRsh@ zgmUcb4<|T(jK+Nm=QEg6G#PW04`s3ha8q+=1c7Z90VX3`{pF|DsL%75JR|qq7q3sL z?6v7OvxGxTwFyI96XRoY;EGEfi$6GcILS=gvekuy-ZVI3hF<&rWPq{5J>(2%*w#JF z;?Y2_Mmp^9GDJnyAtfFTGWQ=@K@@pqT}qNg>EDR+Xw~R@xuerkrkPE%@4`|7zZyV} zp4XrWuv1(DR{7ILI4~g~xSIE$kGftQho3J}taulPT>eGv@GL2{l|~|A zo}2I*aLWD>wUy4w%78BGPM8+s56K$%EH5%TGFHGr5=Egv_v(0ED2l~s>5+)%;`|bS zcf|UA`E1o+I^HQo?@Guw%e>d(^xbiZJ^U&-MBp#{PVUYZAOFhD z<*a8l+R0OnD#Cw4CLWntswV!Fh$>J0(!Fa<=Sn6CR&AKfg5JMbEhP<#c+7dJBy2hw#`!N2Kf90ZOs3cS zefwQGCP050=COqzVSS#17&=TKj}@_N{pN<6MT?$60*UB4U-7fJN@=?Pk+M$Mr`Md% zk+s6U8YH=;k`K!I5fqv9I4F^$G(5x)&Tm_#@fbK|<^4AYV0*UD_?!_2qrOO*aWOkR z_t;7bFLII~p-lD9$r?6upVv3QUeEG}jz#114byT)ONOlZOC2e)|9RnP$pk#hvKQvA zMRrY{9gm$j!KD{5^R9^Vez$VWoivr6^2-O&WN+qYKm z5#}>S<_Bd*g^b+Mi_|eZkHN_@Heh|mSNR0Ea&=_pS>Hk&y&gjKi|NP@0WCu@g|Diz zr{W!Lnw7SHpC|-F8d>vf{u*$7u*^F`%*$yT>i#GaV3F;ml1EB1MJ|Vu6axGo!{=gl zjBd|GfIhlX5TLcH$(VRw1a)$hCv`bznJStSMQbASH~7Gm&L#50X-iUij?l+rBnS6d z9dlLrlr3m&8b6|Np*-p|KIRajsU(Lr>hVAL3-XnIq$IIxJ#PgPkWYEJG&#u8Wh7j# z1yULqIL|${NV7YUonv*x{0V!qV4?VY>Q=dV!adJeEsY*XFc~NomeqTnj}*X#_*H(L zl;DW7|D}eFCxB;-tb3{PunEdvGD~R7sjJb zYC9c~A_GN}2ef9B&RDiL!fZHc#jfp@;zG3}6gC=GeGX>?8jy_@ z;(w+}*z=4jnew0-uw~-mlBpIwcCv1vlAuZv;p-n|8;oucp*s{rZA&`XZ8{f2!uBNL zn*0^$ti(un%1RU@HXJ9@pG7)}p5y$;{#r3tcq9bt!hIvh^%I_8Ptxs#i2V}8U(FO? z7EMyBNjIAgL2Pe$3}*?6*%#H?>CMgl?NsYCiOkV@>x1G(J2QJ`mFF(}6e;Fx$~t}F zF(L1pOCKq6a>?*=H9QPxE8!nJrW%Z1Az(96q_^jj50o`B!aQ;YNpbhjY3YC8vKJHpFq+RzHy`eAfwEdtKLb{EaKR4? zH_(a%6HusHZL8`eXTC5q>+FT!9V5xdz2g^)-+3t?;W{!}!Axfc*&3{OZ2n}&Oy24K z!nUAOc+KM7>$rz8p9n-<4(PnP?;{yT-LG;YQi7%9rUh=B;MokjsTtT8`iZ!x^A@6e-0qNaU zoSompeKZ5D864G1mPR5fCIx1ze2(cT0J0i=d7%x9wu0n*e+b{Xr0G{nR%>%ku(8X+ z-ehmM#75S+?#kgIjO&9*Q7$AB#NIyA@z(RDCSwsQ3)P`T!^7Yu0^Xn z>z>sg{*BGG40Bu>{+b#eZa__cyIKih=jIRTw{)@in!;i%O;+JbKM;lb*MF<0vQF$B zR?Q$A*dn<4QR*UV7+CYQr%c%B z$Wxx!@cg=$uV=wGnnM@e{IzFJyaX^hV>FSoo3196wqJj5_B|y8lX$Dt_;vR5`PQ;8 zuAkRD8xa$G_AuJLY>Z@rzeQ@1^Gj=luzahoQ9ZqrF)rGg8+xOI{V6*v z*ZG?vY_j{|hucK;I-u8tuJsN=OS2q{+DARA1o}Iw=^vAJ#tbN2Z3#aJp03Iz*&QFd zH3f`Q$&5!t>EG(I{qrxNq@Au*oE&R%EKA5G3htf!w;Mr_0bm%r2bR*xm#H0el=iSb z#Snro$O{h2j;|cF7SKzI*G}$j-oE>1r_m1#YbJI#=d_A40w2EizB%15ozDM1!;LR~ zz@yUXy;epNEK-s26A>+|6LL=mnB=RjX?f^h+IB^?3_Tcxi%>YhyGA_w*;v7P_$Fa_ z4nk6n!Q?2AiNwU2y;#d8h~T2c`21U{-B}?ZI|$8+{4OTv%Nv9d-uAttRCv10Id<9x zf)dfhE}%51!Fg}Wmci9uyEE(jfeGl@opjG%hb0nP&Ez$-lUI{APtL5j!02T0c8*tOn2f@qAP_3)StvTmYS519-}_ zs_>N&j|c4+1gc9SGLM1E8!4B6_Hlbr$uk?tvUG_~et*ub*1~QlxqJ~f^I<(ny>`TJ zqWYdav-DGD2~9bks`Sz3gDtZ`} z2yn?^UOmv%M@d%R@j+FO`DA#C9P{Qz1mkj*_16w^YP|(wJD0r}`8Zc~AO;fq`0t}KK zhpv0ebJuP*JhB9{qU6b{+^4n7y4-s@EcuP()sE-RpSw{kf@`r?7eOZ`aM3X_x9?5% z1(GtmCcFmqVp*mlxnKWy|Cjr3P{*h%Y>p#je|KQYWq)OTfTo&d9ZjZ9bT&R-?L4)7 zW|3&rm9ieimnvLjen!#F#$Gp?HcwfYe)!eXF1;r&4e1=#@?#jvk1mTlCoQ?a94k{x z^P8c$$nXos7hYwUiJ;IP?FZyUT~Hm5ib~L9R+l#ao?p7d+Zi!nfkk4T=+(c^%Fl&; zOljt=Q+J_HHsJSI5Os;k_H?R7pT3Dqr6WIVp|kOY?UL2v?UP9^hX3Esh3JdTUhZ?> z0lf(dMcAdU95Xrj%PbhZ;t1{Xb)UVbk1k>WPwCcoX^H=SesOx=2OKURTr8@T{~V`V zTL=%2e_C((YV)@5ud*PNDl>!D%|^zN7z4#QfTU><Dk_b z@w=bA{JYPkA;)YwwrrbJr_G`E+C-@}sTu2W`*SbBmV~ zjz2jZpB=Oy8dA-p(D4ySmOkUCVOK}REhAJn^v_=t_2?TxPi;!(ybuXq?4(tGNtbXBNNRrB!dNZ(XB#LUV9 zjW88-1&w%u5y%Sf=uf(F-jlISwVExFe8q!&a3i*Ow};1Lcyk%%^D+IRK$ieWn!Bh1 zf&#+Q?N9WKrz%$Zq$nb46z@Hwzslg2n0N{veh)c4N|1^j+&M21IPGd*p&xuJUQe`u zn;RcZh0?tI;_{xEcSwthJ9Joa9GMx@RGF-nW!tr)_JK`F#}HZc#98c}NgUIW>>q@N%Q$Sk}G6>&(pZU_e}4tlWqr{et!Kdpfsh z7CXMXk4w@n$?l)Zijna-lAFFY$Gbh}H&n>SwYBt?cR@g4X-^bXb#d7QU*rZ+kdq(U z9p)qkz4j6MSkn8Lb2qY@RaeyF$%I$BC=}IIPCdoD2&c|2q@n071)2l4rN*QUQQ)yz~kLGiyYBBG|HBpurGE&Y85(*OP_{1Z(dbp<`V&@Z7dAE}davJ%RMOg$_ zcg?HZCm2;TvSwyc02p?M!K zymw)6lTY^3g20jYtNo6@W>OeM!@m`fpL^H!%FEV;^~9c&m8UEIm{u;cV=G<$bBk`F z^4A3G3+u!mBC)M^Ml!dqDL3Nna7tU5*O;LlIURRHqA&Z59ojl;1+04*dM-QV@7uoz zU?O~?kLgPgh$F+e^;LnYhKYo|p`xO?dMWnOtC+O7^ZVKz^EER1pkbEr5O|aecNom|1-cbCmfhNlQi@G(L5C->{OQ7dz z!mo{orG(!QHO0Y-^&BoXSY~THXf8B#bxBdq$qeVFoydv|zy_qDb0x(b!v{Ze5Gc@M zugVP?Y{wQpDDA`N`%}h)hP2ghvH}zs=ND;k2P$@jfst9;a;sKPoTgc4{W9slk!=SY zn_`~ck7_Jey@Z?v$$SmIx!T;2g8nVMhuhCkZLpDrz?83*>p73fgK=cTvO)b0DG3Qr zTg{ni9`_rNi$$u@9$0P8@2HRz}MVjil%uvm;t3CO0%`SR_7##3TnlylAeSv2B2&C}#pi8|wKy;oFbXXr@g{-f8- z%%P{-+#tF32(*q_0F9_xLpyg{wvU&Oufn^HXjf6ArI0}8sg;x==!-=6`^$G(@Uu(L zmP+iPVOf!YR^QI;4=2G-*&byJ2P$R@vm%@QO<|_7jw33D1y>b^q1F)l3HvJMi9*Nk z_8h>d-()!&(qfdRg|H30>khq8scdS{n`ODuzYbS74YRI4XlC~#_6O+rD=hY#3M5;e zl#6;@(0%CJ>))SfKS9U$9a9##nd8tPy@9Z8^k0Rv6mi|*4A{9UTQ7?jlH8uXe){*F zpBK6%Wh86)%&sf{?Rmy)tWRAywAiT1^y9e=<@LHO-(J^`HmsdqzQDh#!?c9)=6V*- z>~<{K{vI~{mKM_Kd3b&6dwAr2;N}Hp`C}eKWVvyJWj~_9T^6Fby-|wMcq+<|o>~(9pX%P-kw5_vBHHaHw$6d+2zEGQE%5 zHRYl4-LvrpMi7@3xhrX9jCjY$mQ^mQkI=EhvOGql9;$2|EtS8Ib zG%QFgT-|6m*OL`nUTN9I+2r!6N4v{hVt>gaI1p@BK2?3cNE#4!BP?x!hEU2{TANc{fP-RvbUmDLeHUS%%nF((H((<_~bHemzXdpGYuDG=|X{{^C;h~6!KmIRd zzIX4w8(BNp+YjY(itN3^#trtXJmb0%6hie&rkk3|Rk7h-7F7^^@#ML`Zh02_z>c z_uU+I0DlXSpvC{yy4m)HYj}F%BUt&z%$HR&A%ozZ`Z!|K(dDfg!e0~zlMSCl4H(@ZCQye^BbPM2Ac{Wx5mD=)X_sza1JLe)L}15nHJ&r+!?&LY*<5 z$rvyZ89GJ%a<=;L^k=mcS)1+lRw)y%iCqNEEp&j&w~w(jl+LS z|E%5z2Jh*&ubPF%M_JvuS49=$=Gl2Q1PQMfk3I5o88LY)#TQyfGay;nIY@#p$eEZz zKvCDv0fARQ$}+ou4(tD}EW(YN`qgjTzH6VU2>lrvO3um}k(|fd*4ddEEdTy4RO3${ zE;|=AWa3QK{j`B68DrI+H}F{?Fa(i_iNcCsMsjB=Gz-D{K{MXX;dA{`^^%$0TxF>* z83sSY!Z7(goN9j-Ye+Qp8#Ah+RXF*YTqj*pMfC#N23zG17}O(5qk+M}L^0unbl{w< zt$WN#;U!4s9N^VA2wpU+`+TQI$vi3-tU7JIoKjFqe?0l_oVj6qFq0mW)6UzOStWC) ze6^BM(QdsI4FaDVXG}mCCVW((_?{f4hVgao#m(Z$&&gy8!TYY=Uq2Kv6u|#c*phyD zn%ugL_G4i(Ig&?EcS8v4rQ$0dYFg)j6mfad!{ep2yxanW*L-}5F$}6lt?6X(yawyJ z)5q=&CmSBL?z(NgaoLY3TMYjimoGW;Ddee`Rw%T!cd!{raiQTc8eW#IWkDbT6`-+e zhYj54F(N~Q&sA$$JhzK(o0r?o>Pwi7b|?HeDmuPP2=qpil$9j0*8}o1ZrpBMVRt>i zL=Vk3wXkUUQn7sKiiqqg!vR!`-K3A|KG@T^Kk-)eu`}&SgFBd)K}$!MlTn5!Qv4{X z)qide;k6X>4L$vXF0J<^^zI~-tJnVd+V-O>q-uU%7mS{zV`56r$d4QtkYmJUCzWr0 ziutQ4w(W;#8MR5Nev2?_EN*yg;+ZN7B6Peb0r-lhmQP-Ph-+TBKXX}SBLGCy<)zF3 z4o>bb9QcR96ll1sm~^(T{G`(}Gj2yatl2Emn_F8Q)>TWkJfCP|OgRY^BbB-mxI(gn z<06xif)|ABygfl1K-ywjNY*M+u|+=k^}o~HLO0$*^eXyBLUgPVJ9)f}eR^FPe!&vb z(r6ajpw}nA+u0KY9Z!Cr>+J2Ze43niIx{y293?tIU7|4!%qYcA?o!0;S2lVtS*aV-a3Q^}fZ=`s(>C@L9|CQ-xBbU!Ma@jfNfHM7_H9K?^dkA9KDi z+r}~5J7i@ND0#~MPFl`7Cs{3I-i1%TK=n1RTt}ZIDjOD31}9sF^5WUIak`QDZAeb5 zctA_o_~jlRANj|Rs%5`6H|--=4BN~fQv{4YriP|bH>7Q;vLGk@l(&~4aB#OGt3?69 zg6QJKkN`djM!ymY4)8t_6%WC*G|Kd<@%oUIqSdvKs1J>BrcB8v8yi|};e6T3;uDk`7f5+VS{zJfSSB`@~BWXSese=#ghUq202KSxeup& znm~4aMlx__G4|d`xPPqfdh)2)PpisCK||%)dl|>)y>Tp%^78LdO{rO1PAXk@At6i9 zzq-W%K|QE)X#%p6W~}3!EEi3kW0-;no7OD@c`t}(=Y zeJ?Ti7b$qM(aJ;At5ma)SkUA;HG zp8v7mWyt2KN=CAW0ldM%R3QVYLo_hBd9vJt8^}*^*W=D5TMz)hWYKF}-Mg(D;~q1p z(2VFdBq!HLSi@OUR%OW+W8sH%<;AM2 z%@fDvQ+GGro8*TxfRO0&EJ+8PQy4RqgYeASoWyNd@!`QCrMZO#;<4ll;3{?D*o!tv z*erO`Gx)S0Ur+=|OS2})7HD0M=e8|YeP&CjR&|Xr=nNYfn*1uvOyjXNW``LN5gz{Y z*ZbVF3spr{NenH6P))iLs|b4aW!RdpMZ2{&>B~6rRb4+CT3gd-#H1sYXl^%xT|#By zU%yyPXw?%TR4nYT`9<@PIHV3Pn)hC9vR<2QogHUQOPe*oQ=~$Ssk+qJ)xh74@2-$d-(LN8*3eDAQIkm{n#r=0gv-}QaDMbC4n0q4_R)O+M{KE$fP%LJxjPzVmNV+`4E}R)1D4r;)ciMXx6@?=vCIec+S9HR{ z*0=MeJRqR6r+rKvvILi(J#A>#p2s5)=HEAq4Z^F_C0rw#N>Bpag=aF)(o+*lJ;({5;|A-~>6zHWk%+4gzg6vObsAi@IEa&oC#PocUl6~DY#>21 z>5DA{H~a2@8YR0{MsbbEKfk{AGKF_I<^I-X(PZ{$!~uD^OpkZ(@Q^YkzAKOWs{tzs z2`gl0_bd+}9}fZKCno2DnZWQ-I&X^hi7^klgqA>~K-I!gVhc-7ZP_>A7T&Ua<}L&I zULcP-YJEqdSVS1wDZVyzU^idSL@QYawXjiMe2~iR`rJk5r?Y{9+K^*w=GOjY33wc0 z#!ZB7;pUd+&xl_{t?YDK^NfuxM~|83*EA*!9}kQ51$DM359x(@%R?yr9hH<9KY!jO zV4_zG{A3T%zPyp(r!PUx*VII&J!tt}rOTSjlku*}eP$^5qjxQV} zyUjg9{d%t;5U(GYnwgF5xhecheXn50;*b(ym=_d%BRQ>(0LKetdNEa5Ls2V^56~@`jV94}DiHHbrV3 z1n0<{bfgQZfPc%$sroNXp=a{2_;9Hv=71LyC@TYY{s82q=P^DFsiu&jHYME&9lJnb z;Xj>>1yuC-^u&^Vb-3Ye!qus5@}U$+%M5;rdlq-)xgu_wegCmR9{L zeUI$u5W2|liHxz=&seqK7TVZ)XBoD@_g|IWn!xWYeQ;XH8%6nqc3mv2yGwW zyNx~E6F|SH+1j*V1pWVGNhk18N+i2B9!)WblxJPfJbM4%e?j5p&Y;?Ruackt`)lCs zEgqF{02-R%E(x1%2Q}){>N%UP(7wkDTnw!WqpkI=2ywFyMH5vXL%PW&??S+ZoR+64I+IEX1& z`aFJ9OP;0>GGr-LF{9>n@wo0}v17}{OTQ>-x?-l{!F0hG zCbepC()uFoQh;EfY6NH+aaTNUNf#sX7Co zxu9?nq3_qk;GF;Z6)aAo6rN^t=z8B!pf2zO9P{eqg7dJCi(cpeT&+X0)z@PG! z`r&-pglkwJG6o;#dOO+jhu#la8JTS3J&x@`h@_8?fZVr-MFUd4z7!y93;ACWf%szt zbwv!@A1^ix)vX+oSAR@d=#J9+mT_~0{aCV%lLdTqP&QY)$^J*SNA_MyYVE9^J^d}H zf|b2$Vw00CK?SzMmSLqk><0Eq3T`5|o%rfqE;+Qy4SUv}Xg>#lTB%VTY6{GMZglhTfb|D!jI`akxgVLZc%nT6rLw5TPbt^Hq{GO zpr>PIju7^}s`6jJ1+~tN;t@zfk#D~+M%J|iEeW5C5agL%+M$QDBPBYJa(ItzTea2> zG^wZTMOsqHy{P--lH)`*+RL;cVD?sGY3I_}xR#z#pk64u?%HM-I%Z4kTrXu>`;h4j zne+N7d$r2P!g|J_M{#riNU=HRbv{*lf>PU}|??}N9qvc48tZ3043Av=`ONV^0v~0~e zh9Qa_e_Hj&lLy@HQG+{wgjii`KK{_3Fjb+yZDnzH*h1(Z)xE0}j z(J4ePM1==RKdHPir?YW%&@Y0@gOcClYbleO_Sx2V8pK`cE}6T#d;8H`&9>8S8;xbt z>rgpqOZvBELy0LE@qE+Vlg_Z^%rNxm&-HN~bt}iF-6;e8G9$0(dFYjGOk;W5j~eS5 z+-_8z1n+N0UEomk3&GSMnS>mVJ<_B8h6oT45;QpNNsJQiO1hxdf{o9YK0m8ZeT2Z{ z@SlfP`Xd8G!D{|VUarx}MKm!Bjn|+9z`^JHt@}2e$Dy#aiN(ukxiQf3eNd-q)z`(k z8p~mN&o<=4)6=ow*mp6rv)X^`mwI@O0tbMZZRf&o-BeCn9)K?idY-I;64e0tBP;MG zO^4GmK8gEvM;shEn99z{XVpxFhgrb!eid{krYA52E#};Jw3yz`f`Jr?re;;O2|lmY zX9qI@QlCE6d-iqQe#+z-!{6yGxSToFahATRc-+ulf^51dP{~lA#C!Bj`;CS(L zGz}b_xWvS+z`G4mc2Xbb1_Me#=fvAvKR^qG)*8X96Ki&cA9qLn{K?2;zc_d&j7Vdr z)30>mqa1{AQY zHth4*PA-ux*#-ue&2+KE&A=5EUQb6j?dML zbqCAg9Y0ZZ9?W=Uh5F>Kyy)bclixnD#KFsbB%*L7#;EXH`X!BS4~g2AT}A~xv($6v zu{_XS`*XEE0u{=IsI#DKjc4^?8E znV*;NmI}Q5_F6s}*XAd7p|R|Lp}170NBgxj>yOc}7xpF!Ye3@hID)g97hGIrE{NwC zzXbJLL<%cfLY;(hWFQ|D1EhWdbG|UuV(iGJJ4u=zX?ugWRwJ1>Pq9eD3@aGu=z<3$ z6#yzS2&RTs-Z+T0$xt+%qc-;v>W)QI!SQWDT9P2H4$b=43lx5EF)4F{i`6yIbzJER zI78Bp$Qiq>^Q6NS&(CR`mfxbpLFB#I4rmYZ$C&$TaUiJii_CV=Qq9TEwQ&3NXlAA5 z(h#(BEX>#OU%n{kIQ^_LKuy787b$1Tjc}MF1X9OS3f$a0(CThIE3`Mc}REE)NOkVLjkEj6hf_w4TmhLCJBr6qE?95dCb8pB+;CngZEG8@?0#7Bl! z5x29-f0EucDf7701vrKdDGmJ2E8XD;#UFp^M()xI6I`v`TkB~a!kC39TaGqWEN6KFX{KR4^*mX^*-K{? zfp9;n$g%X5G{ zlK;tSH-2)X&zb^AY0dwkiLPMOx5^AL99vzF2R4g06nOU`v65LVP|HZl*}%^LqLK`A zg7SdtKwd388b%iGebHjPvW(0x!pTg9;(!fAV1FojU0!bPbpVVCTnMg1XSG6wwbhnj z1@D;UM~-Hf|E4HsJzu{2BYGQGAphaYg*=1kJc_qhtkC@9(|&g~Llyk>Z=9)Omx*^b zL@! zO_r;GR?jXW(W-ab9;zFgv`~?PYC0R`Uz9IKWa@_2%lsBL`oge{l$@}cpsGKz;597`a;x8lcBoSJ-chvUyo>9@V zixR%LUNw!2@ZEAaEYD$(FB%(Zuwoq+amJfV$Xu6=OFk3d6LWM0>UqV`MUWP z(1UXrnclV=@8Z3dQIIj8`lo^aLXgvuo}C>Tjy=HqFSvZvQM=B=AgtYa?_WWk4@bSQ z1E%9;<8P(mj6sk>>8|d33cfFL=+77v{yKLS#*Tz=-~uc=w>eL^J~K77uP`d77pM0- zF|&lkb5OK~F(bv!FGx<2$HIqm&7mi)2nclY^C|EzJ@aV}LJKLs3sfxPj1_Z#KpwC( zc*)A+W-(`t&8vtmZamR5#7GoGf)?629F@+y{P&+dN4kViMoTITeNHEb=B?-P($iuw z4%=;(=PY4DCa1$kf|5cK_l+}^M8=IeSk&av=T)I!_38cD5l#fjThrb@FYFfnMwRK+ zNfQOIzh+KvYFg{Yc_PI8SgVTcjebdP^_SuQI^@*Vi4JC>G8~`0wECnN#;agpV9@7E z`H(p8v5yh(p{`yZP*ypz-*ppcxq(xFVrTeI*}_8!ceIEHfM5kROG&lXEp#A8=x~5A zDK@rk`t8E(1rqXZgbMKP;homPwbCnh^*kt@I3i}V?$Us`j%|VlEfFZ9c81NRx$A{$ zuBTZGX4$7}48(UG%=J7t%x3S*xolS0^jX3jtIJ(+MB#Cqd^ZreiimPyOecIAsa)@& z`0*D>*}OsNE=3x?28fb7m@*Gs@}hrd_48S@0Hq%AY|*Ll0basG_XvRsk1TjuL>sKY#wrcDCn9-#b3W4KEoU7&2R4^cLqu z<&uopIM`5eC}cM@q<#N>7aI#?9C3+U)$)y+dEq|=1}HSkwfj*>6g0)d&c;T3r38;1 zjuC;+>2lHcBZK&iUo~DeRnU=DV+v`ZZIsk(I&v&*g24Rp%x%U);KlAGyan@xM~1(2 z-O3D#ZtVimiLI3)=wiamW4XIe9!w_0oBY;5dXkYKl>Svqvr893f<6JCnuJsjfGsgC z4DbGUU?`?MN|_*Dci+79_7?u>R*raU)>vvxmPVGw#f{Nqh3QCqo+65R{T}KI{5}J{ zzAMn1EleMoXlNNWc~MQ@rN3{8dk2mc$2ezk37)#meOIrMm;j6JrwL@+&@mi5xD5GP z{a>kW)tmJlLseA{1dOWf1V%&fdAwa#L4b8YJL{F6fZCN0P2`^70b%S%Ox_m0Q6WHU0-zpN-#szm~%P=C`T!Us0#5 zt1V|M%^+&aS%f9tTG4p@D|!prQ5lfwE;Rg<+%CCbTwLyej6?5&xh z$L`6%x1zY#3C%w~XI|oOH7Kd|M1mKfHP84&o&(>yo8yHgySb)szhbSDrS-|YJz%Wp*jjM zDW?b|DR#4vAJj=Ei0&DLz*;F!GqfuWuh+WdodQINEQ(%PfZ$=t4S+Fe9`>$Qer@l6 zNk?P#$$GE9r!Yh;^57Zr(o2}s#~1j<=Wz;7BL8Jw!KZ6rIC0PXp^X~7bUu%MQ7OfKm-XzHZJ`DpsI%ob? z9Z)@~LyzP}F&~0NlM>3v%FuFAALZ?i=pAS5sn=J%-h;F8vJ>2! zQrFMf55I;_HZ*wW{bc}ePPYuh(WqeNzgQpx%{UnmE;l(2Xd5m5CE`(6(}P@OLKeDz zmi3xEscl*cxgI=&my*>OioG%5JbUuI*{l#EuNb;n5j9Pd5=YzhV_ zfa$oolM)lvfBYcl^H{eYMTbvk>X}#y0K=#jQ@Yq;gfipw zZV#pNyKid&DG%sm4tUFJ%$=RCmo6|3jUU!PJ*=83ORz+c=0GsCD5@VfkCE6+v@2ePeBR@vp`DNv)agsqU(;Y=~^ zEMBzu0y#Da0Rc6}hHu3iBI?eCt1bp!1I4?^fQ!L30&l|usWQqt_!2V=4ITG0_l5dx z#epI;*{K4%Ozx#3#k`^G$ZV@`;l4Rd&9EKJ>`#mfL}ucXf(oM;4eAMj`kkVPe~{#p z`mK+fvu(T<%6Nx@VxK-4TYMy;9ywlWWGDCKztGDj${sUc2d&L60m1C$P@Q!sW1v86 zd-()Vzkp5$jfP{7mEI>?u!YZjD-sIDPJWAw$4VJ~6r>#W7p`kL@`G@(2n-N%|3g%vWCt5{Bg`c`n%AvXM_~{S0c*F-oYy;JAjc?y!FYr?`hoKbCeln zMqkH5l0y`W=mg$8PSX8m*S2r#=*Tt_kri6Yt9gyQtJG)8dzwPZCSCLJ=*-LO`kKaS z7_zoiqUz)8tMVr4)hG9~%vx3AaR*yHTbav@HGnDLdV6~}`P~pa*?fCb z*NPH+`Y7^feSF>6>$`4&<-xYS$&pv{@;eg?t~E5ruxMPI7-znYokfJ{>4h6hb7TiZ zPfjCsrt7`csi!g{5#YUPBHruM-_8m0DU1mGM9OqH1+eitE_H33WvdpSdI^Q)hE42k zUWc0jPcc+dqUR|lV8gt!B2OYJ;S+U?%UHJCZzP+j`~u?KGFe`kTQI5z_XN7cN4}mQ z)@Q;US;8lI{W2l6#dUAUOjML0SMs%gvrZyf1YV>xO}3`r{Q6@*+=m$JU)c(Bl^H?f ztemRn?(<0i6t#e?NCr{!89q=htoK#XDf}A*rQVbY^W-BkJ0cJ;(|`l5STSjH)ywbe zdYn*45M?{kXPg8Kv{|0tw`R0Xi-As}j1Pbk=X6Rd?)3szhUcUK4ktZvu!IZIztN+y z(*4$UZpO8ePfyN@^74YS0&c>6VJD9f?EY}KWHvo-_)lxVOt)6vi+{DTRZcrciSS+NvOH+c}&fEjjX|AOii^GWqsizK~RRT z&B?&UEB#w{Rq5b^AyFhbZSiih)EkE;mklx^;@rg0lY^`1=JR*gLgmhUp$12 zOPd!r@31j3M!Q4HlkosiEq-*HeblP`0egrEwhlSL^kBt)8R&jF6C!fM;xC;!&p-9^@U62?Elcnuzy-<#0xS7V4U$C6zY@3N?|4 zHqT2}K4_h9L)}%NUa+A1T+VA~Ru~3L8aUfd?^UDm}OMiwpPlF*C-VOI({aya8ZGKpo z$y1>O0)pZ^QEK^}>;`r!>&Z``W9@(}`4*R!uAWwP0#zyC26~mK7=(p`0TF|j9t?E3 z)KvfBe?3D=g^kI`N(ZuCR1+(VJTa(OfAcl$Ukh$`?w(wAJXqHj5|TBm?++n*WEgJB z%>hlW!cnXG+Z8*}Wo@p?I*+ID`v*Srp&zE-`NPncVbskswU(FU0NUpZxaDj1-+D+b z>J6kbR`=K=5XMPT@$wBDv8Ke#|NQq|1Nu4T8_O>vtfXsCy)8m zU3x{U#X|BC&6Y^01Wk;e-*QqUH1b(cUQUk4IzdMp2GY5D;Yh#9kG;N3nQ^xHz?Y|V z#seQ+OKH)l;iWqm&b@l{rWg$78`-`#1bn|?USre}Z9fc=L> zJ%{p{2%!CE4dPmz`q9FsFs}B>eaB4G90@!GP1C|Cz3iAJ^a724*(%~pJp~LLmIa6dN0;V zXmanh(%KJO=4vDeGamPJ6P)n_B;R7ILSi99m|?SE*|G*GC!Q@P_nRNiwwS$lew5wZ zEWF;*#2nn4UH=;`hWEVkDFR~>NtN$;LT2K7h>~9m-cdxP=i_#gC zvd}uzFcF<h7XBxDT7GYwA9!eL@O_;U^C#QXM5vl4x;a z{*c_-+N9{=J{+~VY~`$=+;#0rm+pVDM|z%)A6SNylTywG;ZNmyVjN z2dwc4-OStF-(PKKJh26H7A5w(Crw@|yEV<_Ej;Us9H8B&>&125+{KTk6dXJ=iji_d zf-atzc6~UFGZcxyMeDS>6`=-P+0D_dBaKA>AT30Mg7iH7q@ue>m7`l9?K2FVr+tro zpSv|Ag1h4EeBRD@x8!*?V9#Lr0aZIZkjQQ&cm0j27|T6R`$q|BmFfV6I%dbON^Ggn zF{RL)6%l+iX<`RL5-^h;ODafUCcV6)wt4}&PZzvsUVu|%o_HMLp2+}L-pwMTfc)YH zCOcHIn(MWxL+h?_-2y*iAjWW#|`++~JpA6MpIllqj z!=djU*y#+!CpMGi0}gfR5h-jTNd}aWD?3H``Cp5qkdI42yqthXnIJ4JEWW)8cKfTJ z!S7429qq}dKu>5j0(pw>zde*mlKfdCZ|rMVF0c^CU+T+LWB_cdK6BIatGcRE)&DB3 zResJ+PEB>!+RjKf4TJCxtf5cgmj~=eBlk-7r)SfkU!mJpsS-RS5)SM^hQcI7V!A5j zKgwE6eZM=+1l$pzovDPZ032njRq^6a)a!_pudnbO936M|Ynk!Aaoqvw4jAB+eF?_0d<)k@C*ac+@7Wp6`Tv!a6pqVWfQ?^?5ep?&>b_<$?|F`^xDkcTa z?zyCP^VkQjRwFMsMuH@AjAwWoWk|U~0KH#WQexXT`d`t6tdP-lB zKL(B--eB$2`@?vEh)v%?OIh8Uuw``V#q}K!22qXg`WOK+H9jMQa?ykDaIwjqFnH|Q zbIT1kfxZ3Xb)7NJ0FhfxdwctwK+gtRf*2l$#jlzaq|E*m5Fq))zOx_BXi%V_X_ff& z=}kJX!_%|#dbb+_Kx-7$)sa-N3B8=MtQnhL?rDhF`B`3u8vubohNF)JLxq&&KE2s{ zbnXPiQUSv5@jZpo;0FdG1~6kYbI|^&b{rH9;B)g`ETpM~TR+NTdH_B?(0zwOU-^<) z4rO##e_@M*d6Uf@dEinDE9iL7#hUO2v{L8w%Zy#n0+l4;_{f(pUn-Hk6yndWw+|d8 zoBjY`px*0-79?BA5aRE-K%G~x?@kQn<~D(z?e=y+Nyv-z_$EiiwgYkm_^NH7r3H8- zO-1mMNl{r@6v#U0CD(mhF4N_PuwmL*%{&Y6*t8oh?N7;OmzRgf%sv36Ikl^ari8@$ z(z9jB_0mLbnW%*08&4WGwc*9XyPe80dY3w(^LNuhKrHp`wZr3belRFQ%g;Kw>8r9+ zU?Vn+0}7qtf;Xxg7LG%`<*H9M^ZVy&^gl{VnA2|#sq$L-qMB0qWryK6oe`AmZQPey`V9WYwM5eTJmvxSy;r0UVks7dfco z)`y!HGLT0sZ_9#|qZ#c1l3%g2dM7Eq0mA|K>2NtKfc1iN-YhLGy|<~omJ$UMUlCJZ z+KsbYTA~qn$FAV5sYHA}YnF5akeBp-I2b+sWW}Iqt;@~7f@dbSTi=s5M@FcfGyVf; z?$45vyxKBi6(aT@kw61Q`y?no8MlZK=x~&@c-=4E7k!;#CajdybTnl3?I>A2TX(Cy z#!Qom{m90=SES9<=;|C-I(Wpq5I#FIG!pn|+wUbMg}H0F#U;xDl?cQIKqJmtgauv? z1a)9XvDoYxij~583KWF7d1ex5EUhaeMt4vpI&v>Cuz^P<@&pi3yXconz@+#5;se#i z@84XyxeHwb{(#N=eB4NDpzh`=Jh1TS(wDTtUp zlJt>eN|O8W;|Dm*v?(VxEGTP^0&`ias;VIh4>B_1CBW%FvbP768Tc4r z>goRm3R&wYLh~F&$Z!&qQk;Pfm(`$!w8=rOy88MAbru-%B;)}Q%dE}4R6!WsU9pL` z+q?v$T>11x%4%BjR#ptTj9^_nm=NeN=K8w1sa@lPbFni-?HqZp0D0cf5vst`vL3AZ zpOS4(j<3Gbg{+Hl?GFo1xG){Hy4UsPS|ghs*%7VY^Iu`pXaN&ISOtVwzjP7kIH7v4 zbm`2FiO_$wbQRhNkWo>S+68%<6g(ty31UnV(@%^{OCa)VR2V8m|5fRf{6T|CZol(L zwnoxiH8*k*8=Zq@)KEGXyM%Di1OJ=87!zI?!x(wcS8FnPmn2I4Jd^g{r=B%tFnv<{ z_}DAf`~zbc2iemT-V?a*2O^@Z0Ak`NUH}dXV^qWr+JFIB^>DjChBp$<&G!8fKQ|Ok z1@fh|r8ND)Oc@@z9aBHkt3ikD8udCwRK=03TPZRzyHe5~Et&cI-4d zvGfrP*nvt0Un&mVgnOOc-7Px)rTxM5k$$VQ@GFxXApa_L1fHqqYy)c5Fl$tK zB-}_zSzlK|+DE|o?Cj-tF#QKSD2hu;a5RU)qM|IuzDR*j9a^X9d0!t8Cv6dtkbp>N zZEd#-QU@5>kuH)K%bQxQ6x=%U~E-?Ngvgy5? zWWzsk(}@?D52lAe+5>e1=o~BOSFQ0mdpv*3W8dmh1Bm#v#Gy-;NQzeHywXzBix>0PgPB z-9UW~c`kg<0?h7`3W=~;CWQZm3xE-V;AIY=PX+Uu$tSG~oTW4^K;Ho-rozCc>)X4} zAEp~Fc7Fb3Vc_RSua%1$IFeXdkT7q#R7 zdb^MQ{{G5*n#Jmy3Pbn8h0bkhG9{qy=dVP)R7^<#S`3gCZ0tU5&Zsefn2?@PtOi7| zj7j$*Pho)Y|EWDY*I)w44P-+@2P!a1M_G-WOjHlxyMDx)5&_CNlPc|>KTF&8+|F~a z`N0G2LZS|rZd_mO`0w0|T$oRPLmW~p?IP&s%+-jPzURt>=f9FqWbu~x(_`ivm>Py8 z@qnd7%^f(VM$M(p0^hmmUc0WyX5E-xfuEfMcFP|;Yvy47tB)~%{_?vDEu}kn`0MjS zO!OwBoadO|N(Wi8$q#QQVK)H?c%otvH>Y5U?M%g9f|3ZU=5bVX^uBFHph0g;g}(^2 zKcNVU1Y1so{jS9Tj;i$x4c@OOsuE~~`T@f+j^Z)?@}0;q`*7bjA3(h@rg zF)11FboueaRae_(|05X=Ojp>wZ>B-A#gF;ppa9NQ>21dkh$p;tS$DFk84`W`R5IGk z*UujnF@BA!R7ZvJ1A@1yPug`zRV@(6SIB?||6%B~|9v7fPvZ((RuIm-{m4;efnl{W zUUvbb!osvFX+~5F6XERl0TeLfouPO{Xlsn!z99XVB zIO7R~1Ecj{sw+JfMEu9bOWY zdqn{D+s3&DJs-CzxQHTe{QGxuW}&DEB2ct~I6m2%EQTj=MpKI9io!cZHHDrhPb(>Y z+^=EI+5><6x+Pw;IR((%ha^86OZ0%QV)~$>Ij5rOoVZ|T=W==^un^>E+cMTb51pNn zil(ljIVuJ=A=DQtVK^bO+X(rs+^Ds1FU`iF(W7kk{UpraqDIGiTFodhOOaAEXUFLJ z3oaGu%t*i)C-6h*CoM^B{Y!SlTnzJ5%oHdSz3UX6Zbh`BGFc7(W)~MT8Ni|Oto}Z- zhIcOxp{BYnCKhAhN52$Yxh@3G$WluUzIxOF*Euc?8Gb7>TLW1d1G|fuCfhli!x+dI%>-;*T!6b1UJm6?hS z2F2Y)Lf}_iTLMqNZA1X$kATv&KvPC_hsR5@5QRTsyJrdL@7FZ#)2L@Yxu6J{m zeNhJYM-=7{es$uM3$W@O2N!#Pw#p2C#oJm244WE`??xc}C8D?O)S!dh<84KjSr!(# zZ|xHsH26q?Qg;7%e|mg89(?!j6-59cyWcA`rPY$_=zlO25@1ydMKVD*>irSPcSoo@ zM{a)p;v3*}hsZglak*=0rD(+d3D3N+^<~tB^OFA|S32(R)VRNd z(*8FUABP0}o!*v&>l9T8Pt0BpeBiByPf!cTI7g` zQE1qyLPP1_Zo6A{o#?>+;eV}bTko`&XDf?;{H)QikN7M(ukX(n5)$%n`Eo_n!&n31 zeY3@O&@)55h$?x%%0A{ZKsWvAE6;(De(p{o9)h93ws$>~asfH(~K#ue{N1{9||t1?{xUd6%s4 zO;>`?>1mK^eZryI@T};BxS5KR7 z3sP#WVCN)3=vInZD!`BHc*z&q&p>@2YZ#kTHR%&H05%ktC`UiFFi~R8kwX_6y*t^j z=5u#LLavK>e!G*Ea)N;0umzTTE_>_YQ3)y2r>lUCC2?`Q@3O%b>CRBFWq+^6XCsQ2 z>yM{Gh2M>au-B>b#hy3tiQW2iYugS({+bz=%TX-oxSn5bF+{GXw2t;t2j=$@#v~1y zCk=gT583hFKRKD1N^t;GhUT@L1R8S3kDGW?zcdzKSB|se+t#8VlM9 zt7N_P({p(?<~W&MlGf{nWq*``u+OdboPzY6zGe?8j<+O;bOJt`KOuhSvdwq%6!Q&c zMJ@28iZ?Zh8a~eA_jmi*_-B7#n9WK{m1*0WH)Pps!}g;ArTjS7NT7KCdsR{4Q!qhN zpFL6@P*_m#+JaN(Ei3Ck?Y!k>k#0YuVs*J9rA**QQw04+VpvN4NH2n2dgU5^^0Tf^ zOY~+>$^C2{eTa>PF=gmqf4{EMnU{hu?0ACE3NpGQ0^@%n?DsxPzdjpeQ=3mda-G4Q zX}+w<+TTzT z!SyX;`6jya?nx~<6}cvOx}&49ikU(MY|YSWh;U%bc04ANz6b{wmpZl#1_lO2faJ#N zwYWiJfW|O?RCHuHAN&Sd%n1)ZLCiP%)`=jY6Q`aE0{R?@0~X&+j{Ew;2+qFt^!B<# z`&61wZ%LqsoI6{+$#{5KBCy{es+MOqUXJxr?}PKNtE;a{pFSqxBQ9M?nh(13z0r0_ zklxsKyGe{DXP2vfASfv*SrB)C(I!Sw#L5*yw|Q44FDZDTd4BFB6Y8I@7Wx8F(NQwZ zvNpf`6E+2hccQzA`0@(uW<@u4s4c)k$*Zh6K`oBIehfvOjh*t%U)hA+D}`2L^F2id z_LZ7{(+~H$-^nNlGv%2p%=4OV>6Em|&gs|ZZ^47b90QZbVnq9X3YtK_C9vx-qX#7p zvjh%{`U&PKDEe&Z>ofHFxidt@pH&uxa;vgF>75yVE8y=&S9Jf{)T|*W+!NUza|JJ| zx#{2Vd5_Y#s~CsS`KZyWr+m}iM<$&28Rw(a-P%lFV{a|?72Yna1>rj{`p4*`nYu-A zVP!@OW_mH0#tDoE#z2U0I~#w!)aP zQW<2~@MyVKJ{0c-@L>KgD*Djc3pD}>)C8w?s(~SEdT!YSQf~R2M#CB!5S-@>q*%{^P>I&Zwg%g4#^{)mp4B?-E zIx~ayd3ys!0aAE$6GB%k7jVw$-ETWaqbFlDZj{vZu( zQVQupW1bheG{MhH=9^vXk~zp;6Fz+*(&S?O^4pX||NA{tkKR69Vc6k5xi&ccob$lC z2B6;MK+wP2-WmEMGi~f}5!4TAN{$t9U-)m&6_FSGZtpiXHZ0VnG{-^2tZ2S-KU&}| zOrLSwRlGa#;pt;aQn0bMRyIRi9h|Opb{FOYL}@JR_AIafNLel~uWV4-?ndE1@9qNp zVVbQ1I|S*{z5eG<#?OaCT~Q z^Q+~~_I+&dVkrcO0S->|?&@`Y4_@@aEM_2{q0iatZogJ-U+3e+2@ae)E7X*hVcdzY zOMEn?fc_L+Vek$-H=IkxkKG`&ZLMX}maH9+tRSWN4Wf~d%v-WAJF}GziqO2_y z_npkbWn5u?uO~P|q$O=aP^TSQjeC1;x-kq?wk84MSbOLI2>Pb)<`Fjo(9-Lg`f~<*8i}mi3ST|c@@ov!5v5=TJ(C_i`7JY+$x%Xy^n`~i*vTbl>hzp~kE3BB1%u z)z(%X-bxH(0KG=t_KeHqkzgHg0x#$;cnz#FF1H`GPqmb%T%4{+g38<4#6&#JPMTe` zP@MzV{E2{ClJ|Ga%}qmPgTvj$*jik5v0RkMpR`_LuwzQt2P)%==e&_WpE44{bHwx z+7ElsZtpZS3cUum6i> zV$Cm#Y<$RVQ_YCEX+M+)o^Iv!g&Re6W~M-Z)B7&vo#xf z;%XmJR&q$|YO89XW0Jdm%7V7uwoM%OVD1a*8pkjE{L;y%vyr%f*3A#uDZ9os<20?( zomCg1fb0koHW|#U9a|&+vOTQhBvu*F_Gt=dn!FBwZ)(uOHil#%c*^Z5 ztG?3L_Y;gg=>4t(vD{NKicIf?gUl~Ij+HQTj=AxOD>+fbjb67CU6R~hZAHs+Jo4)5 zibo@}iaayu&y&3})}utJX&(0=%d z9`3~T%g!yHBOp3X@J&mz|2p*N_mCweSQJzW=sxeJw0AumIYyghfALK3&pPDAT_TIN z3#|>(!Om<>z8=& z;CFBpMV(N=>NQnf4pk9CjoC3`v|}8OjGT5|*~MmOyY(eoke;yqGd0yxr8xh7NLo;b zzDC_nEM>@&o1HzUZW&&@44>1B@7Rw@X@ypUDX+RkhtD5@cUJM?6#x(}J6n-CBDcGe zvL=^36cvFVk#BXMqd#c#shsX-;oP$krkrPbq2n84^WbfDo{>P+mCl0~77OZ5GB*}) zrkVO`1JmRfT!;7W>-VJn_#N*bX_-^qSR_y_)EZmF>sz4M9jjZpRZJftvK}5^k6%XZ z`r~Z*MhihwNAC|q>upKvsD!|wh8uj6%zv+dt@eWJzX#gMwMS_$XR|m2ge{>ZN|`RD zEDz?7!B22zKWVH!pZwp4?9Uc&YzfeU?vPn1Ur-I}j7)&w<)Gi16Oq!fOo2oqeU3^h z4z8;<>_ybF`SGZPC$o@Qfw~#V93ab&l=~F|#j89fLbT~da0OgX<4IojoBE}q&(Hg} zUF7Hq$tfu(fkf~h-h)xqBW(z+Y>$i;i~`3)P&i}mwDVC|*v&X0Mn2@yR;7-~zT~qc zW=b*X#^&OcT(#~$S<$A<@M6s;^y%sP6d zN6%w>y<~sWim@u;@lGszexc7zOOM9q9xa@-HZQQP_5lpkW#Q+9T!fBoE!d&z4bgap zc3xTpTBG|EzxIvf5^h^@&=z#&kwriHTm4NzP7V!N+k!6sa^-?p>D6!7Ud3i;H`_kA zQoxc&bEr+cAR;SZ{8&v43kMg++R0iBb>oPN0Gnhcht!FUB}pm1`tQt}SL*q|v&S6S z1mChx5n5x>?HvZ}jq>P0XzeFH(`6J&v{mW$ko)f}tc*n+%>1tZoo`NC+}QJ+ z=&yixvi)@bW4)f;zOWYLwkH=if3X66-hJ;L6S;fZ-YMXFRT>f+vf%$+taq-#wJ4fO z7Zz@YFP{V`ffD%NX0;rB{Cx*#+_J|wJ+t~? zid}E4uJ!Uorii;PC`oN##Siq1T2#IBii*?)nkIG^>)ntvL()MWe1GMATqszT;{MpS zsaW&KwN-rr4TGvd!|^@so$nTFjzrK)gO`s3F7cox%-saU#?3kh^M8d3 zvIT9Riz<6%N*PoNbqKr9N3Zb{L;?q`VK4vd{bNF;>73fukG^n$f%hPNsEUqZ-XHV9 zXhY1RJ{GFz;F|IO*n7*rD7!Ci6cCW^9=f|kx?|{ODCv@JP!Ny? zfuWJk0fb>l=@JHMK{_M^X#|l}O3&p_@8>*!!g+D#GjABKU2CoVU9s1-7fAzOQi{e$ zdZDv67u8sgCrG`jFTlOjD3XOO3QJEtGpN30^*%>drF`MD8DI@L=wJcIJ@fo5cG~u= zZ?mOs{7+$v2B&;PFl^(_7WURYeXV!Vyf5~X9CMHNY8B7=w?{6;9w2WuBOlyN zLuh2-y^cV!D9yg-k6eI2r9XyFWmcs0SvMLa;NrkjQ)x-7m5B9JR{JE`mvj5oKBJ!( zOz8SQdPdDhV{4#+y+KvGC&!hO~d5>Q1)~s_bEqh%YQp4yT9Ltj0d_B}q z6DSmxY<=;<#7hmGR+#;c@PQ`ePw(nU6F)LnqUS;SA8Cbe?ucOyh_$C^?lMJLIw|S` zg?OqWUe8u!H&U7LRfGOiX)IBS?hzAx2GkoJ=mFOk2uU`-{onH}Ju_~)RM3+RJ7eaJ zRAur%HESgkTly=Hd-TT-8U?raHCt}A!fRQm0nQi&|H{!{W!Xr@5>G&KC(|{+RFTojt$qQppoE?HN^WIQasTTcBKhcb zNd4N=2mY~z_W)gKLVr~;(IN)Ceqg&B$8ZyHaE%1}KG}N51~lClT)?~*TW_6|Q_B!K z@Ka4<-%WCw(GjCp>t6^8mLJFW*R+1*V{{g+gqZ-xDGeI1M9jBV zj9!WSh4e?Stz;c9K9Pc9_s6mzoAX0Wb!%y+&~s{-%*C211?n&kP8RL!N*FNalKcYZ zS-1RX{VL$u~o*CRMbIi2d>YQsH%RaIReJ|}& zt_vUqOsoipCzqr#uo&`G_#meFE&tlL^L;hjomhSTy%;7{`9Js(K18*3Hg4c+JEhRB zzq1fM`%yM+{YHFBRjeX6Ca{=z${fhp{9Sc%Cj@jS@=4Bhd-j_T1TCXH(w+|~B(sTS zW?#BqNJDI7N&Ho-$*}|UlGvs1cPY~0i=5!c3VBWEgdS+@c81H`kO0i z037!Rw`O{6!$=Y2sH(VBTNDr9c>R|P;NYPHZl&^XaKHeTos~425;2hyS>bB$2w=`$ z3N`dXJ<4*XUO;NCFmso3CDf_!@cq5u>d!lU7^NEXP|izOmyD%TzrYqq5)!9x zV(>}M?qVGEptw$atvKDug)3g0pE~=-VCGR=(j-HAk*ss`vytr;`Vt71_<{mcp_mR% z(spD6D%X|+K*LsIL(?(Zk+-%j0uYc}8%zMPza+(a3=$6h5nMu87l*Gln;Te6r^mmm z4oL5Je%S17dA1HU6kq+p8Y}2)M7Vt+z|XY?Ki?rP!C0RrfPO2=KV!2qgs!bXBoGi^ zW&((Ao$K;VIqU`E3SS~LM6=WPqYFD9pW{ks?9w-Bo=et>wWyh8A=n#2>cUl?V|tM$ z3z9m}(0k8t#VX=$L}cHH1b7Ufq`S&cd&4VdKvR@14h4^Imv?386tAYQ_v~Jr^1aDV zK|1oF(=9p2*w~kwNIrJ*cdhsTRx(|O8tj2+>tn?h7ftuu6eizu7k6Q6@GT?eLP3#N zeUzTYBTCRB%3-h4(Ad9AQE_Xi!?5muvmfh!nmju(ym^060W80@vt1y718KAM-U_RpCnE1SCj^87Fly(suznxWrve*QvRgCwOZc>NvB>IxKqOSTZ`vtF zs40R+gz537DqIqvtLPiMmRJWZ@o)2a^0pcHk@%KH>ru;?0R0O3irCTrwJ2d zj|`Y8eyI;tG}^*Km1gP`4(4QGmJeq1bj8;m9xL@Vj*4?xRHmQI5?_7ggak&f5IqN$ zfsoodQ;2mS8(I3^tJs2Rs4tn~H?!S6yWQ>oQlry<_~2Xlg*UkKe(B)UeJQi~JBQLn zqsO(C`1<2-aW8MuXTCAHJbgE! zgd>ZrS7*Ke1~hGH!okb8jx^Y>wlzY`RehqMAypAKeyg70|iA^R3_|BzZ717 zSLA>9fwjB7VY~eKJLQTYioF7n{J=czI8qZQih^%CG;77adFpXJ(z}!u%PKB`3VGJP z=B7ANDSa_1d*n^m)G;qVdi>HAW)EPiC@R<2WiAMx$-=979Q&feG79{_(%_l!ow?V{ z$QuJ7b!%f^4>E3a2BDuvRV{6c)A%j4*CqhySAMMvrC$ungAHZU8=NnwB8FNi#NWiQ z!D86nT%>1CMr#^7&CXneP{n@W+s$qygGsS_(k1<;=c`JQyn39A20@lYr8oxE>{_oxQ@ zJ9oBhYq){&VE?;8F$U7wb-^a~ceHHpc-eMmmF#(#HkK4!dmaFiiDGjMHiqy&Og}^Sd8ox9%{ZNYaqb-rinRWQ5fC@|CNO&BTo2_js2|h`Scj~ zmsbCEi|hQXp^<*r%c1xf3xp*p3KYBiGlP8_5S@Folv=gX*AP+e=RgCCzYR(rcoP9jm^+N}q@}f{yFM*+o>1L<+TCA95j!#&s`Pif{3$0aQP+nwJSOJut0Y;$Z8aZZ zx#6}wkpzm#m;dIJuO)SBo9SiEGbI?%x<)m@r_WXR+m!~|8hnj2Py017{7(AAari{oLCa(DSx~FgdpDp!){G`&0!Ju%Dq-%Bi=f7 zzVc(P5Pb9M&`HP-WA{a^rD^A>>4a3+^p>sT-;t)kU!~wJ>w%X7zucr7{NJ9XO*7he zSR$A<1$heH8YjRiYD8;wqYbcWI00Njl=3e>oui=ePo|#K8f(J>e(!kDak8x8-G0j- z%70Pv2wa#1Cszt*3hw-%>@~haO#X5+2@qMRfsrdbWE%eT7gPE$SK{H&$|$JhEiWcz z-B2j`53xYc>7nBJP5GY(V;}3ALkImc%_CIHkua+z{VrFS^u8%qxT%|(!bqd&KO#je zNUb`nG;D5bP)EI5rCc`+e96=Hy^6Fv69aGjWFY6TvHtR#0IoE5_S-qLAp^@a+rw4s z-$4k`suF?W!~Z5?rrF;38V&_VLeEf28s%ZH6jRyXbY*%jc1{uU(2bqcP*omgSEdU`#3-|sEL2gu zlKDY{=Nalmf+~hOvepdus`I|gilp6Y9pKiHsTo?hyL?(afg~B7#ylXxwsImZi zsnH=O)jsH-=-TBotvZV=afae?_gb;cRgs4%BO~m>(|*6(d7Ewiu|@c~H+gY(tB@l@ zVNwm{^D6G6s+ZjfIH}Gms;9Ru@Yl6-@Lkh-HlAqO%fI}T$E{^wb#`eWt}SuqT;hx& z$B&`$-TBdBKF>(L8S4iL0v|tzHQliOZiZ#ivTJ$ZY^pL{2wv;LAEX4^JHh7Zb`+Fr zOJGY$BEBznT$8i15k$Vt1b?xqS#4zi>Rb$kyW&DZ5XA0A=(LbhaIYdPq&Q8GCEhr2 z4xWQmoTwjktXz8{?>J(|yh5{+U$Ogx4qqlALUq$k_so+&OF)3=k3>zOf#^NwoKa{jlMR-on@eq2Zdzzc!Dc1;`aPv;C5$Z8_K@}=fffNi zPklp`RlU_tvcT;Wv!y5eSW#p57CZ4#E2<@!R1ty z=4p9!&6u{G@&6)#DeRI!jHddNsf)yzMnCv8%aj*#%pf=EWr(8M!aHAhF)X83@zZl$M69M=^^En>F{?~&GK zJBZAeC@J%wZse}jZP$OHu3^TIu%Dhu(<53ou73#eG{OYMxI(}5vdq(f!DA*aWZa^N zz0r{|bDpne*J08Oo=XkGu`Sn({jUqH9aj>X8eremuU%8VyG0moM2v+ z=GRLdALV*-MK#Jbf-%TmKA9(}pbAYOFu`)rXoEdb=4*4Puo_`9q^ltEMf@{P+*6HX zLF1R7s&IsvSNK8h%J9btl)tYlXok##iWp)+3mGTF4oMRxF3}81m4~b2#ZNDfoA@j) z12;XPh*KP~fr;*rfH*&21)XJg;|>cj|H#X_q@*MSYc_7exH}Gnj>p8GtVrJGC0TJ{FV^3&Q7BuQsdJXW78Q3FX7-2kTXF zVhtpPP{)RRCXB-SXu?ZDIDkjWFT?ecVDO1{sI@VyIbnE&O1FqLs8?F9j&VetUynaO zYl43iBO;RVjl6_9PVqA$ey>J+V}BzZ3$CQs&;os~Rl&!Vh&d10G2bj8s`+PAgtON= zxs09Ny4o0w32(;MOuNCj=c;}N6DddHgZTLxTbs-W45jI_FGH3?MyAL1)?J5kzIQ{n zgM@+_+qcmig*C?G=jwk#Sb0Ge`}FrM_31sgpLrq>*e~2o-5hHLKK>w2xt&4Z97EXkkd7pH|E(7vhk#)vPjpOXPBEWX0WXG z-gUR){@aPF<8hz>*%Wk&ia>(t3XYw7(J>|H$UQeYO~no;Kh_Cub}kvWm~@cUlF2~I zpkpn^R?y|Gt$*tG$j@Ux2H<3$Q@F5v&CRuVuz)am_oA*Cw9{~C(+;Pw zZMdz5roLUHGL`%DB1Frkkfiur7291ebx@d=aW7(zYhqQFOzD`>k9Yb9ln1E7akZ5n$IveyjY3nRmYB0+yR#`azZ@Fl3iqU zg~7cH8GFNIDI7?^hq8fX)I6rq|7jImWPS9A#Nm;!Xsv}PM{a?I4J+lyeARA4Wp_F( zINJT1bJ=_xVvN|X@>^fwYjCFc2iK-=H^ED=rVz7)8ayfHO(jS>F}B25uqV3hWvmc8 zo}k0_HLwC88E*9a&Tea}mfjtBy~+CH`Hq?H!jCLV^ABRIKo(jw%rS})ag>Um9lFFQ z7u+hj&$;%LSzpDaNdq!cYCgzV>6z2m8AiLpLB{k~UdfxwI(B`tKXJar^X4uKAmCJd6ENxy^9#MSZG0}{Jj&hatD&BaGy*#bQ2 zm`E@^kxZp zD)&-Kn9s4pd!S<<%QWjDD29zoC%xr5oW&wJO)D^qPgOy}-d$VsCxM7sB3x3F5jPy z?eBsqs_8t9_jA{S9xyG2>Nv|ZG?mS!HXx(yxh44Bl=ikI4~#NekBN-s((|$ zg7j$%M%O;pUpdlKb$eY&=9!!D5k^@EjRQ>|Li;c(h#M5dcSVlcHe+D=_KgjTu?!iP z(veVhsz2$Sj!D?r2nXQ`Hic_8`3P3G3i${GB9ie=`i>ZKu^n&hJ7-$QytfUfNuBX; zrv5jK63^>O5ow{p%$K!52*RF^czfxHJRCOfO?zfFfdz87y*VpRO= zrnt?ye}*=B!@j`$D&e2O%_m@{IjkBnB{(E^S}Yw(D|-=WCO zr5;z@{6Y#xkNGrYK|mF9)4iYyZ$mk}x2t|qlbMg@g)}g|nW0)Hzww#F0S0oO#_Kv`{~ve< z=UL|2!99(1A=N)PycHnrwulL__ZL}e<&|w;Zb(qx!t<6m64Cl(i^rvQjwxJH>%tSRHLWh&cWt|g(SMcc*+Oud zX%G`lk6fong`g#@-J;a)iAlG3^402y7!`hj10t#AtlJyUowq1c$YR~2xk)4W?xV7S zOOm^uzK>zcc9k$njTQV0_lc6_9Z+Hy_*BP?OOAbZe8wFEvh^-)s;5l0yqXN#?o||s zb8fel(On4EU^N6zR+^!H{MMt>e4kTxl#<6Zk{Jy;^n1~{R^zrUY{T3G&X*4t*&wu+ z-`oDzs}UUT?7ZRx6cZm@9A9tw_*?&4>UOY?R z2ZL|U_ooKNwZxWrBpue|^)dTy4G|UGoWq}iBP0|QXKKTFJ>TUDuE%)^6#U@>W6+WJ zWcJ8cbPL=Ri9Bgq(|-0DoEO__tXRnTD|pFD z6IjN8~oX`=!{~K!%pD%KJLi zFJi{iDxGYbl@i^TT@G6{BI(iQ`z%EO%^2dp#ujjLOKMARy?aV z{)`b0<^x5~x$v4#8!~-%YjSnf2%7_|7*2b;BqU`Ci5~q(J$k)2yl{YRr=6tFTVy0% zlsS=-lBIDkeyBad1*NrrgVX!iB~+~6hj&#~O9 ziD{mJGj|@KhwPsmmigBR4RIH#QQZ2New{xUE^Ye7TTxO|-n8j_`7!hs;=&vEKWXY~ zZEbBR`DMCs8jtX*Ufb^_+`;dhrj9R89{CK|(c zVOk9k&Xd7I5QP1aduVup9;8Vgr~G%X^OzreX|y`Qr|b~n4aW_L#@SXA&NkmU?2CGMasYC0mA%#9h4?D z&Oma|rMJ|2WO>^+O7(;m8>Gu>mN!#c;J0b|Ss5Mz*HC%1{JlbNy}kmGpp>=_BG)Xb z`og*C^+yiFJ*ua1-#H1;z$nWqj>kFPkhSryQ%1KXY8oOCEeRaRXTRHKRSu1}3Xt3d zcShv)xDeVMa+%H6`7GM$SlBf`@D`N*e?fo}*PsN$#s-#8_NDEiI~X3U`>B4L7!64F zEMRGuF9}So7tYBNeM~94;n3xW%iz%#DnN`3!zJH6T~@=SpC1;GCZp9c*#HY|*da8jgDzVWhg69hJ{?wGj%p7@!){QyH~ zc|U@=o-jLQfIHX+G3VYq%Ohm`FJ?vq@dC=qJL`8_UVrO4^C066_No$;7z8pJ>`on? zqbBhUNR0ll1I!>{oEmYB`ly>8aX2zz-qW0a z1$;%Lh98S3KM{P(VsxcfB|{;~Y1JGXO5J@Y9E&~=dXlp@jI^S~;RzfYLN1tw8OT5? zjM$7;u0oIhO~VjgBkam${4}!%baM7PQA-Eg;%9Q$ueH$6DYZXHXRZCU>L>S+BJF;=N>ns0_XE$?%>Oq)&l0#C6+~js9-y}eYE#TE8 zYI}#;EHsm=;CN9GUx<}yr9p4~rCH@n^=ACP7;!js=5U8=vg2%rD~1`W_A%OEgE%No z77N*R2D2UTW?}!F67XSgXM0OxLR-L9s4&Yf0?5T~;;HP7;IHUh^g`1j64%!L;O6P1 zuX&JE;FmBcUC~K$NUg>--*U}kqb2)cjF&HV+=wi?M?6WtM82rI$qG2VaTw*ikrWQd zhGn*{=={@|7>Bzg)9Tb|)5Q_EN(A<`+XW$V?DjqQQlIC8y`P!?@X|fFc|$02{oG13 zQDRgA$ns()pREgx`XnV=WqFG^H=p5r(0KB>Xr||hVQ1KNX(h*QOLI;ZEAU_wPu-gR zp9jkeZ!tv!rZx)z>)=eSN)O~o_dl);xLQzmo`JINNzcv=#!kkqN5-PJ6l)vL?k{VConsiiJAWNK==qWF z*7?HTz%XSm%WMqd@$B}Kedv{XN^;>^h5geX&d*g(@=^*XcKz>XlzMKzx^=#E(=a4G zA9FgSyDdtV>rhqIIi8tv{+O52V+A-yfM&}ck7cp<>j?Sjm@om>{uLNxqu*O>fHmXn zO^nI(g!nHGDbK;*SFLpmy^~g{*x{PC0>yYFj+>J82)s*c6voNsdH!p+SGwzLRObH6 z3IG1LiP5}%aR2IuvRx;N$5K5dxz)`@L8lF|&k#(`-ZMKX-wqgl{VMymUSPxznp^V^ zl&oDoXm+Zk5RuAS=%6HU^)7#LIc%lNUr<$s6d~UkQ*4Y4+-!9)FTHo>4l*`0-T_vDm8FSIq_`G20 z`jqwFYQiu-?StzN@RZQ4Lzeq9V9E>g!{f0a{fv~|i%mJJMb+Q_u=XU!M`!mr(w_xH zb05cm%OZH{QGBQ6re)fj2nS!RE!57K{)@RG%mR?WO-s~kyzZqN3FC%IEt^u;`OO^L zUlhS7zi;w9twL8X?Dw8hTyDy(PhE}^8V4L*FSGTW6Pg6v-YCze6y|Dtz5i^nSPLom z5i2B$*H*xz^U)__{0AIr^xLRAxs24r0cYIExK8%d;a{1yp{~GHHViE_6B>JTR|WSd z@WY8LdET}3av*Dc@G}A+?OX-n$rwgWmtIkbbbaEYV1z)gauv*b`OwP1tT_4m z=_ZB;m8bjq4!T&K^vuP3WZsp}@fnLuuiVPTmZw!v`SQUVIT}PdK^St`~Oa*k(FYgprmhYnJvLFxVzYYL*NDSzoe_cWUr82eq z|D1Y!@6K@$J8?AQsZYyaGqFwgluGWImi}YsEs$XnW}A45kac&!K`WEH_xb;`H$9B$ zE#~ga%EwL%V^ks1<=&^c?^0We787PI#EPC~9VXX@s)98F9=8>nt{R%)CVR-NTr`U1 z?Oit&aoE>7;M6}hMEo*ysa6lFPaaC{AkVJ-ha;ACyB>x=5zPHnFHp^^wnr#^sO%jb zwwEX9ys235p;vW5ay<{Y@KdftE~!4Z(JU{o+PhLyeOu7W7+!Z?2tIRfHySkswHDVt zYJAFrNm~2Q_0u-HMti$NJ3~%M%V~~bcP1l_xD>rli0pY7H#b{sJ}Wy!0Cjd&J!cwN zKeY|GWL@w-x{r&Ums@SA3`K}NHA3tP#f;&rSgU5;iKp{=}RDUTcQhI;w{o`ZJz zceh8ZZXQ$?IU(Nq!PClenQ|X?81U5pdd7?{0TsVn=nbFAk^gw|bu?$T1uXox27NJZr`v2SpUNCI)niZpj3de-AjQnu*0lf6&L_VWiaY}waa~+ly zca^`SZ2(U~1gJUln@O&NH<52=`Q^8;tskbG=;>M;h}Fx^OpFsgmU>$?7=tLl-M%g| zSn~ly?{MlB9nJFmn_vAck2(0%D(xgge6w#bJN>^NvaH=5r8zyFG1u55YnK=LYzXHl z-K3KNgy0Q0@DlzE3e^|OYbioB;ut;maKBGTG=B77vf3Xega@=)y!w51FQRqY>c{_q z_v{2g`KF@XU=U1%o?Z~y$K?%#bM=5g)ehRSsn!tnYS{HBS+q~So#k-?) z;Y2eGe%h~jYBz`9G+TI=s)CMUk}BU&Gz*%99XW7Ue(Np2!Ib1#;uIWm4huBU)Jz24 z_f1EmcH0RuURd(R)+|-tqh z&j=$?w|?bN-24`QSZ7cq&ozvBeAgteq>LO#+?7uOJF0CmRJ%oSkM{=1p+X676@<*w zO#ty{k75Q=H*M92w5EjMp`{tOiiK3N*R{O=>{wURiKQiQ^hpz`cZtC9U|gnH*|ry)@W{5JZUn*oh2huot7G~55l(r6%X zy;>R((C4~CbuC3c4fv;pVC`Ri$7oZAOWSLV=RJ2s{A)JX;AyB+fb@=PJJ7GZ!nKo$ zJ9ckTm;EO>yluO<{a?1U#kmGXJvxXBO?gbX*En(eH0pcI?H%<0!yEH}4gIQhZyg*g zaU#cU3r0gKw(ix}kF8!lsim=hd>2^!hwmuvkB4&d$Z3W+LtyJMHTE=$J zv}FrskNnLO6U{mpQ%_!K1P;%OEmf@dZg$+|JJyI2zf?w4pH$P+lx%bMn%}Z%RcstosqH`c)7{M~Q2f6fmRvGbK974$U`3pK9n{+$@G1RSY8PEV_a6Ts|r(-#$7A z+bPrykrYdbaRl7Y#z_VTw`UU>!fd!x~_ov)^UIl`fUeBvo_y$ zxV7sW!QcJz$-7^tZvgYtb$J(Y(80Er`($R--~8<(7n$3^0h({AsRJGI3aK@>cgA>p z76$`YNn*~zIDa^rZv3O}hwlVE4~G?5+k|x=CjT3q4zix0owTs)AKCu5H}9J^!E;AO z=9KGQwLiiFw9nrr*2rD~X4Pd4gXK(5eg?j7QErjN6vxTx%zmWjBz5QY~lx`_GY;L($)&Fb{_f+qAjpwMKZM2S(#)gtYirwssZ}s&D-=ME58S;xh?r*hu z9UfLZtdYC|1nT71VcxgTd9F8&z_>Tx8N3c}o~8f>9h3rHVzlGSS*8J_@eL6^7t%xY zPDJeSznF#MBN?KkS5}D5FZVh+3aN%f4vXhTPrgiEVq5;&{|3*G(bDDL2guu;;|bhT z(RpKPBCu+`7Qv=1lQ&yI=*g2mQ5(zv>Zgji7t;zdd5v^?+GXMdWFr3$Rq~7^m@0@X zDtK(kRX5OfA}2yO`C^y`j*Nn}Z`%QXh5mD~%Z(3!%I33Y6zZL)%PiWmd4ORis+c-X zu8K82XX;s|=nO8=C;Y3B=kz;s`~%iiQo^iyqDvO4Ox10p^!Jd7pC&%21M*ZCB=*)2 zH~6lkBb+54|Kg|Bd8t}Owd#4^^M+ibJPeVK`#fV1a<(A)1U8-S_ zOZ7YZ$K!kmHDV4FR@-32iv0V{LCt^|s5i?=PFLBEyz;Pmq;u_bOvm=kMB&I}qMmk?n(mp4(6-H(MJ?r_5 z>-$MA@t;UnQ7q2_c9oa!o%vsj`IGv3u>oaWhU42YWeCGe#WxR-brZvla;6gbB zi(iF6<|PIJN|lwV=$7nQO@h9mNzsA?9A3iyV6hItuMl8)kaRmrQ2-M3w+#07PMdlX zMUqsBiA{^HK=k*Uks4Mut$e$6?v2INhe8$Wv}ZY@N>N`04uYH}-I#tdf{uN

CJX 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�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

+!j3+N7;^JvstUTX^mKz2i*J~`QuRohhpux$ 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