From a3c098aa5803811f03d9b5704e5100e9b1b9fffc Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Thu, 10 Sep 2026 02:57:38 +0800 Subject: [PATCH 01/25] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E8=B5=84=E6=BA=90=E9=AA=8C=E6=94=B6=E4=B8=8E=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E6=9C=8D=E5=8A=A1=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Server Sync阶段一部署-2026-09-10.md | 47 +++++++ .../OpenNexus生产化实施进度-2026-09-08.md | 17 +++ frontend/scripts/benchmark-theme-check.cjs | 2 +- frontend/scripts/phase2-browser-check.cjs | 2 +- frontend/src-tauri/src/extension_commands.rs | 2 +- frontend/src-tauri/src/extension_container.rs | 49 +++++-- frontend/src-tauri/src/extension_instance.rs | 20 +-- frontend/src-tauri/src/extension_io.rs | 18 +-- frontend/src-tauri/src/extension_job.rs | 89 +++++++++--- .../src/extension_launch_authorization.rs | 11 +- .../src-tauri/src/extension_launch_data.rs | 50 ++++++- frontend/src-tauri/src/extension_mcp.rs | 11 +- frontend/src-tauri/src/extension_pinned.rs | 4 +- frontend/src-tauri/src/extension_process.rs | 43 ++++-- frontend/src-tauri/src/extension_stdio.rs | 8 +- frontend/src-tauri/src/extension_trust.rs | 2 +- frontend/src-tauri/src/main.rs | 4 +- frontend/src-tauri/src/request_lifecycle.rs | 2 +- frontend/src-tauri/src/sync_commands.rs | 2 +- frontend/src-tauri/src/workspace.rs | 9 +- .../tests/fixtures/sandbox_network_probe.rs | 7 + .../src/features/editor/inlineCodeInput.ts | 12 +- frontend/src/utils/markdown.ts | 9 +- frontend/tests/performance/agent-task.html | 5 +- scripts/acceptance_cases/c04_resources.py | 128 ++++++++++++++++++ scripts/phase3_acceptance.py | 14 ++ server sync/.env.example | 3 + server sync/README.md | 4 +- server sync/compose.yaml | 2 +- 29 files changed, 464 insertions(+), 112 deletions(-) create mode 100644 docs/deployment/Server Sync阶段一部署-2026-09-10.md create mode 100644 scripts/acceptance_cases/c04_resources.py diff --git a/docs/deployment/Server Sync阶段一部署-2026-09-10.md b/docs/deployment/Server Sync阶段一部署-2026-09-10.md new file mode 100644 index 0000000..c465f0d --- /dev/null +++ b/docs/deployment/Server Sync阶段一部署-2026-09-10.md @@ -0,0 +1,47 @@ +# OpenNexus Server Sync 阶段一部署记录 + +部署时间:2026-09-10 + +部署类型:阶段一明文 HTTP 测试 + +代码提交:`d703ab64e3f483dde9e5153c55b986d358f4e37d` + +## 访问入口 + +- 控制台:`http://160.202.254.170:18080/` +- 健康检查:`http://160.202.254.170:18080/health` +- 依赖就绪:`http://160.202.254.170:18080/ready` +- 协议握手:`http://160.202.254.170:18080/sync/v1/handshake?protocol=1` + +该环境按测试要求直接监听 `0.0.0.0:18080`,没有修改现有 Nginx,也没有配置 +HTTPS。正式部署仍须恢复本机监听并由 TLS 反向代理提供外部入口。 + +## 服务组成 + +Docker Compose 项目名为 `opennexus-stage1`,包含 PostgreSQL 17.6、MinIO、 +一次性初始化任务和两个 Uvicorn worker 的 Sync 服务。长期服务使用独立的 +Bucket 限权账号,不使用 MinIO 管理员身份。数据库、对象存储和暂存区均使用 +独立具名卷。 + +目标机可用内存较少,因此阶段一部署为容器设置以下上限: + +| 服务 | 内存上限 | CPU 上限 | +| --- | ---: | ---: | +| PostgreSQL | 256 MiB | 1 | +| MinIO | 320 MiB | 1 | +| 初始化任务 | 256 MiB | 1 | +| Sync | 384 MiB | 1 | + +## 已执行验证 + +- `/health` 返回 `status=ok`。 +- `/ready` 返回 `status=ready`。 +- 握手返回协议版本 1、100 MiB 对象上限和 1 MiB 分块大小。 +- 根路径返回构建后的 Vue 3 + TypeScript Sync Console。 +- 从部署机外部完成登录、创建并列出 Vault、退出登录的公开 API 闭环。 +- 验证结束后删除临时 Vault、设备与会话,仅保留阶段一演示账号。 +- 验证时 Sync、PostgreSQL、MinIO 的内存用量分别约为 214 MiB、27 MiB、 + 135 MiB,均低于容器上限。 + +部署凭据只保存在目标机权限受限的 `.env` 中,本文和验收日志不记录口令、 +访问令牌、数据库密码或对象存储密钥。 diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index 59b28a8..3aaeacf 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -953,3 +953,20 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 提交 `ba7414e` 后构建 release Host 与 1523 文件的 PyInstaller Core。便携交付目录为 `H:\OpenNexus-demo\OpenNexus-0.3.0-alpha.1-portable-ba7414e\`,总计 274.3 MiB;Core 全部文件与 SHA-256 清单一致,`OpenNexus.exe` 摘要为 `E6136C61CAA910CD0006EF9FD94955B47FCF561ABF3FB217097AE36317394023`。实际启动后 Host 窗口保持响应,Core 子进程从便携目录运行。 - 构建完成后删除工作树中的 `.build`、Rust `target`、前端 `dist`、Sync Console `node_modules` 和 TypeScript 构建缓存,G 盘剩余空间回升到 49.62 GiB。用户 Vault 的现有修改未纳入提交、未移动也未删除。 - 测试地址 `http://yui.kronecker.cc:18080` 当前仍返回空响应,SSH 连接在认证前由对端关闭,因此本次未能把控制台发布到该测试端口;本地 Demo 与可部署源码、静态产物均已完成。其余 12 项长时生产验收继续按用户要求暂缓。 + + +## 增量:Server Sync 阶段一远端部署 + +- 使用新的测试服务器完成 Server Sync 全栈部署,公开入口为 `http://160.202.254.170:18080/`。按阶段一要求直接监听 HTTP,没有修改 Nginx 或配置 HTTPS;根路径发布 Vue 3 + TypeScript Sync Console。 +- Docker Compose 运行 PostgreSQL 17.6、MinIO、一次性初始化任务和两个 Uvicorn worker。Compose 新增可配置的 `SYNC_BIND_ADDRESS` 与 `SYNC_PORT`,默认仍只绑定 `127.0.0.1:8080`,只有测试部署显式使用 `0.0.0.0:18080`。 +- 外部验证 `/health`、`/ready`、协议握手、登录、创建与列出 Vault、退出登录全部成功。验证后已删除临时 Vault、设备和会话,只保留阶段一演示账号;数据库计数为 1 个用户、0 个 Vault、0 个会话。 +- 目标机内存较小,部署为 PostgreSQL、MinIO、初始化任务和 Sync 分别设置 256、320、256、384 MiB 上限。验证时三项长期服务约使用 27、135、214 MiB。部署说明见 `docs/deployment/Server Sync阶段一部署-2026-09-10.md`,不包含口令或令牌。 + + +## 增量:C-04 扩展资源与工具期限生产验收 + +- 为每个原生扩展实例增加 256 MiB scratch 上限。Host 每 100 ms 从实例专用物理目录重新遍历,拒绝重解析点、符号链接、非普通对象、超过 10000 个条目或超过配额;触发后返回 `EXTENSION_RESOURCE_SCRATCH_EXCEEDED` 并终止整个 Job 进程树。 +- 发现 AppContainer 会把传入的物理 `Packages/<实例>/AC` 路径再次虚拟化,导致子进程 TEMP 出现双重 `AC/Packages/.../AC` 路径。现在启动数据为子进程提供 LocalAppData 下的逻辑路径,Host 继续持有并监控真实 scratch 路径,同时通过实例 SID 授予目录继承写权限。 +- C-04 driver 固定执行四个精确 Rust Host oracle。真实后台 MCP 分别触发 CPU、512 MiB 内存、16 进程和 256 MiB scratch 上限,全部返回对应错误、清空进程树并能启动替代实例;scratch 超限本轮约 92 ms 可见。真实 60 秒工具期限在 60.015 秒左右回收两级进程树,期限前后 Host 均成功保存并重开本地笔记。文件 broker 第 33 个同秒请求返回 `EXTENSION_BROKER_RATE_LIMITED`。 +- 直接 driver 结果为 `PASSED`,四组断言全部通过。严格 Clippy `-D warnings` 通过;desktop Rust 全量回归 146 通过、0 失败、14 ignored,总耗时 744 秒;runner 7 项、Server Sync 30 项、前端类型检查和 542 项前端测试均通过。前端测试出现一次对未启动 `localhost:3000` 的预期连接拒绝诊断,但测试进程仍以 104 个文件、542 项全部通过结束。 +- 测试依赖和断链目录在验证后从 G/H 盘清理,G 盘可用空间约 49.95 GiB。C-04 可记为通过,正式验收累计 19/30;A-01/A-04、C-01/C-02、D-02/D-04 与 E-01~E-05 尚未通过,不能据此声明完整第三阶段生产验收完成。 diff --git a/frontend/scripts/benchmark-theme-check.cjs b/frontend/scripts/benchmark-theme-check.cjs index 3739f4b..8b49417 100644 --- a/frontend/scripts/benchmark-theme-check.cjs +++ b/frontend/scripts/benchmark-theme-check.cjs @@ -1,4 +1,4 @@ -// Visual fixtures only: all API traffic is intercepted; no provider calls or user data. +// 仅使用视觉夹具:拦截全部 API 流量,不调用提供商,也不读取用户数据。 const {chromium}=require('playwright');const fs=require('node:fs/promises');const path=require('node:path'); (async()=>{ const output=path.resolve(process.argv[2]||'.local-plans/phase2-review/themes');await fs.mkdir(output,{recursive:true}); diff --git a/frontend/scripts/phase2-browser-check.cjs b/frontend/scripts/phase2-browser-check.cjs index 20c4871..e312a24 100644 --- a/frontend/scripts/phase2-browser-check.cjs +++ b/frontend/scripts/phase2-browser-check.cjs @@ -1,4 +1,4 @@ -// Run with NODE_PATH pointing to the bundled Playwright package, or a local install. +// 运行时让 NODE_PATH 指向随包提供的 Playwright,或使用本地安装。 const { chromium } = require('playwright') const fs = require('node:fs/promises') const path = require('node:path') diff --git a/frontend/src-tauri/src/extension_commands.rs b/frontend/src-tauri/src/extension_commands.rs index 04d5a6d..34b4ac5 100644 --- a/frontend/src-tauri/src/extension_commands.rs +++ b/frontend/src-tauri/src/extension_commands.rs @@ -121,7 +121,7 @@ pub async fn extension_install_preview( let workspace = host.workspace.clone(); let extensions = host.extensions.clone(); tauri::async_runtime::spawn_blocking(move || { - // Keep the workspace binding stable until this preview finishes. + // 在预览完成前保持工作区绑定不变。 let workspace = workspace.lock().map_err(|_| "HOST_BUSY")?; if workspace.as_ref().ok_or("VAULT_NOT_OPEN")?.vault_id != request.vault_id { return Err("VAULT_CHANGED".into()); diff --git a/frontend/src-tauri/src/extension_container.rs b/frontend/src-tauri/src/extension_container.rs index 95499ec..9019c5b 100644 --- a/frontend/src-tauri/src/extension_container.rs +++ b/frontend/src-tauri/src/extension_container.rs @@ -60,13 +60,43 @@ impl Profile { /// 并须在整个启动期间持有已验证的包句柄。这里不使用递归继承,每个目录和文件都要分别检查、授权。 /// 此操作只会添加一条 ACE,不会清理已有权限。 pub fn grant_package_read_execute(&self, object: &std::fs::File) -> Result<()> { - self.update_package_access(object, false) + use windows_sys::Win32::Storage::FileSystem::{FILE_GENERIC_EXECUTE, FILE_GENERIC_READ}; + self.update_access( + object, + false, + FILE_GENERIC_READ | FILE_GENERIC_EXECUTE, + 0, + true, + ) + } + /// 授予当前实例修改其专用 scratch 目录及新建子对象的权限。 + pub fn grant_scratch_modify(&self, object: &std::fs::File) -> Result<()> { + use windows_sys::Win32::{ + Security::SUB_CONTAINERS_AND_OBJECTS_INHERIT, + Storage::FileSystem::{ + DELETE, FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, FILE_GENERIC_WRITE, + }, + }; + self.update_access( + object, + false, + FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, + SUB_CONTAINERS_AND_OBJECTS_INHERIT, + false, + ) } /// 使用最初持有的对象句柄,仅移除这个新实例对应的允许 ACE;其他安全主体的 ACL 保持不变。 pub fn revoke_package_access(&self, object: &std::fs::File) -> Result<()> { - self.update_package_access(object, true) + self.update_access(object, true, 0, 0, false) } - fn update_package_access(&self, object: &std::fs::File, revoke: bool) -> Result<()> { + fn update_access( + &self, + object: &std::fs::File, + revoke: bool, + permissions: u32, + inheritance: u32, + reject_hardlinks: bool, + ) -> Result<()> { // 跨并发实例序列化 Host 读/合并/写操作。 let _lock = PACKAGE_ACL_LOCK .lock() @@ -77,7 +107,7 @@ impl Profile { Security::{Authorization::*, DACL_SECURITY_INFORMATION}, Storage::FileSystem::{ GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, - FILE_ATTRIBUTE_REPARSE_POINT, FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, + FILE_ATTRIBUTE_REPARSE_POINT, }, }; struct LocalAllocation(*mut core::ffi::c_void); @@ -99,7 +129,7 @@ impl Profile { { return Err(HostError::new("EXTENSION_CONTAINER_ACL_OBJECT_INVALID")); } - if metadata.is_file() && !revoke { + if metadata.is_file() && !revoke && reject_hardlinks { let mut info = BY_HANDLE_FILE_INFORMATION::default(); if unsafe { GetFileInformationByHandle(object.as_raw_handle(), &mut info) } == 0 || info.nNumberOfLinks != 1 @@ -127,9 +157,9 @@ impl Profile { return Err(HostError::new("EXTENSION_CONTAINER_ACL_FAILED")); } let entry = EXPLICIT_ACCESS_W { - grfAccessPermissions: FILE_GENERIC_READ | FILE_GENERIC_EXECUTE, + grfAccessPermissions: permissions, grfAccessMode: if revoke { REVOKE_ACCESS } else { GRANT_ACCESS }, - grfInheritance: 0, + grfInheritance: inheritance, Trustee: TRUSTEE_W { TrusteeForm: TRUSTEE_IS_SID, TrusteeType: TRUSTEE_IS_UNKNOWN, @@ -973,7 +1003,7 @@ mod tests { mcp_modes.push("mcp_deadline"); } if resources { - mcp_modes.extend(["mcp_cpu", "mcp_memory", "mcp_processes"]); + mcp_modes.extend(["mcp_cpu", "mcp_memory", "mcp_processes", "mcp_scratch"]); } for mode in mcp_modes { use std::sync::{ @@ -1093,10 +1123,11 @@ mod tests { ); assert!(session.take_tools_changed()); } - "mcp_cpu" | "mcp_memory" | "mcp_processes" => { + "mcp_cpu" | "mcp_memory" | "mcp_processes" | "mcp_scratch" => { let expected = match mode { "mcp_memory" => "EXTENSION_RESOURCE_MEMORY_EXCEEDED", "mcp_processes" => "EXTENSION_RESOURCE_PROCESSES_EXCEEDED", + "mcp_scratch" => "EXTENSION_RESOURCE_SCRATCH_EXCEEDED", _ => "EXTENSION_RESOURCE_CPU_EXCEEDED", }; assert_eq!(result.unwrap_err().code, expected); diff --git a/frontend/src-tauri/src/extension_instance.rs b/frontend/src-tauri/src/extension_instance.rs index c64cc5c..4d79da2 100644 --- a/frontend/src-tauri/src/extension_instance.rs +++ b/frontend/src-tauri/src/extension_instance.rs @@ -152,7 +152,7 @@ pub struct Ticket { cancel: Arc, } impl Ticket { - /// Background wait only; dropping a ticket cancels its queued/in-flight work. + /// 仅供后台等待;丢弃票据会取消排队中或执行中的工作。 pub fn wait(self, timeout: Duration) -> Result { if timeout > Duration::from_secs(65) { return Err(HostError::new("EXTENSION_INSTANCE_WAIT_INVALID")); @@ -204,8 +204,8 @@ impl Endpoint { request, }) } - /// Host route only: the user must have approved the exact saved review. - /// Confirmation and consumption happen together on the instance thread. + /// 仅供 Host 路由使用:用户必须批准完全一致的已保存审查。 + /// 确认和消费在实例线程上同步发生。 pub fn invoke_confirmed(&self, review_id: String) -> Result> { if uuid::Uuid::parse_str(&review_id).is_err() || review_id.len() != 36 { return Err(HostError::new("EXTENSION_CALL_REVIEW_UNKNOWN")); @@ -252,9 +252,9 @@ pub struct Registry { } impl Registry { /// # Safety - /// The caller must establish all sandbox limits and current install/user - /// authorization. before_resume must recheck live trust/active installation. - /// This API is not exposed to renderer/Core and does not enable extensions. + /// 调用方必须建立全部沙箱限制以及当前安装和用户授权。 + /// before_resume 必须重新检查实时信任与活动安装状态。 + /// 此 API 不向 renderer/Core 暴露,也不会自行启用扩展。 pub unsafe fn start(&mut self, spec: LaunchSpec) -> Result { self.reap(); spec.authority @@ -299,7 +299,7 @@ impl Registry { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { run(spec, &control, receiver) })); - // All native stack owners have dropped before publishing terminal state. + // 发布终止状态前,所有原生调用栈所有者均已销毁。 let error = match result { Ok(Ok(())) => None, Ok(Err(error)) @@ -335,8 +335,7 @@ impl Registry { ); Ok(endpoint) } - /// Reap only threads confirmed finished, so an old generation cannot overlap - /// a replacement merely because stop was requested or status was changed. + /// 只回收已确认结束的线程,不能仅因请求停止或状态改变就让旧代实例与替代实例重叠。 pub fn reap(&mut self) { let done: Vec<_> = self .entries @@ -439,7 +438,7 @@ fn run_with_access( if control.stop.load(Ordering::Acquire) { return Ok(()); } - // Safety obligation belongs to Registry::start's caller, rechecked above. + // 安全义务属于 Registry::start 的调用方,并已在上方重新检查。 let running = unsafe { suspended.resume()? }; #[cfg(test)] { @@ -736,6 +735,7 @@ mod tests { ("mcp_cpu", "EXTENSION_RESOURCE_CPU_EXCEEDED"), ("mcp_memory", "EXTENSION_RESOURCE_MEMORY_EXCEEDED"), ("mcp_processes", "EXTENSION_RESOURCE_PROCESSES_EXCEEDED"), + ("mcp_scratch", "EXTENSION_RESOURCE_SCRATCH_EXCEEDED"), ] } else { Vec::new() diff --git a/frontend/src-tauri/src/extension_io.rs b/frontend/src-tauri/src/extension_io.rs index 720cc29..5e84cff 100644 --- a/frontend/src-tauri/src/extension_io.rs +++ b/frontend/src-tauri/src/extension_io.rs @@ -84,9 +84,8 @@ impl Drop for Worker { fn drop(&mut self) { self.state.stopped.store(true, Ordering::Release); if let Some(thread) = self.thread.take() { - // Cancellation is not sticky: retry to cover the interval between - // the worker checking stopped and actually entering Read/WriteFile. - // These workers only issue anonymous-pipe IO, never arbitrary device IO. + // 取消状态不会自动作用于后续调用,因此需要重试,以覆盖工作线程检查停止状态到实际进入 + // ReadFile/WriteFile 之间的窗口。这些线程只操作匿名管道,不访问任意设备。 while !thread.is_finished() { unsafe { CancelSynchronousIo(thread.as_raw_handle()); @@ -185,8 +184,7 @@ impl Pump { &pump.state, "extension-stderr", move |state| { - // Drain without persisting possible secrets. Diagnostic retention - // needs an explicit redaction policy before it can be enabled. + // 清空数据但不持久化可能的秘密;只有明确配置脱敏策略后才能保留诊断信息。 let mut buffer = [0; 4096]; let mut total = 0; while !state.stopped.load(Ordering::Acquire) { @@ -206,12 +204,11 @@ impl Pump { )?); Ok(pump) } - /// Nonblocking admission; at most one pending write plus one in progress. + /// 非阻塞接收;最多允许一个等待写入和一个正在写入的请求。 pub fn send(&self, frame: Vec) -> Result<()> { self.send_wait(frame, Duration::ZERO) } - /// Bounded admission for serial protocol notifications immediately followed - /// by a request; retries retain the same frame, without allocating copies. + /// 为协议通知紧接请求的串行场景提供有界接收;重试保留同一帧,不分配副本。 pub(crate) fn send_wait(&self, mut frame: Vec, timeout: Duration) -> Result<()> { self.state.check()?; if timeout > Duration::from_secs(1) { @@ -275,7 +272,7 @@ impl Pump { pub fn check(&self) -> Result<()> { self.state.check() } - /// Stop the process group first, then cancel and join every pipe worker. + /// 先停止进程组,再取消并等待所有管道工作线程。 pub fn shutdown(mut self) -> Result<()> { let result = self.state.job.terminate(); self.state.stopped.store(true, Ordering::Release); @@ -364,8 +361,7 @@ mod tests { let started = Instant::now(); pump.shutdown().unwrap(); assert!(started.elapsed() < Duration::from_secs(2)); - // The peer handles remained open throughout shutdown. No process - // exit or peer EOF is available to mask broken IO cancellation. + // 关闭期间对端句柄始终保持打开,不能用进程退出或对端 EOF 掩盖失效的 IO 取消。 drop(peers); } } diff --git a/frontend/src-tauri/src/extension_job.rs b/frontend/src-tauri/src/extension_job.rs index 3823d25..4262a0a 100644 --- a/frontend/src-tauri/src/extension_job.rs +++ b/frontend/src-tauri/src/extension_job.rs @@ -3,6 +3,7 @@ use crate::workspace::{HostError, Result}; use std::{ mem::size_of, os::windows::io::{AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle}, + path::{Path, PathBuf}, }; use windows_sys::Win32::System::{ JobObjects::*, @@ -26,9 +27,15 @@ impl Job { }) } pub fn new() -> Result { - Self::with_process_limit(16) + Self::with_process_limit(16, None) } - fn with_process_limit(processes: u32) -> Result { + pub fn with_scratch(scratch: &Path) -> Result { + if !scratch.is_absolute() { + return Err(HostError::new("EXTENSION_RESOURCE_UNAVAILABLE")); + } + Self::with_process_limit(16, Some(scratch.to_owned())) + } + fn with_process_limit(processes: u32, scratch: Option) -> Result { let raw = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) }; if raw.is_null() { return Err(HostError::new("EXTENSION_RESOURCE_UNAVAILABLE")); @@ -63,7 +70,7 @@ impl Job { }, }; job.set(JobObjectCpuRateControlInformation, &cpu)?; - job._monitor = Some(ResourceMonitor::arm(&job)?); + job._monitor = Some(ResourceMonitor::arm(&job, scratch)?); Ok(job) } pub fn check_resources(&self) -> Result<()> { @@ -74,6 +81,7 @@ impl Job { 3 => Err(HostError::new("EXTENSION_RESOURCE_TERMINATE_FAILED")), 4 => Err(HostError::new("EXTENSION_RESOURCE_MEMORY_EXCEEDED")), 5 => Err(HostError::new("EXTENSION_RESOURCE_PROCESSES_EXCEEDED")), + 6 => Err(HostError::new("EXTENSION_RESOURCE_SCRATCH_EXCEEDED")), _ => Err(HostError::new("EXTENSION_RESOURCE_MONITOR_FAILED")), } } @@ -93,7 +101,7 @@ impl Job { } /// 在任何扩展指令执行之前附加。没有启用任何分离标志。 /// - /// # 安全性 + /// # Safety /// 调用方必须拥有尚未恢复执行的 CREATE_SUSPENDED 进程,并在出现任何错误时终止该进程。 /// 只有 AppContainer、句柄与权限检查全部通过后,才能恢复执行。 pub unsafe fn assign_suspended(&self, process: BorrowedHandle<'_>) -> Result<()> { @@ -129,18 +137,18 @@ impl Job { } } -/// The Windows notification uses a ten-second window and ToleranceHigh (60% -/// over budget). This is not a measurement of ten uninterrupted busy seconds. -/// Only the original Job owns this monitor; observation/deadline clones do not. +/// Windows 通知使用十秒窗口和 ToleranceHigh(允许超出预算 60%),不表示已经 +/// 测得连续十秒满载。只有原始 Job 拥有监视器,观察和期限副本不拥有。 const JOB_MEMORY_LIMIT: u32 = 10; // JOB_OBJECT_MSG_JOB_MEMORY_LIMIT const JOB_PROCESS_LIMIT: u32 = 3; // JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT const JOB_NOTIFICATION_LIMIT: u32 = 11; // JOB_OBJECT_MSG_NOTIFICATION_LIMIT (Windows SDK) +const SCRATCH_LIMIT_BYTES: u64 = 256 * 1024 * 1024; struct ResourceMonitor { stop: std::sync::Arc, worker: Option>, } impl ResourceMonitor { - fn arm(job: &Job) -> Result { + fn arm(job: &Job, scratch: Option) -> Result { use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -178,10 +186,16 @@ impl ResourceMonitor { let worker = std::thread::Builder::new() .name("extension-resources".into()) .spawn(move || { - // All exits, including a caught panic or completion-port failure, - // terminate the tree while this worker still owns a Job handle. + // 所有退出路径(包括捕获到的 panic 或完成端口错误)都会在本线程 + // 仍持有 Job 句柄时终止整个进程树。 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { while !thread_stop.load(Ordering::Acquire) { + if scratch + .as_deref() + .is_some_and(|path| scratch_usage(path).is_err()) + { + return 6; + } let (mut code, mut key, mut pointer) = (0, 0, std::ptr::null_mut()); let ok = unsafe { GetQueuedCompletionStatus( @@ -203,8 +217,8 @@ impl ResourceMonitor { if key != 1 { return 2; } - // These hard-limit notifications are best effort on Windows; - // the kernel still enforces the configured allocation caps. + // 这些硬上限通知在 Windows 上是尽力投递;即使通知丢失, + // 内核仍执行已配置的分配上限。 if code == JOB_MEMORY_LIMIT { return 4; } @@ -246,6 +260,48 @@ impl ResourceMonitor { }) } } + +fn scratch_usage(root: &Path) -> Result { + if !root.is_absolute() { + return Err(HostError::new("EXTENSION_RESOURCE_SCRATCH_EXCEEDED")); + } + let mut total = 0u64; + let mut entries = 0usize; + let mut pending = vec![root.to_owned()]; + while let Some(path) = pending.pop() { + let metadata = std::fs::symlink_metadata(&path) + .map_err(|_| HostError::new("EXTENSION_RESOURCE_SCRATCH_EXCEEDED"))?; + use std::os::windows::fs::MetadataExt; + if metadata.file_attributes() & 0x400 != 0 || metadata.file_type().is_symlink() { + return Err(HostError::new("EXTENSION_RESOURCE_SCRATCH_EXCEEDED")); + } + entries += 1; + if entries > 10_000 { + return Err(HostError::new("EXTENSION_RESOURCE_SCRATCH_EXCEEDED")); + } + if metadata.is_dir() { + for child in std::fs::read_dir(&path) + .map_err(|_| HostError::new("EXTENSION_RESOURCE_SCRATCH_EXCEEDED"))? + { + pending.push( + child + .map_err(|_| HostError::new("EXTENSION_RESOURCE_SCRATCH_EXCEEDED"))? + .path(), + ); + } + } else if metadata.is_file() { + total = total + .checked_add(metadata.len()) + .ok_or_else(|| HostError::new("EXTENSION_RESOURCE_SCRATCH_EXCEEDED"))?; + if total > SCRATCH_LIMIT_BYTES { + return Err(HostError::new("EXTENSION_RESOURCE_SCRATCH_EXCEEDED")); + } + } else { + return Err(HostError::new("EXTENSION_RESOURCE_SCRATCH_EXCEEDED")); + } + } + Ok(total) +} impl Drop for ResourceMonitor { fn drop(&mut self) { self.stop.store(true, std::sync::atomic::Ordering::Release); @@ -440,8 +496,7 @@ mod tests { ); idle_job.check_resources().unwrap(); assert!(idle_job.active_processes().unwrap() > 0); - // An observation handle must not keep the monitor alive after its owner - // is dropped, or keep the idle process running indefinitely. + // 观察句柄不能在所有者销毁后继续维持监视器,也不能让空闲进程无限运行。 let observation = idle_job.clone_for_deadline().unwrap(); let stop = std::time::Instant::now(); drop(idle_job); @@ -530,7 +585,7 @@ mod tests { } #[test] fn actual_suspended_process_assignment_limits_and_close_cleanup() { - let job = Job::with_process_limit(1).unwrap(); + let job = Job::with_process_limit(1, None).unwrap(); let first = worker(); unsafe { job.assign_suspended(first.process.as_handle()).unwrap(); @@ -538,8 +593,8 @@ mod tests { assert_eq!(job.active_processes().unwrap(), 1); let second = worker(); assert!(unsafe { job.assign_suspended(second.process.as_handle()) }.is_err()); - // Both processes were still suspended. The attempted limit violation - // now revokes the entire first job, rather than leaving it runnable. + // 两个进程仍处于暂停状态。触发上限后撤销整个首个 Job,不能把旧进程 + // 留在可运行状态。 drop(second); assert_resource_cleanup(&job, "EXTENSION_RESOURCE_PROCESSES_EXCEEDED"); assert_eq!( diff --git a/frontend/src-tauri/src/extension_launch_authorization.rs b/frontend/src-tauri/src/extension_launch_authorization.rs index 311ff95..b217f9a 100644 --- a/frontend/src-tauri/src/extension_launch_authorization.rs +++ b/frontend/src-tauri/src/extension_launch_authorization.rs @@ -80,7 +80,8 @@ impl PreparedLaunch { } } impl<'a> LeasedSuspended<'a> { - /// # Safety Live 信任、主动安装、代理和所有沙箱资源策略要求也必须满足。租约不规定这些条件。 + /// # Safety + /// 实时信任、主动安装、代理和所有沙箱资源策略要求也必须满足。租约不规定这些条件。 pub unsafe fn resume(self) -> Result> { unsafe { self.process.resume_with_lease(self.lease, self.identity) } } @@ -93,8 +94,7 @@ impl Drop for EnvironmentValues { } } } -/// Credential setup and execution must use the same derived identity. The -/// reference is an opaque ID inside this package's domain, never a caller scope. +/// 凭据设置和执行必须使用同一派生身份。引用是该包域内的不透明 ID,不能充当调用方作用域。 pub fn credential_id(claims: &Claims, reference: &str) -> Result { if reference.is_empty() || reference.len() > 128 @@ -132,8 +132,7 @@ pub fn credential_id(claims: &Claims, reference: &str) -> Result { }) } impl Context<'_> { - /// Capture epochs before resolving credentials; never adopt a newer lock - /// generation for launch bytes prepared under an earlier session. + /// 解析凭据前捕获代际;为旧会话准备的启动数据不得采用较新的锁代际。 pub fn prepare( &self, authority: &Authority, @@ -205,7 +204,7 @@ impl Context<'_> { .ok_or_else(|| HostError::new("EXTENSION_CREDENTIAL_MISSING"))?; let value = std::str::from_utf8(&value) .map_err(|_| HostError::new("EXTENSION_CREDENTIAL_ENCODING_INVALID"))?; - // Insert directly into the cleaning owner, never an error or log. + // 直接写入负责清零的所有者,绝不写入错误或日志。 values.0.insert(name.clone(), value.to_owned()); } } diff --git a/frontend/src-tauri/src/extension_launch_data.rs b/frontend/src-tauri/src/extension_launch_data.rs index aabb844..07fd41c 100644 --- a/frontend/src-tauri/src/extension_launch_data.rs +++ b/frontend/src-tauri/src/extension_launch_data.rs @@ -1,11 +1,16 @@ //! 本机 ​​argv/环境编码。这不会授权或启动进程。 use crate::workspace::{HostError, Result}; -use std::{collections::BTreeMap, os::windows::ffi::OsStrExt, path::Path}; +use std::{ + collections::BTreeMap, + os::windows::ffi::OsStrExt, + path::{Path, PathBuf}, +}; use zeroize::Zeroize; pub struct LaunchData { command: Vec, environment: Vec, + scratch: PathBuf, } impl Drop for LaunchData { fn drop(&mut self) { @@ -40,6 +45,7 @@ impl LaunchData { let mut result = Self { command: Vec::with_capacity(32767), environment: Vec::with_capacity(32767), + scratch: scratch.to_owned(), }; result.command.push(34); result.command.extend(executable); @@ -91,13 +97,17 @@ impl LaunchData { if result.command.len() > 32767 { return Err(bad()); } + // AppContainer 会把用户 LocalAppData 下的逻辑路径重定向到配置文件的 AC 目录。 + // 直接把物理 AC 路径交给子进程会被再次重定向,形成 AC\Packages\...\AC 的错误路径。 + let (visible_local_app_data, visible_scratch) = + visible_container_paths(local_app_data, scratch); // ASCII 名称给出确定性的 Windows 不区分大小写的顺序。值在编码之前一直是借用的,因此不存在秘密克隆。 let mut fields: BTreeMap = BTreeMap::new(); for (name, path) in [ ("SYSTEMROOT", system_root), - ("LOCALAPPDATA", local_app_data), - ("TEMP", scratch), - ("TMP", scratch), + ("LOCALAPPDATA", visible_local_app_data.as_path()), + ("TEMP", visible_scratch.as_path()), + ("TMP", visible_scratch.as_path()), ] { if !path.is_absolute() { return Err(bad()); @@ -146,6 +156,29 @@ impl LaunchData { pub fn environment(&self) -> &[u16] { &self.environment } + pub(crate) fn scratch(&self) -> &Path { + &self.scratch + } +} + +fn visible_container_paths(local_app_data: &Path, scratch: &Path) -> (PathBuf, PathBuf) { + let is_ac = local_app_data + .file_name() + .is_some_and(|name| name.eq_ignore_ascii_case("AC")); + let is_package = local_app_data + .parent() + .and_then(Path::parent) + .and_then(Path::file_name) + .is_some_and(|name| name.eq_ignore_ascii_case("Packages")); + if is_ac && is_package { + if let (Some(base), Ok(relative)) = ( + local_app_data.ancestors().nth(3), + scratch.strip_prefix(local_app_data), + ) { + return (base.to_owned(), base.join(relative)); + } + } + (local_app_data.to_owned(), scratch.to_owned()) } #[cfg(test)] @@ -220,4 +253,13 @@ mod tests { assert!(data.environment().contains(&0xd800)); assert!(data.environment().ends_with(&[0, 0])); } + #[test] + fn converts_physical_appcontainer_paths_to_child_visible_paths() { + let (local, scratch) = visible_container_paths( + Path::new(r"C:\Users\tester\AppData\Local\Packages\OpenNexus.sandbox.id\AC"), + Path::new(r"C:\Users\tester\AppData\Local\Packages\OpenNexus.sandbox.id\AC\Temp"), + ); + assert_eq!(local, Path::new(r"C:\Users\tester\AppData\Local")); + assert_eq!(scratch, Path::new(r"C:\Users\tester\AppData\Local\Temp")); + } } diff --git a/frontend/src-tauri/src/extension_mcp.rs b/frontend/src-tauri/src/extension_mcp.rs index 8642977..1f5cc20 100644 --- a/frontend/src-tauri/src/extension_mcp.rs +++ b/frontend/src-tauri/src/extension_mcp.rs @@ -78,7 +78,7 @@ fn decode(bytes: &[u8]) -> Result { { return Err(invalid()); } - // Error data is never propagated or logged; it may contain secrets. + // 错误数据可能包含秘密,因此绝不传播或记录。 let _ = &error.data; } Ok(value) @@ -217,8 +217,8 @@ impl<'a, 'p> Session<'a, 'p> { .tool(name)?; self.calls.review(&tool, arguments) } - /// Only invoke from an authenticated Host route after the user approved this - /// exact review. This method is not registered as a renderer/Core command. + /// 仅在用户批准完全一致的审查后由已认证 Host 路由调用。 + /// 此方法不会注册为 renderer/Core 命令。 pub fn confirm_call( &mut self, review_id: &str, @@ -312,8 +312,7 @@ impl<'a, 'p> Session<'a, 'p> { } Ok(true) } - /// Apply already-received notifications before selecting a cached contract. - /// The eventual registry loop must also call this while the instance is idle. + /// 选择缓存契约前应用已收到的通知;最终注册表循环在实例空闲时也必须调用此方法。 pub fn drain_pending(&mut self) -> Result<()> { let result = (|| { for _ in 0..128 { @@ -373,7 +372,7 @@ impl<'a, 'p> Session<'a, 'p> { self.process.check_authorization()?; deadline.check()?; if cancel.load(Ordering::Acquire) || started.elapsed() >= budget { - // initialize cannot be cancelled at the protocol level. + // initialize 无法在协议层取消。 if method != "initialize" { let _ = self.send(json!({"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":id}})); } diff --git a/frontend/src-tauri/src/extension_pinned.rs b/frontend/src-tauri/src/extension_pinned.rs index c1a2b1a..76c9e0d 100644 --- a/frontend/src-tauri/src/extension_pinned.rs +++ b/frontend/src-tauri/src/extension_pinned.rs @@ -35,8 +35,8 @@ impl Drop for PackageAccess<'_> { } } } -/// The package borrow and all ancestor handles must outlive the process using -/// this path. Only files present in the verified package can produce this guard. +/// 包借用和所有祖先句柄的生命周期必须长于使用此路径的进程。 +/// 只有已验证包中存在的文件才能生成此守卫。 pub struct BoundEntry<'a> { name: String, path: std::path::PathBuf, diff --git a/frontend/src-tauri/src/extension_process.rs b/frontend/src-tauri/src/extension_process.rs index 88c738b..5859807 100644 --- a/frontend/src-tauri/src/extension_process.rs +++ b/frontend/src-tauri/src/extension_process.rs @@ -96,9 +96,8 @@ pub struct Running<'a> { identity: Option, } impl<'a> Suspended<'a> { - /// Creates hidden, with no inherited handles and an explicit environment and - /// current directory. The profile borrow prevents cleanup while this owner - /// exists. This API never resumes extension instructions. + /// 使用显式环境和工作目录创建隐藏进程,不继承任意句柄。Profile 借用会在 + /// 所有者存活期间阻止清理;此 API 永远不会恢复扩展指令。 pub fn create(profile: &'a Profile, executable: &Path, data: LaunchData) -> Result { Self::create_inner(profile, executable, data, None) } @@ -118,6 +117,28 @@ impl<'a> Suspended<'a> { } let executable: Vec<_> = executable.into_iter().chain(Some(0)).collect(); let folder = profile.folder()?; + std::fs::create_dir_all(data.scratch()) + .map_err(|_| HostError::new("EXTENSION_RESOURCE_UNAVAILABLE"))?; + let scratch_metadata = std::fs::symlink_metadata(data.scratch()) + .map_err(|_| HostError::new("EXTENSION_RESOURCE_UNAVAILABLE"))?; + use std::os::windows::fs::MetadataExt; + if !scratch_metadata.is_dir() + || scratch_metadata.file_attributes() & 0x400 != 0 + || scratch_metadata.file_type().is_symlink() + { + return Err(HostError::new("EXTENSION_RESOURCE_UNAVAILABLE")); + } + use std::os::windows::fs::OpenOptionsExt; + use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, READ_CONTROL, WRITE_DAC, + }; + let scratch_handle = std::fs::OpenOptions::new() + .access_mode(READ_CONTROL | WRITE_DAC) + .share_mode(1 | 2 | 4) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT) + .open(data.scratch()) + .map_err(|_| HostError::new("EXTENSION_RESOURCE_UNAVAILABLE"))?; + profile.grant_scratch_modify(&scratch_handle)?; let directory: Vec = folder.as_os_str().encode_wide().chain(Some(0)).collect(); let mut attributes = Attributes::new(if io.is_some() { 2 } else { 1 })?; let caps = SECURITY_CAPABILITIES { @@ -140,12 +161,12 @@ impl<'a> Suspended<'a> { { return Err(HostError::new("EXTENSION_PROCESS_ATTRIBUTES_FAILED")); } - let job = Job::new()?; + let job = Job::with_scratch(data.scratch())?; let mut startup = STARTUPINFOEXW::default(); startup.StartupInfo.cb = size_of::() as u32; startup.lpAttributeList = attributes.buffer.as_mut_ptr().cast(); - // Keep both the handle array and the owning pipe ends alive across - // CreateProcessW. No arbitrary inheritable Host handle is admitted. + // 在 CreateProcessW 返回前同时保留句柄数组和管道所有者,不允许任意 + // Host 可继承句柄进入扩展进程。 let io = io .map(crate::extension_stdio::ChildIo::inherit) .transpose()?; @@ -209,8 +230,7 @@ impl<'a> Suspended<'a> { verify_identity(&process.handles, profile)?; Ok(Self(process)) } - /// Package launch path: retain the entry guard (and its package/ancestor - /// handles) for the entire suspended/running process lifetime. + /// 包启动路径在暂停和运行的整个生命周期内保留入口守卫及包祖先句柄。 #[cfg(feature = "desktop")] pub fn create_bound( profile: &'a Profile, @@ -234,10 +254,9 @@ impl<'a> Suspended<'a> { Ok((value, host)) } /// # Safety - /// Caller must hold the verified package/entry handles and revalidate the - /// current execution permit, trust, Vault binding, environment declarations, - /// broker and all resource policy requirements immediately before this call. - /// None of those authorization checks is supplied by this low-level module. + /// 调用方必须持有已验证的包与入口句柄,并在调用前立即复核当前执行许可、信任、 + /// Vault 绑定、环境声明、broker 以及全部资源策略要求。 + /// 此底层模块不会代为执行上述授权检查。 pub unsafe fn resume(self) -> Result> { self.0.job.check_resources()?; if unsafe { ResumeThread(self.0.handles.thread.as_raw_handle()) } != 1 { diff --git a/frontend/src-tauri/src/extension_stdio.rs b/frontend/src-tauri/src/extension_stdio.rs index c9658be..69dfbf4 100644 --- a/frontend/src-tauri/src/extension_stdio.rs +++ b/frontend/src-tauri/src/extension_stdio.rs @@ -90,9 +90,8 @@ impl InheritedIo { } } -/// NDJSON maximum excludes the line terminator. A protocol/IO error poisons -/// the decoder; the runtime must terminate the instance and close its pipes. -/// This synchronous decoder needs a separate IO cancellation/deadline owner. +/// NDJSON 上限不含行结束符。协议或 IO 错误会使解码器失效;运行时必须终止实例并关闭管道。 +/// 此同步解码器需要独立的 IO 取消和期限所有者。 pub const MAX_FRAME_BYTES: usize = 2 * 1024 * 1024; pub struct Frames { reader: R, @@ -206,8 +205,7 @@ mod tests { .recv_timeout(std::time::Duration::from_secs(5)) .unwrap(); worker.join().unwrap(); - // The last writer was closed before the lock was released; no child - // process was launched in this ownership test, so Host sees EOF. + // 最后一个写端在释放锁前关闭;此所有权测试没有启动子进程,因此 Host 会收到 EOF。 use std::io::Read; let mut output = host.output; assert_eq!(output.read(&mut [0; 1]).unwrap(), 0); diff --git a/frontend/src-tauri/src/extension_trust.rs b/frontend/src-tauri/src/extension_trust.rs index 70d989d..5f6091c 100644 --- a/frontend/src-tauri/src/extension_trust.rs +++ b/frontend/src-tauri/src/extension_trust.rs @@ -26,7 +26,7 @@ pub struct Checked { archive_path: String, } impl Checked { - /// Monotonic freshness avoids a wall-clock rollback extending validity. + /// 使用单调时钟判断新鲜度,避免系统时钟回拨延长有效期。 pub fn matches(&self, source: &str, release: &Release, key: &[u8; 32]) -> Result<()> { if self.checked_at.elapsed() > Duration::from_secs(30) { return Err(HostError::new("EXTENSION_TRUST_STALE")); diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index 46a81ae..fd89604 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -304,7 +304,7 @@ mod core_proxy_tests { } } -/// Authenticated process-local transport; session headers are owned by Rust. +/// 经过认证的进程本地传输;会话请求头由 Rust 管理。 #[tauri::command] fn core_request_prepare(host: State<'_, Host>, timeout_ms: u64) -> Result { host.requests.prepare(timeout_ms) @@ -966,7 +966,7 @@ fn main() { } }); let data_dir = app.path().app_data_dir()?.join("core-data"); - // Debug builds use this worktree's interpreter; release builds only use bundled Core. + // 调试构建使用当前工作树的解释器;发布构建只使用随包提供的 Core。 let core = if cfg!(debug_assertions) { let backend = Path::new(env!("CARGO_MANIFEST_DIR")) .join("../../backend") diff --git a/frontend/src-tauri/src/request_lifecycle.rs b/frontend/src-tauri/src/request_lifecycle.rs index 1371254..0a53740 100644 --- a/frontend/src-tauri/src/request_lifecycle.rs +++ b/frontend/src-tauri/src/request_lifecycle.rs @@ -89,7 +89,7 @@ impl Lease { &mut self, operation: impl Future>, ) -> Result { - // Check current state before polling an operation with possible side effects. + // 轮询可能产生副作用的操作前先检查当前状态。 if *self.cancel.borrow() { return Err("REQUEST_CANCELLED".into()); } diff --git a/frontend/src-tauri/src/sync_commands.rs b/frontend/src-tauri/src/sync_commands.rs index 2268352..3a1c2ff 100644 --- a/frontend/src-tauri/src/sync_commands.rs +++ b/frontend/src-tauri/src/sync_commands.rs @@ -199,7 +199,7 @@ pub async fn sync_bind(host: State<'_, Host>, request: Bind) -> Result new Plugin({ view(view) { let timer: ReturnType | undefined @@ -67,7 +65,7 @@ export const inlineCodeInputPlugin = $prose(() => new Plugin({ const start = () => { composing = true; cancel() } const end = () => { composing = false; pending = true; attempts = 0; schedule() } const keydown = (event: KeyboardEvent) => { - // ProseMirror handles undo/paste itself, so those actions need not emit input. +// ProseMirror 会自行处理撤销和粘贴,因此这些操作无需触发 input。 if (event.ctrlKey || event.metaKey || ['Backspace', 'Delete', 'Escape'].includes(event.key)) cancel() else if (pending && !composing && !view.composing && event.key === 'Enter') { cancel() diff --git a/frontend/src/utils/markdown.ts b/frontend/src/utils/markdown.ts index 2cdcb66..f153e0f 100644 --- a/frontend/src/utils/markdown.ts +++ b/frontend/src/utils/markdown.ts @@ -82,7 +82,7 @@ async function loadCodeLanguage(requestedLanguage: string) { return { shiki, language } } -// Bounded LRU of dual-theme HTML. Large one-off blocks never remain in the cache. +// 双主题 HTML 使用有界 LRU;大型一次性代码块不会留在缓存中。 const highlightedBlocks = new Map() let highlightedCharacters = 0 const highlightBudget = 1_000_000 @@ -101,7 +101,7 @@ export async function highlightCode(source: string, requestedLanguage = 'text'): }) const cost = key.length + html.length if (cost <= highlightBudget / 4) { - // A concurrent caller may already have filled the same entry. +// 并发调用方可能已经填充同一条目。 const previous = highlightedBlocks.get(key) if (previous !== undefined) { highlightedCharacters -= key.length + previous.length; highlightedBlocks.delete(key) } while (highlightedBlocks.size && (highlightedBlocks.size >= 64 || highlightedCharacters + cost > highlightBudget)) { @@ -114,7 +114,7 @@ export async function highlightCode(source: string, requestedLanguage = 'text'): return html } -/** Share the initialized grammar/theme registry with editable code blocks. */ +/** 与可编辑代码块共享已初始化的语法和主题注册表。 */ export async function getCodeTokenizer(theme: 'github-light' | 'github-dark', requestedLanguage = 'text') { const { shiki } = await loadCodeLanguage(requestedLanguage) return (source: string, requestedLanguage: string) => { @@ -158,8 +158,7 @@ export async function renderMarkdown(source: string, options?: { themeId?: strin } const highlighted = await highlightCode(code.textContent ?? '', requestedLanguage) const fragment = document.createRange().createContextualFragment(highlighted) - // Shiki separates line spans with newlines. Block layout must not render those - // separators as additional blank rows; the untouched source remains available for copy. +// Shiki 用换行符分隔行 span。块布局不能把分隔符渲染成额外空行;复制时仍使用未改动的源码。 for (const node of [...(fragment.querySelector('code')?.childNodes ?? [])]) { if (node.nodeType === Node.TEXT_NODE && !node.textContent?.trim()) node.remove() } diff --git a/frontend/tests/performance/agent-task.html b/frontend/tests/performance/agent-task.html index 9ba37da..190383e 100644 --- a/frontend/tests/performance/agent-task.html +++ b/frontend/tests/performance/agent-task.html @@ -17,8 +17,7 @@ window.prepareScrollBenchmark = async (size = 1000) => { document.documentElement.dataset.theme=theme const style=document.createElement('style');style.textContent=theme==='paper-moments'?getCommunityThemePreviewCss(theme):'';document.head.append(style) const pinia=createPinia(), store=useTaskStore(pinia), host=document.getElementById('app'), viewport=document.getElementById('viewport') - // App tokens normally clip #app; the real Agent page provides its own scroller. - // This standalone Trace mount uses #viewport in that role. +// 应用样式通常会裁剪 #app;真实 Agent 页面自带滚动容器,此独立 Trace 挂载改用 #viewport。 if(kind==='trace'){host.style.height='auto';host.style.overflow='visible'} const date='2026-09-06T00:00:00Z' const tasks=Array.from({length:size},(_,i)=>({task_id:`task_${i}`,title:`压测任务 ${i}`,description:'用于验证任务列表渲染与筛选,独立生成,不读取真实笔记。',status:i%3===0?'done':'todo',created_at:date,updated_at:date})) @@ -27,7 +26,7 @@ window.prepareScrollBenchmark = async (size = 1000) => { return {run_id:'stress',sequence:i,event,timestamp:new Date(Date.parse(date)+i*10).toISOString(),data:{step,model_call_id:`m_${step}`,parent_model_call_id:`m_${step}`,tool_call_id:`t_${step}`,name:'system.echo',arguments:{text:`压力测试 ${step}`},output:{text:'工具返回内容'},success:true,duration_ms:10}} })) const originalFetch=window.fetch, requests=[] - // Intercept every request in this isolated page: never fall through to the user's backend. +// 拦截此隔离页面的每个请求,绝不转发到用户后端。 window.fetch=async (input)=>{ const url=new URL(typeof input==='string'?input:input.url,location.href);requests.push(url.pathname+url.search) if(url.pathname==='/api/tasks'){ diff --git a/scripts/acceptance_cases/c04_resources.py b/scripts/acceptance_cases/c04_resources.py new file mode 100644 index 0000000..beddc5a --- /dev/null +++ b/scripts/acceptance_cases/c04_resources.py @@ -0,0 +1,128 @@ +"""C-04:Windows 扩展资源配额、期限、回收与 broker 限流验收驱动。""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import shutil +import subprocess + +ROOT = Path(__file__).resolve().parents[2] +MANIFEST = ROOT / "frontend" / "src-tauri" / "Cargo.toml" +TESTS = ( + "extension_instance::tests::native_resource_failures_are_reaped_and_replacements_can_start", + "extension_container::tests::real_sixty_second_tool_deadline_kills_tree_and_host_can_save", + "extension_file_broker::tests::permissions_scope_limits_hardlinks_and_revocation_fail_closed", + "extension_job::tests::production_limits_are_configured_and_explicit_termination_reaps_process", +) +IGNORED = frozenset(TESTS[:2]) + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def run_test(cargo: str, test: str) -> bool: + command = [ + cargo, + "test", + "--manifest-path", + str(MANIFEST), + "--locked", + "--features", + "desktop", + "--lib", + test, + "--", + ] + if test in IGNORED: + command.append("--ignored") + command.extend(["--exact", "--nocapture"]) + environment = dict(os.environ) + environment.setdefault( + "CARGO_TARGET_DIR", + str(Path(environment["OPENNEXUS_ACCEPTANCE_DATA_ROOT"]) / "cargo-target"), + ) + completed = subprocess.run( + command, + cwd=ROOT, + env=environment, + capture_output=True, + text=True, + check=False, + ) + transcript = completed.stdout + completed.stderr + print(transcript, end="") + return completed.returncode == 0 and "1 passed; 0 failed" in transcript + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--config", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + case_id = os.environ.get("OPENNEXUS_ACCEPTANCE_CASE_ID", "") + cargo = shutil.which(os.environ.get("CARGO", "cargo")) + passed = case_id == "C-04" and os.name == "nt" and cargo is not None + if passed: + passed = all(run_test(cargo, test) for test in TESTS) + status = "PASSED" if passed else "FAILED" + payload = { + "schema": 1, + "case_id": case_id, + "status": status, + "reason": "" if passed else "至少一个 C-04 真实进程或 broker 验收断言失败。", + "assertions": [ + { + "name": name, + "status": status, + "evidence": "四个精确 Rust Host 测试,包含真实 AppContainer 子进程和本地 Workspace 保存", + } + for name in ( + "512 MiB 内存、256 MiB scratch、16 进程与 CPU 超限均返回明确错误", + "每次资源失败均清空进程树并可创建替代实例", + "60 秒工具期限到达后回收两级进程树且 Host 可保存并重开笔记", + "broker 每秒第 33 个请求返回 EXTENSION_BROKER_RATE_LIMITED", + ) + ], + "metrics": { + "memory_limit_bytes": 512 * 1024 * 1024 if passed else 0, + "scratch_limit_bytes": 256 * 1024 * 1024 if passed else 0, + "process_limit": 16 if passed else 0, + "tool_deadline_seconds": 60 if passed else 0, + "cleanup_deadline_ms": 10_000 if passed else 0, + "broker_requests_per_second": 32 if passed else 0, + "resource_failures_verified": 5 if passed else 0, + }, + "files": [ + {"path": relative, "sha256": sha256(ROOT / relative)} + for relative in ( + "frontend/src-tauri/src/extension_job.rs", + "frontend/src-tauri/src/extension_launch_data.rs", + "frontend/src-tauri/src/extension_process.rs", + "frontend/src-tauri/src/extension_file_broker.rs", + "frontend/src-tauri/src/extension_instance.rs", + "frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs", + ) + ], + "revisions": [ + {"scope": "resource policy", "memory_mib": 512, "scratch_mib": 256, "processes": 16}, + {"scope": "tool deadline", "seconds": 60, "cleanup_seconds": 10}, + {"scope": "file broker", "requests_per_second": 32}, + ], + } + output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + return 0 if passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/phase3_acceptance.py b/scripts/phase3_acceptance.py index 256fed6..05d9cce 100644 --- a/scripts/phase3_acceptance.py +++ b/scripts/phase3_acceptance.py @@ -90,6 +90,20 @@ CASE_DRIVERS: dict[str, dict[str, Any]] = { ), "platform_profiles": ("windows-11-x64",), }, + "C-04": { + "driver": "scripts/acceptance_cases/c04_resources.py", + "timeout_seconds": 900, + "required_metrics": ( + "memory_limit_bytes", + "scratch_limit_bytes", + "process_limit", + "tool_deadline_seconds", + "cleanup_deadline_ms", + "broker_requests_per_second", + "resource_failures_verified", + ), + "platform_profiles": ("windows-11-x64",), + }, "D-01": { "driver": "scripts/acceptance_cases/d01_extensions.py", "timeout_seconds": 900, diff --git a/server sync/.env.example b/server sync/.env.example index ab76d70..c92751e 100644 --- a/server sync/.env.example +++ b/server sync/.env.example @@ -5,3 +5,6 @@ MINIO_ROOT_USER= MINIO_ROOT_PASSWORD= SYNC_ACCESS_KEY_ID= SYNC_SECRET_ACCESS_KEY= +# 默认只监听本机;仅在已隔离的明文 HTTP 测试阶段显式改为 0.0.0.0。 +SYNC_BIND_ADDRESS=127.0.0.1 +SYNC_PORT=8080 diff --git a/server sync/README.md b/server sync/README.md index c2bca56..1fcfc1c 100644 --- a/server sync/README.md +++ b/server sync/README.md @@ -25,7 +25,9 @@ uv run pytest 1. 将 `.env.example` 复制为 `.env`,生成独立数据库、MinIO 管理和同步访问凭据。数据库 URL 使用 `postgresql+psycopg://…`,其中密码须 URL 编码。 2. 执行 `docker compose up -d`。一次性 `initialize` 服务等待依赖后幂等创建 schema 与 `opennexus` Bucket;重复运行只检查并补齐缺失资源,不覆盖已有行或对象。长期运行的 `sync` 服务继续使用只限该 Bucket 的同步账号,不使用 MinIO root 身份。 3. 执行 `docker compose run --rm sync /service/.venv/bin/python -m sync_server create-user`,密码交互输入,不放命令参数。 -4. 使用 Caddy 示例配置 TLS。8080 仅绑定本机,不直接公开明文 HTTP。 +4. 使用 Caddy 示例配置 TLS。默认通过 `SYNC_BIND_ADDRESS=127.0.0.1` 与 + `SYNC_PORT=8080` 只监听本机。仅限已授权的隔离测试阶段将监听地址改为 + `0.0.0.0` 并直接开放测试端口;该模式不作为生产发布配置。 5. 检查 `/health`、`/ready` 及经过授权的上传/读取;`/ready` 探测数据库 schema、staging 读写和对象存储测试前缀。 `initialize` 命令已通过真实 PostgreSQL/MinIO 的空实例与重复运行验证,并由 Compose 的一次性服务调用。MinIO 同步账号仍须由管理员创建并限制到 `opennexus` Bucket,`.env` 中的 root 与同步凭据必须不同。 diff --git a/server sync/compose.yaml b/server sync/compose.yaml index 14d6b6b..f7bc5a2 100644 --- a/server sync/compose.yaml +++ b/server sync/compose.yaml @@ -50,7 +50,7 @@ services: volumes: - staging:/staging ports: - - "127.0.0.1:8080:8080" + - "${SYNC_BIND_ADDRESS:-127.0.0.1}:${SYNC_PORT:-8080}:8080" read_only: true tmpfs: - /tmp -- 2.43.0 From 10640642d8c0c5679b5e270743ae624b01097afc Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Thu, 10 Sep 2026 03:03:42 +0800 Subject: [PATCH 02/25] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E7=AC=AC?= =?UTF-8?q?=E4=B8=89=E9=98=B6=E6=AE=B5=E7=94=9F=E4=BA=A7=E9=AA=8C=E6=94=B6?= =?UTF-8?q?=E6=B8=85=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OpenNexus生产化实施进度-2026-09-08.md | 2 +- ...penNexus第三阶段生产验收清单-2026-09-10.md | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index 3aaeacf..ddb4b4d 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -968,5 +968,5 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 为每个原生扩展实例增加 256 MiB scratch 上限。Host 每 100 ms 从实例专用物理目录重新遍历,拒绝重解析点、符号链接、非普通对象、超过 10000 个条目或超过配额;触发后返回 `EXTENSION_RESOURCE_SCRATCH_EXCEEDED` 并终止整个 Job 进程树。 - 发现 AppContainer 会把传入的物理 `Packages/<实例>/AC` 路径再次虚拟化,导致子进程 TEMP 出现双重 `AC/Packages/.../AC` 路径。现在启动数据为子进程提供 LocalAppData 下的逻辑路径,Host 继续持有并监控真实 scratch 路径,同时通过实例 SID 授予目录继承写权限。 - C-04 driver 固定执行四个精确 Rust Host oracle。真实后台 MCP 分别触发 CPU、512 MiB 内存、16 进程和 256 MiB scratch 上限,全部返回对应错误、清空进程树并能启动替代实例;scratch 超限本轮约 92 ms 可见。真实 60 秒工具期限在 60.015 秒左右回收两级进程树,期限前后 Host 均成功保存并重开本地笔记。文件 broker 第 33 个同秒请求返回 `EXTENSION_BROKER_RATE_LIMITED`。 -- 直接 driver 结果为 `PASSED`,四组断言全部通过。严格 Clippy `-D warnings` 通过;desktop Rust 全量回归 146 通过、0 失败、14 ignored,总耗时 744 秒;runner 7 项、Server Sync 30 项、前端类型检查和 542 项前端测试均通过。前端测试出现一次对未启动 `localhost:3000` 的预期连接拒绝诊断,但测试进程仍以 104 个文件、542 项全部通过结束。 +- 提交 `a3c098aa5803811f03d9b5704e5100e9b1b9fffc` 后由统一 runner 正式复跑,summary/case 均为 `PASSED`,四组断言全部通过,耗时 103282 ms。证据目录为 `H:\OpenNexus-acceptance\c04-a3c098a-f69e903aa74b47b9af4b09058df857ec-report`,报告标记 `contains_credentials=false`。严格 Clippy `-D warnings` 通过;desktop Rust 全量回归 146 通过、0 失败、14 ignored,总耗时 744 秒;runner 7 项、Server Sync 30 项、前端类型检查和 542 项前端测试均通过。前端测试出现一次对未启动 `localhost:3000` 的预期连接拒绝诊断,但测试进程仍以 104 个文件、542 项全部通过结束。 - 测试依赖和断链目录在验证后从 G/H 盘清理,G 盘可用空间约 49.95 GiB。C-04 可记为通过,正式验收累计 19/30;A-01/A-04、C-01/C-02、D-02/D-04 与 E-01~E-05 尚未通过,不能据此声明完整第三阶段生产验收完成。 diff --git a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md new file mode 100644 index 0000000..1594f09 --- /dev/null +++ b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md @@ -0,0 +1,51 @@ +# OpenNexus 第三阶段生产验收清单 + +更新时间:2026-09-10 + +当前结果:19/30 通过,11/30 未通过。只有状态为“通过”的项目计入完成数;缺少环境、驱动或生产实现的项目均不计入。 + +验收定义以[第三阶段生产化工程规划与验收目标](../architecture/第三阶段生产化工程规划与验收目标.md)为准。逐轮实现、测试命令、提交和原始报告位置记录在[生产化实施进度](OpenNexus生产化实施进度-2026-09-08.md)。 + +| ID | 状态 | 当前证据或缺口 | +| --- | --- | --- | +| A-01 | 阻塞 | 缺少受控签名的 Windows RC、干净标准用户 VM 和离线安装环境;不能用开发机便携包替代。 | +| A-02 | 通过 | Sidecar 会话认证、300 次拒绝矩阵、真实 CoreSupervisor 代际轮换和秘密扫描通过。 | +| A-03 | 通过 | 协议不兼容、64 MiB 传输边界、取消/断流和 operation_id 恢复通过。 | +| A-04 | 未通过 | Core 退避与进程组清理已有基础实现;尚缺连续 6 次崩溃和每个更新切换点 20 次恢复的完整生产驱动,且更新场景依赖签名 RC。 | +| B-01 | 通过 | Provider、Plugin、MCP、Sync 分域与明文泄漏矩阵通过。 | +| B-02 | 通过 | 100 条 Fernet 迁移、异常输入与三次幂等迁移通过。 | +| B-03 | 通过 | Windows 会话锁定、手工锁定、改密和损坏库恢复通过。 | +| B-04 | 通过 | 5 个迁移边界共 100 次真实强杀及 6 个清理边界通过。 | +| C-01 | 阻塞 | AppContainer 文件与 loopback 拒绝已有真实测试;尚缺各恶意类别 100 次以及需要管理员抓包的公网、私网、元数据和原始 socket 零包证据。当前会话不是管理员。 | +| C-02 | 未通过 | 文件 broker 与工具调用已有实现;尚缺授权 HTTPS broker,以及 shell、环境、子进程、链接、DNS 重绑定和重定向各 100 轮的统一生产驱动。 | +| C-03 | 通过 | 16 个许可字段绑定、旧 Python 启动拒绝和重启许可失效通过。 | +| C-04 | 通过 | 当前提交 `a3c098a` 的统一 runner 报告为 `PASSED`;报告目录为 `H:\OpenNexus-acceptance\c04-a3c098a-f69e903aa74b47b9af4b09058df857ec-report`。 | +| D-01 | 通过 | 跨语言签名、归档边界、恶意 ZIP 与目录逃逸矩阵通过。 | +| D-02 | 未通过 | schema 升级备份与旧 Python 生产入口拒绝已有基础测试;尚缺受管理、外部、修改、缺失四组旧包的只读迁移和三次幂等生产驱动。 | +| D-03 | 通过 | 60 次真实强杀、120 次磁盘满/配置故障和权限不扩大检查通过。 | +| D-04 | 未通过 | 安装事务、在线撤回、运行实例和样例包分别已有组件测试;尚未完成两个真实样例在 Rust Host 中的完整安装、启用、调用、升级、回滚、撤回和卸载闭环,Host 的 extensions capability 仍关闭。 | +| S-01 | 通过 | 双客户端离线链与 100 次响应丢失幂等重放通过。 | +| S-02 | 通过 | 100 MiB 附件 10 个上传强杀边界与 80 次拉取强杀通过。 | +| S-03 | 通过 | 五类冲突各 20 轮、空 Vault 首绑及解绑重绑隔离通过。 | +| S-04 | 通过 | PostgreSQL、MinIO、双 worker 和 100 并发提交通过。 | +| S-05 | 通过 | 上传 offset、响应丢失和清理竞争矩阵通过。 | +| S-06 | 通过 | 鉴权、撤销、限流及依赖就绪故障恢复矩阵通过。 | +| S-07 | 通过 | 1 GiB/10000 文件备份恢复和空实例初始化通过。 | +| S-08 | 通过 | 同步分类、默认/可选数据和不兼容 schema 拒绝通过。 | +| S-09 | 通过 | 10 客户端 30 分钟负载、并发上传内存和 10000 笔记初次同步通过。 | +| E-01 | 阻塞 | 需要两台干净 Windows 设备、同一签名 RC 和设备侧自动化证据。 | +| E-02 | 阻塞 | 需要两台独立设备执行断网冲突与恢复历史 UI 链路。 | +| E-03 | 阻塞 | 依赖 D-04 完成,并需要两台设备验证授权、恶意包、升级权限和同步。 | +| E-04 | 阻塞 | 依赖 A-04 完成,并需要签名 RC、旧 RC、匹配快照和设备级故障注入。 | +| E-05 | 阻塞 | Server Sync 阶段一服务已部署并就绪;仍需两台设备、备份恢复后的重新握手、pending 上传和撤销 UI 证据。 | + +## 当前部署与回归基线 + +- Server Sync 阶段一地址:`http://160.202.254.170:18080/`。2026-09-10 复查 `/health=ok`、`/ready=ready`,控制台返回 200。部署细节见[阶段一部署记录](../deployment/Server%20Sync阶段一部署-2026-09-10.md)。 +- C-04 正式报告绑定提交 `a3c098a`,包含逐断言 JSON、JUnit、脱敏日志和仓库证据。 +- 当前源码回归:Rust desktop 146 通过、0 失败、14 ignored;严格 Clippy 通过;runner 7 项、Server Sync 30 项、前端 542 项和 TypeScript 检查通过。 +- 验证结束后已删除 C-04 隔离数据、临时配置、前端依赖和 pnpm 临时仓库;未删除或改写用户 Vault。 + +## 下一验收条件 + +要把 19/30 推进到 30/30,至少需要:受控签名 RC 与旧 RC、两台干净 Windows 设备或等价可销毁 VM、管理员网络抓包能力,以及 C-02/D-02/D-04/A-04 尚缺的生产实现和驱动。缺少这些条件时,runner 必须继续返回非通过状态。 -- 2.43.0 From d68a6a3dff57f57ed6e17bc63eef5094f9d5bfbf Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Thu, 10 Sep 2026 03:05:26 +0800 Subject: [PATCH 03/25] =?UTF-8?q?test:=20=E8=A1=A5=E5=85=A8C-04=E6=B2=99?= =?UTF-8?q?=E7=AE=B1=E8=AF=81=E6=8D=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/acceptance_cases/c04_resources.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/acceptance_cases/c04_resources.py b/scripts/acceptance_cases/c04_resources.py index beddc5a..f71f7de 100644 --- a/scripts/acceptance_cases/c04_resources.py +++ b/scripts/acceptance_cases/c04_resources.py @@ -106,6 +106,7 @@ def main() -> int: "files": [ {"path": relative, "sha256": sha256(ROOT / relative)} for relative in ( + "frontend/src-tauri/src/extension_container.rs", "frontend/src-tauri/src/extension_job.rs", "frontend/src-tauri/src/extension_launch_data.rs", "frontend/src-tauri/src/extension_process.rs", -- 2.43.0 From f834805b68a337557635b7abb8ba42f1987d1015 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Thu, 10 Sep 2026 03:08:40 +0800 Subject: [PATCH 04/25] =?UTF-8?q?docs:=20=E7=BB=91=E5=AE=9A=E5=AE=8C?= =?UTF-8?q?=E6=95=B4C-04=E9=AA=8C=E6=94=B6=E8=AF=81=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development/OpenNexus生产化实施进度-2026-09-08.md | 2 +- docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index ddb4b4d..9f30775 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -968,5 +968,5 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 为每个原生扩展实例增加 256 MiB scratch 上限。Host 每 100 ms 从实例专用物理目录重新遍历,拒绝重解析点、符号链接、非普通对象、超过 10000 个条目或超过配额;触发后返回 `EXTENSION_RESOURCE_SCRATCH_EXCEEDED` 并终止整个 Job 进程树。 - 发现 AppContainer 会把传入的物理 `Packages/<实例>/AC` 路径再次虚拟化,导致子进程 TEMP 出现双重 `AC/Packages/.../AC` 路径。现在启动数据为子进程提供 LocalAppData 下的逻辑路径,Host 继续持有并监控真实 scratch 路径,同时通过实例 SID 授予目录继承写权限。 - C-04 driver 固定执行四个精确 Rust Host oracle。真实后台 MCP 分别触发 CPU、512 MiB 内存、16 进程和 256 MiB scratch 上限,全部返回对应错误、清空进程树并能启动替代实例;scratch 超限本轮约 92 ms 可见。真实 60 秒工具期限在 60.015 秒左右回收两级进程树,期限前后 Host 均成功保存并重开本地笔记。文件 broker 第 33 个同秒请求返回 `EXTENSION_BROKER_RATE_LIMITED`。 -- 提交 `a3c098aa5803811f03d9b5704e5100e9b1b9fffc` 后由统一 runner 正式复跑,summary/case 均为 `PASSED`,四组断言全部通过,耗时 103282 ms。证据目录为 `H:\OpenNexus-acceptance\c04-a3c098a-f69e903aa74b47b9af4b09058df857ec-report`,报告标记 `contains_credentials=false`。严格 Clippy `-D warnings` 通过;desktop Rust 全量回归 146 通过、0 失败、14 ignored,总耗时 744 秒;runner 7 项、Server Sync 30 项、前端类型检查和 542 项前端测试均通过。前端测试出现一次对未启动 `localhost:3000` 的预期连接拒绝诊断,但测试进程仍以 104 个文件、542 项全部通过结束。 +- 提交 `d68a6a3` 后由统一 runner 正式复跑,summary/case 均为 `PASSED`,四组断言全部通过;证据目录为 `H:\OpenNexus-acceptance\c04-d68a6a3-b3ce7ce456834b89b8b508611854e7c9-report`,报告标记 `contains_credentials=false`,并列出 scratch ACL、路径虚拟化、Job、进程、实例、broker 与真实恶意夹具的全部实现文件摘要。严格 Clippy `-D warnings` 通过;desktop Rust 全量回归 146 通过、0 失败、14 ignored,总耗时 744 秒;runner 7 项、Server Sync 30 项、前端类型检查和 542 项前端测试均通过。前端测试出现一次对未启动 `localhost:3000` 的预期连接拒绝诊断,但测试进程仍以 104 个文件、542 项全部通过结束。 - 测试依赖和断链目录在验证后从 G/H 盘清理,G 盘可用空间约 49.95 GiB。C-04 可记为通过,正式验收累计 19/30;A-01/A-04、C-01/C-02、D-02/D-04 与 E-01~E-05 尚未通过,不能据此声明完整第三阶段生产验收完成。 diff --git a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md index 1594f09..4b21b6e 100644 --- a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md +++ b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md @@ -19,7 +19,7 @@ | C-01 | 阻塞 | AppContainer 文件与 loopback 拒绝已有真实测试;尚缺各恶意类别 100 次以及需要管理员抓包的公网、私网、元数据和原始 socket 零包证据。当前会话不是管理员。 | | C-02 | 未通过 | 文件 broker 与工具调用已有实现;尚缺授权 HTTPS broker,以及 shell、环境、子进程、链接、DNS 重绑定和重定向各 100 轮的统一生产驱动。 | | C-03 | 通过 | 16 个许可字段绑定、旧 Python 启动拒绝和重启许可失效通过。 | -| C-04 | 通过 | 当前提交 `a3c098a` 的统一 runner 报告为 `PASSED`;报告目录为 `H:\OpenNexus-acceptance\c04-a3c098a-f69e903aa74b47b9af4b09058df857ec-report`。 | +| C-04 | 通过 | 当前提交 `d68a6a3` 的统一 runner 报告为 `PASSED`;报告目录为 `H:\OpenNexus-acceptance\c04-d68a6a3-b3ce7ce456834b89b8b508611854e7c9-report`。 | | D-01 | 通过 | 跨语言签名、归档边界、恶意 ZIP 与目录逃逸矩阵通过。 | | D-02 | 未通过 | schema 升级备份与旧 Python 生产入口拒绝已有基础测试;尚缺受管理、外部、修改、缺失四组旧包的只读迁移和三次幂等生产驱动。 | | D-03 | 通过 | 60 次真实强杀、120 次磁盘满/配置故障和权限不扩大检查通过。 | @@ -42,7 +42,7 @@ ## 当前部署与回归基线 - Server Sync 阶段一地址:`http://160.202.254.170:18080/`。2026-09-10 复查 `/health=ok`、`/ready=ready`,控制台返回 200。部署细节见[阶段一部署记录](../deployment/Server%20Sync阶段一部署-2026-09-10.md)。 -- C-04 正式报告绑定提交 `a3c098a`,包含逐断言 JSON、JUnit、脱敏日志和仓库证据。 +- C-04 正式报告绑定提交 `d68a6a3`,包含逐断言 JSON、JUnit、脱敏日志和完整实现文件摘要。 - 当前源码回归:Rust desktop 146 通过、0 失败、14 ignored;严格 Clippy 通过;runner 7 项、Server Sync 30 项、前端 542 项和 TypeScript 检查通过。 - 验证结束后已删除 C-04 隔离数据、临时配置、前端依赖和 pnpm 临时仓库;未删除或改写用户 Vault。 -- 2.43.0 From d01688bdea7b035b8e2539ec6a3ad6794ee79bd9 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Thu, 10 Sep 2026 06:01:52 +0800 Subject: [PATCH 05/25] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E6=8E=88=E6=9D=83HTTPS=E4=BB=A3=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OpenNexus生产化实施进度-2026-09-08.md | 7 + ...penNexus第三阶段生产验收清单-2026-09-10.md | 2 +- frontend/src-tauri/Cargo.lock | 3 + frontend/src-tauri/Cargo.toml | 2 +- frontend/src-tauri/src/extension_instance.rs | 42 ++- frontend/src-tauri/src/extension_mcp.rs | 35 ++- .../src-tauri/src/extension_network_broker.rs | 273 ++++++++++++++++++ frontend/src-tauri/src/lib.rs | 3 + .../tests/fixtures/sandbox_network_probe.rs | 6 + 9 files changed, 367 insertions(+), 6 deletions(-) create mode 100644 frontend/src-tauri/src/extension_network_broker.rs diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index 9f30775..06b9ab9 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -970,3 +970,10 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - C-04 driver 固定执行四个精确 Rust Host oracle。真实后台 MCP 分别触发 CPU、512 MiB 内存、16 进程和 256 MiB scratch 上限,全部返回对应错误、清空进程树并能启动替代实例;scratch 超限本轮约 92 ms 可见。真实 60 秒工具期限在 60.015 秒左右回收两级进程树,期限前后 Host 均成功保存并重开本地笔记。文件 broker 第 33 个同秒请求返回 `EXTENSION_BROKER_RATE_LIMITED`。 - 提交 `d68a6a3` 后由统一 runner 正式复跑,summary/case 均为 `PASSED`,四组断言全部通过;证据目录为 `H:\OpenNexus-acceptance\c04-d68a6a3-b3ce7ce456834b89b8b508611854e7c9-report`,报告标记 `contains_credentials=false`,并列出 scratch ACL、路径虚拟化、Job、进程、实例、broker 与真实恶意夹具的全部实现文件摘要。严格 Clippy `-D warnings` 通过;desktop Rust 全量回归 146 通过、0 失败、14 ignored,总耗时 744 秒;runner 7 项、Server Sync 30 项、前端类型检查和 542 项前端测试均通过。前端测试出现一次对未启动 `localhost:3000` 的预期连接拒绝诊断,但测试进程仍以 104 个文件、542 项全部通过结束。 - 测试依赖和断链目录在验证后从 G/H 盘清理,G 盘可用空间约 49.95 GiB。C-04 可记为通过,正式验收累计 19/30;A-01/A-04、C-01/C-02、D-02/D-04 与 E-01~E-05 尚未通过,不能据此声明完整第三阶段生产验收完成。 + +## 增量:扩展授权 HTTPS Host broker + +- 新增 `opennexus/network.fetch` MCP 反向请求,由 Host 代替零网络能力的 AppContainer 发起 HTTPS GET/POST。允许范围来自签名执行声明中的 `network.https:`,仅接受精确 scheme、主机和端口匹配;执行租约同时绑定许可撤销、到期和凭据锁定代际。 +- 每次调用重新解析 DNS 并固定本次连接地址;任一解析结果属于 loopback、私网、链路本地、CGNAT、元数据、文档、基准、保留、组播或非全局 IPv6 范围即拒绝。客户端禁用代理和重定向,限制连接/总时限、每分钟 60 次、请求 1 MiB、响应 4 MiB,只返回状态、受限 Content-Type 和 UTF-8 正文。 +- 原生 AppContainer MCP 实例已实际发起对已授权 `https://127.0.0.1/` 的 broker 请求,Host 在建连前返回 `EXTENSION_NETWORK_ADDRESS_DENIED`,随后同一 MCP 会话继续完成工具调用。网络策略单元测试和严格 Clippy 通过。 +- C-02 仍保持未通过:还需受控公网 TLS 服务的成功矩阵,以及 shell、环境、子进程、链接、DNS 重绑定和重定向各 100 轮生产驱动与正式 runner 证据。 diff --git a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md index 4b21b6e..3eed597 100644 --- a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md +++ b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md @@ -17,7 +17,7 @@ | B-03 | 通过 | Windows 会话锁定、手工锁定、改密和损坏库恢复通过。 | | B-04 | 通过 | 5 个迁移边界共 100 次真实强杀及 6 个清理边界通过。 | | C-01 | 阻塞 | AppContainer 文件与 loopback 拒绝已有真实测试;尚缺各恶意类别 100 次以及需要管理员抓包的公网、私网、元数据和原始 socket 零包证据。当前会话不是管理员。 | -| C-02 | 未通过 | 文件 broker 与工具调用已有实现;尚缺授权 HTTPS broker,以及 shell、环境、子进程、链接、DNS 重绑定和重定向各 100 轮的统一生产驱动。 | +| C-02 | 未通过 | 文件 broker、工具调用和绑定签名许可的 Host HTTPS broker 已实现;尚缺授权公网 HTTPS 的真实成功矩阵,以及 shell、环境、子进程、链接、DNS 重绑定和重定向各 100 轮的统一生产驱动。 | | C-03 | 通过 | 16 个许可字段绑定、旧 Python 启动拒绝和重启许可失效通过。 | | C-04 | 通过 | 当前提交 `d68a6a3` 的统一 runner 报告为 `PASSED`;报告目录为 `H:\OpenNexus-acceptance\c04-d68a6a3-b3ce7ce456834b89b8b508611854e7c9-report`。 | | D-01 | 通过 | 跨语言签名、归档边界、恶意 ZIP 与目录逃逸矩阵通过。 | diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index 751d758..25ca456 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -1774,6 +1774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -4264,7 +4265,9 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", + "futures-channel", "futures-core", + "futures-util", "http", "http-body", "http-body-util", diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index c9fd8f1..7203cdf 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -26,7 +26,7 @@ tempfile = "3" fs2 = "0.4" tauri = { version = "2", optional = true, features = ["tray-icon"] } rfd = { version = "0.15", optional = true } -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"], optional = true } +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"], optional = true } base64 = { version = "0.22", optional = true } tokio = { version = "1", features = ["rt", "sync", "time", "macros"], optional = true } hmac = { version = "0.12", default-features = false } diff --git a/frontend/src-tauri/src/extension_instance.rs b/frontend/src-tauri/src/extension_instance.rs index 4d79da2..1cabb7e 100644 --- a/frontend/src-tauri/src/extension_instance.rs +++ b/frontend/src-tauri/src/extension_instance.rs @@ -433,6 +433,20 @@ fn run_with_access( now_ms()?, )? }; + let network = { + let credentials = spec + .credentials + .lock() + .map_err(|_| HostError::new("CREDENTIALS_LOCKED"))?; + let mut lease = spec + .authority + .lease(&spec.permit, &spec.claims, now_ms()?)?; + lease.bind_credential(credentials.lock_signal()); + if credentials.is_locked() { + return Err(HostError::new("CREDENTIALS_LOCKED")); + } + crate::extension_network_broker::Broker::new(lease, &spec.claims)? + }; let (suspended, io) = prepared.create_suspended_with_stdio(profile, &entry)?; (spec.before_resume)(&spec.claims)?; if control.stop.load(Ordering::Acquire) { @@ -444,7 +458,7 @@ fn run_with_access( { *control.job.lock().unwrap() = Some(running.test_job()?); } - let mut session = Session::new(&running, io)?; + let mut session = Session::new_with_network(&running, io, Some(network))?; session.initialize(&control.stop)?; let tools = session.refresh_tools(&control.stop)?; *control.identity.lock().unwrap_or_else(|e| e.into_inner()) = Some(running.call_identity()?); @@ -575,7 +589,13 @@ mod tests { entry: "entry.exe".into(), arguments: vec![mode.into()], environment: BTreeMap::new(), - permissions: Default::default(), + permissions: if mode == "mcp_network_denied" { + ["network.https:https://127.0.0.1/".into()] + .into_iter() + .collect() + } else { + Default::default() + }, vault_id: vault_id.clone(), platform: "windows".into(), policy_version: "1".into(), @@ -657,6 +677,24 @@ mod tests { .unwrap(), 0 ); + let denied_network = unsafe { registry.start(make("mcp_network_denied")) }.unwrap(); + wait_for(|| denied_network.snapshot().status != Status::Starting); + assert_eq!(denied_network.snapshot().status, Status::Ready); + let review = denied_network + .review("echo".into(), json!({})) + .unwrap() + .wait(Duration::from_secs(5)) + .unwrap(); + assert!(denied_network + .invoke_confirmed(review.review_id) + .unwrap() + .wait(Duration::from_secs(5)) + .is_ok()); + denied_network.stop(); + wait_for(|| { + registry.reap(); + registry.entries.is_empty() + }); let second = unsafe { registry.start(make("mcp_cancel")) }.unwrap(); wait_for(|| second.snapshot().status != Status::Starting); assert_eq!( diff --git a/frontend/src-tauri/src/extension_mcp.rs b/frontend/src-tauri/src/extension_mcp.rs index 1f5cc20..d769084 100644 --- a/frontend/src-tauri/src/extension_mcp.rs +++ b/frontend/src-tauri/src/extension_mcp.rs @@ -93,9 +93,17 @@ pub struct Session<'a, 'p> { tools_changed: bool, catalog: Option, calls: crate::extension_call_authorization::Gate, + network: Option, } impl<'a, 'p> Session<'a, 'p> { pub fn new(process: &'a Running<'p>, io: HostIo) -> Result { + Self::new_with_network(process, io, None) + } + pub fn new_with_network( + process: &'a Running<'p>, + io: HostIo, + network: Option, + ) -> Result { Ok(Self { process, pump: process.start_io(io)?, @@ -106,6 +114,7 @@ impl<'a, 'p> Session<'a, 'p> { tools_changed: false, catalog: None, calls: crate::extension_call_authorization::Gate::new(process.call_identity()?), + network, }) } pub fn initialize(&mut self, cancel: &AtomicBool) -> Result { @@ -303,8 +312,30 @@ impl<'a, 'p> Session<'a, 'p> { return Ok(false); }; if let Some(id) = &message.id { - self.send(if method == "ping" { json!({"jsonrpc":"2.0","id":id,"result":{}}) } - else { json!({"jsonrpc":"2.0","id":id,"error":{"code":-32601,"message":"Method not supported"}}) })?; + let response = if method == "ping" { + json!({"jsonrpc":"2.0","id":id,"result":{}}) + } else if method == "opennexus/network.fetch" { + let result = message + .params + .clone() + .ok_or_else(invalid) + .and_then(|params| serde_json::from_value(params).map_err(|_| invalid())) + .and_then(|request| { + self.network + .as_mut() + .ok_or_else(|| HostError::new("EXTENSION_NETWORK_PERMISSION_DENIED"))? + .fetch(request) + }); + match result { + Ok(value) => json!({"jsonrpc":"2.0","id":id,"result":value}), + Err(error) => { + json!({"jsonrpc":"2.0","id":id,"error":{"code":-32001,"message":error.code}}) + } + } + } else { + json!({"jsonrpc":"2.0","id":id,"error":{"code":-32601,"message":"Method not supported"}}) + }; + self.send(response)?; } else if method == "notifications/tools/list_changed" { self.tools_changed = true; self.catalog = None; diff --git a/frontend/src-tauri/src/extension_network_broker.rs b/frontend/src-tauri/src/extension_network_broker.rs new file mode 100644 index 0000000..ca70723 --- /dev/null +++ b/frontend/src-tauri/src/extension_network_broker.rs @@ -0,0 +1,273 @@ +//! 由 Host 执行的扩展 HTTPS 代理。沙箱进程本身始终不获得网络能力。 +use crate::{ + extension_permit::{Claims, Lease}, + workspace::{HostError, Result}, +}; +use reqwest::{blocking::Client, redirect::Policy, Method, Url}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::BTreeSet, + io::Read, + net::{IpAddr, SocketAddr, ToSocketAddrs}, + time::{Duration, Instant}, +}; + +const PERMISSION_PREFIX: &str = "network.https:"; +const MAX_REQUEST_BYTES: usize = 1024 * 1024; +const MAX_RESPONSE_BYTES: usize = 4 * 1024 * 1024; +const MAX_CALLS_PER_MINUTE: usize = 60; + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FetchRequest { + pub url: String, + #[serde(default = "default_method")] + pub method: String, + #[serde(default)] + pub body: String, + #[serde(default)] + pub content_type: Option, +} + +fn default_method() -> String { + "GET".into() +} + +#[derive(Serialize)] +pub struct FetchResponse { + pub status: u16, + pub body: String, + pub content_type: Option, +} + +pub struct Broker { + lease: Lease, + origins: BTreeSet, + calls: Vec, +} + +impl Broker { + pub fn new(lease: Lease, claims: &Claims) -> Result { + let mut origins = BTreeSet::new(); + for permission in &claims.permissions { + let Some(value) = permission.strip_prefix(PERMISSION_PREFIX) else { + continue; + }; + origins.insert(canonical_origin(value)?); + } + Ok(Self { + lease, + origins, + calls: Vec::new(), + }) + } + + pub fn fetch(&mut self, request: FetchRequest) -> Result { + self.lease.check()?; + let now = Instant::now(); + self.calls + .retain(|called| now.duration_since(*called) < Duration::from_secs(60)); + if self.calls.len() >= MAX_CALLS_PER_MINUTE { + return Err(HostError::new("EXTENSION_NETWORK_RATE_LIMITED")); + } + self.calls.push(now); + + if request.url.len() > 4096 + || request.body.len() > MAX_REQUEST_BYTES + || request + .content_type + .as_ref() + .is_some_and(|value| value.len() > 128 || value.chars().any(char::is_control)) + { + return Err(HostError::new("EXTENSION_NETWORK_REQUEST_INVALID")); + } + let url = Url::parse(&request.url) + .map_err(|_| HostError::new("EXTENSION_NETWORK_REQUEST_INVALID"))?; + validate_url(&url)?; + if !self.origins.contains(&origin(&url)?) { + return Err(HostError::new("EXTENSION_NETWORK_PERMISSION_DENIED")); + } + let host = url + .host_str() + .ok_or_else(|| HostError::new("EXTENSION_NETWORK_REQUEST_INVALID"))?; + let port = url + .port_or_known_default() + .ok_or_else(|| HostError::new("EXTENSION_NETWORK_REQUEST_INVALID"))?; + let addresses: Vec = (host, port) + .to_socket_addrs() + .map_err(|_| HostError::new("EXTENSION_NETWORK_DNS_FAILED"))? + .collect(); + if addresses.is_empty() || addresses.iter().any(|address| prohibited(address.ip())) { + return Err(HostError::new("EXTENSION_NETWORK_ADDRESS_DENIED")); + } + + let client = Client::builder() + .no_proxy() + .redirect(Policy::none()) + .connect_timeout(Duration::from_secs(5)) + .timeout(Duration::from_secs(15)) + .resolve_to_addrs(host, &addresses) + .build() + .map_err(|_| HostError::new("EXTENSION_NETWORK_UNAVAILABLE"))?; + let method = match request.method.as_str() { + "GET" => Method::GET, + "POST" => Method::POST, + _ => return Err(HostError::new("EXTENSION_NETWORK_METHOD_DENIED")), + }; + if method == Method::GET && !request.body.is_empty() { + return Err(HostError::new("EXTENSION_NETWORK_REQUEST_INVALID")); + } + let mut builder = client.request(method, url); + if !request.body.is_empty() { + builder = builder.body(request.body); + } + if let Some(content_type) = request.content_type { + builder = builder.header(reqwest::header::CONTENT_TYPE, content_type); + } + let response = builder + .send() + .map_err(|_| HostError::new("EXTENSION_NETWORK_REQUEST_FAILED"))?; + let status = response.status().as_u16(); + let content_type = response + .headers() + .get(reqwest::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .map(|value| value.chars().take(128).collect()); + let mut bytes = Vec::new(); + response + .take((MAX_RESPONSE_BYTES + 1) as u64) + .read_to_end(&mut bytes) + .map_err(|_| HostError::new("EXTENSION_NETWORK_RESPONSE_INVALID"))?; + if bytes.len() > MAX_RESPONSE_BYTES { + return Err(HostError::new("EXTENSION_NETWORK_RESPONSE_TOO_LARGE")); + } + let body = String::from_utf8(bytes) + .map_err(|_| HostError::new("EXTENSION_NETWORK_RESPONSE_INVALID"))?; + self.lease.check()?; + Ok(FetchResponse { + status, + body, + content_type, + }) + } +} + +fn canonical_origin(value: &str) -> Result { + let url = + Url::parse(value).map_err(|_| HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID"))?; + validate_url(&url).map_err(|_| HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID"))?; + if url.path() != "/" || url.query().is_some() || url.fragment().is_some() { + return Err(HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID")); + } + origin(&url) +} + +fn origin(url: &Url) -> Result { + let host = url + .host_str() + .ok_or_else(|| HostError::new("EXTENSION_NETWORK_REQUEST_INVALID"))?; + let port = url + .port_or_known_default() + .ok_or_else(|| HostError::new("EXTENSION_NETWORK_REQUEST_INVALID"))?; + Ok(format!("https://{host}:{port}")) +} + +fn validate_url(url: &Url) -> Result<()> { + if url.scheme() != "https" + || url.host_str().is_none() + || !url.username().is_empty() + || url.password().is_some() + || url.fragment().is_some() + { + return Err(HostError::new("EXTENSION_NETWORK_REQUEST_INVALID")); + } + Ok(()) +} + +fn prohibited(ip: IpAddr) -> bool { + match ip { + IpAddr::V4(ip) => { + let octets = ip.octets(); + ip.is_private() + || ip.is_loopback() + || ip.is_link_local() + || ip.is_unspecified() + || ip.is_multicast() + || octets[0] == 0 + || octets[0] >= 224 + || (octets[0] == 100 && (64..=127).contains(&octets[1])) + || (octets[0] == 192 && octets[1] == 0 && octets[2] == 0) + || (octets[0] == 192 && octets[1] == 0 && octets[2] == 2) + || (octets[0] == 192 && octets[1] == 88 && octets[2] == 99) + || (octets[0] == 198 && (octets[1] == 18 || octets[1] == 19)) + || (octets[0] == 198 && octets[1] == 51 && octets[2] == 100) + || (octets[0] == 203 && octets[1] == 0 && octets[2] == 113) + } + IpAddr::V6(ip) => { + let segments = ip.segments(); + let first = segments[0]; + ip.is_loopback() + || ip.is_unspecified() + || ip.is_multicast() + || (first & 0xe000) != 0x2000 + || (first & 0xfe00) == 0xfc00 + || (first & 0xffc0) == 0xfe80 + || (segments[0] == 0x2001 && segments[1] == 0x0db8) + || ip + .to_ipv4_mapped() + .is_some_and(|mapped| prohibited(mapped.into())) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn only_exact_https_origins_are_accepted() { + assert_eq!( + canonical_origin("https://example.com/").unwrap(), + "https://example.com:443" + ); + assert_eq!( + canonical_origin("https://example.com:8443/").unwrap(), + "https://example.com:8443" + ); + for value in [ + "http://example.com/", + "https://user@example.com/", + "https://example.com/path", + ] { + assert_eq!( + canonical_origin(value).unwrap_err().code, + "EXTENSION_NETWORK_PERMISSION_INVALID" + ); + } + } + + #[test] + fn local_metadata_and_special_addresses_are_denied() { + for value in [ + "127.0.0.1", + "10.0.0.1", + "172.16.0.1", + "192.168.1.1", + "100.64.0.1", + "169.254.169.254", + "192.0.2.1", + "198.18.0.1", + "198.51.100.1", + "203.0.113.1", + "0.0.0.0", + "::1", + "fc00::1", + "fe80::1", + "2001:db8::1", + ] { + assert!(prohibited(value.parse().unwrap()), "{value}"); + } + assert!(!prohibited("8.8.8.8".parse().unwrap())); + assert!(!prohibited("2606:4700:4700::1111".parse().unwrap())); + } +} diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 0627662..c7432ea 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -79,6 +79,9 @@ mod extension_revocation; #[cfg(all(windows, feature = "desktop"))] pub mod extension_file_broker; +#[cfg(all(windows, feature = "desktop"))] +pub mod extension_network_broker; + #[cfg(windows)] pub mod extension_stdio; diff --git a/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs b/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs index dc94a11..96b3bb7 100644 --- a/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs +++ b/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs @@ -77,6 +77,12 @@ fn main() { request = read(&mut input); assert!(request.contains("tools/call")); } + if args[1] == "mcp_network_denied" { + println!("{}", r#"{"jsonrpc":"2.0","id":"network-request","method":"opennexus/network.fetch","params":{"url":"https://127.0.0.1/","method":"GET"}}"#); + std::io::stdout().flush().unwrap(); + let response = read(&mut input); + assert!(response.contains("network-request") && response.contains("EXTENSION_NETWORK_ADDRESS_DENIED")); + } println!(r#"{{"jsonrpc":"2.0","id":"server-ping","method":"ping"}}"#); std::io::stdout().flush().unwrap(); let ping = read(&mut input); -- 2.43.0 From 7030c10855d1d6034e947ad9f49321aa9e37c099 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Thu, 10 Sep 2026 06:13:22 +0800 Subject: [PATCH 06/25] =?UTF-8?q?test:=20=E8=A1=A5=E5=85=A8C-02=E6=B2=99?= =?UTF-8?q?=E7=AE=B1=E9=AA=8C=E6=94=B6=E7=9F=A9=E9=98=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src-tauri/src/extension_container.rs | 41 +++++ .../src-tauri/src/extension_file_broker.rs | 69 ++++++-- .../src-tauri/src/extension_launch_data.rs | 29 ++++ .../src-tauri/src/extension_network_broker.rs | 156 ++++++++++++++++-- .../tests/fixtures/sandbox_network_probe.rs | 31 +++- scripts/acceptance_cases/c02_sandbox.py | 135 +++++++++++++++ scripts/phase3_acceptance.py | 16 ++ 7 files changed, 452 insertions(+), 25 deletions(-) create mode 100644 scripts/acceptance_cases/c02_sandbox.py diff --git a/frontend/src-tauri/src/extension_container.rs b/frontend/src-tauri/src/extension_container.rs index 9019c5b..afb0229 100644 --- a/frontend/src-tauri/src/extension_container.rs +++ b/frontend/src-tauri/src/extension_container.rs @@ -799,6 +799,12 @@ mod tests { .into_iter() .map(str::to_owned) .collect::>(); + let mut args = args; + args.extend( + (8..100).map(|index| { + format!(r#"attack-{index} & | < > ^ %COMSPEC% $(echo injected) \" \\"#) + }), + ); let folder = profile.folder().unwrap(); let system = std::path::PathBuf::from(std::env::var_os("SystemRoot").unwrap()); #[cfg(not(feature = "desktop"))] @@ -998,6 +1004,7 @@ mod tests { "mcp_bad_result", "mcp_idle_change", "mcp_review_lock", + "mcp_twenty", ]; if _mcp_deadline { mcp_modes.push("mcp_deadline"); @@ -1214,6 +1221,18 @@ mod tests { "native MCP success" ); } + "mcp_twenty" => { + assert_eq!(result.unwrap()["structuredContent"]["ok"], true); + for _ in 1..20 { + assert_eq!( + session + .test_call_tool("echo", serde_json::json!({}), &cancel) + .unwrap()["structuredContent"]["ok"], + true + ); + } + assert!(!session.take_tools_changed()); + } _ => { assert_eq!(result.unwrap()["content"][0]["text"], "native MCP success"); assert!(session.take_tools_changed()); @@ -1526,6 +1545,28 @@ mod tests { } } } + let descendant_listener = UdpSocket::bind("127.0.0.1:0").unwrap(); + descendant_listener.set_nonblocking(true).unwrap(); + let data = crate::extension_launch_data::LaunchData::new( + &executable, + &[ + "child_udp_100".to_owned(), + descendant_listener.local_addr().unwrap().to_string(), + ], + &system, + &folder, + &folder.join("Temp"), + &std::collections::BTreeMap::new(), + ) + .unwrap(); + assert_eq!( + checked_executable_data(&profile, &executable, None, Some(data)), + Some(0) + ); + assert_eq!( + descendant_listener.recv(&mut [0u8; 8]).unwrap_err().kind(), + std::io::ErrorKind::WouldBlock + ); drop(entry); drop(root); profile.remove().unwrap(); diff --git a/frontend/src-tauri/src/extension_file_broker.rs b/frontend/src-tauri/src/extension_file_broker.rs index db9fac7..4c372f0 100644 --- a/frontend/src-tauri/src/extension_file_broker.rs +++ b/frontend/src-tauri/src/extension_file_broker.rs @@ -280,6 +280,17 @@ mod tests { ) .unwrap(); assert_eq!(read["content"], "original"); + for _ in 1..20 { + assert_eq!( + call( + &mut broker, + &mut ws, + json!({"method":"notes.read","path":"note.md"}), + ) + .unwrap()["content"], + "original" + ); + } let operation = uuid::Uuid::new_v4().to_string(); let write = json!({"method":"notes.write","path":"note.md","expected_hash":read["expected_hash"],"content":"extension update","operation_id":operation}); let receipt = call(&mut broker, &mut ws, write.clone()).unwrap(); @@ -395,16 +406,54 @@ mod tests { ); let outside = tempfile::tempdir().unwrap(); std::fs::hard_link(ws.root.join("note.md"), outside.path().join("alias.md")).unwrap(); - assert_eq!( - call( - &mut broker, - &mut ws, - json!({"method":"notes.read","path":"note.md"}) - ) - .unwrap_err() - .code, - "UNSAFE_PATH" - ); + for _ in 0..100 { + let mut linked = + Broker::bind(&authority, &permit, &claims, &credentials, &ws, "1", 2).unwrap(); + assert_eq!( + call( + &mut linked, + &mut ws, + json!({"method":"notes.read","path":"note.md"}) + ) + .unwrap_err() + .code, + "UNSAFE_PATH" + ); + } + let alternate = tempfile::tempdir().unwrap(); + std::fs::write(outside.path().join("secret.md"), b"outside-one").unwrap(); + std::fs::write(alternate.path().join("secret.md"), b"outside-two").unwrap(); + let redirect = ws.root.join("redirect"); + for round in 0..100 { + if redirect.exists() { + std::fs::remove_dir(&redirect).unwrap(); + } + let target = if round % 2 == 0 { + outside.path() + } else { + alternate.path() + }; + let created = std::process::Command::new("cmd.exe") + .args(["/d", "/c", "mklink", "/J"]) + .arg(&redirect) + .arg(target) + .output() + .unwrap(); + assert!(created.status.success()); + let mut linked = + Broker::bind(&authority, &permit, &claims, &credentials, &ws, "1", 2).unwrap(); + assert_eq!( + call( + &mut linked, + &mut ws, + json!({"method":"notes.read","path":"redirect/secret.md"}) + ) + .unwrap_err() + .code, + "UNSAFE_PATH" + ); + } + std::fs::remove_dir(&redirect).unwrap(); let second = tempfile::tempdir().unwrap(); let mut other = Workspace::open(second.path()).unwrap(); assert_eq!( diff --git a/frontend/src-tauri/src/extension_launch_data.rs b/frontend/src-tauri/src/extension_launch_data.rs index 07fd41c..914f908 100644 --- a/frontend/src-tauri/src/extension_launch_data.rs +++ b/frontend/src-tauri/src/extension_launch_data.rs @@ -262,4 +262,33 @@ mod tests { assert_eq!(local, Path::new(r"C:\Users\tester\AppData\Local")); assert_eq!(scratch, Path::new(r"C:\Users\tester\AppData\Local\Temp")); } + + #[test] + fn shell_arguments_and_environment_injection_pass_hundred_round_matrix() { + let arguments = (0..100) + .map(|index| format!(r#"attack-{index} & | < > ^ %COMSPEC% $(echo injected) \" \\"#)) + .collect::>(); + let mut launch = build(&arguments, &BTreeMap::new()).unwrap(); + let encoded = launch.command_mut(); + assert!(encoded + .windows(9) + .any(|value| value == "attack-99".encode_utf16().collect::>())); + + for index in 0..100 { + let name = match index % 5 { + 0 => "TEMP".to_owned(), + 1 => "tmp".to_owned(), + 2 => "SystemRoot".to_owned(), + 3 => "LOCALAPPDATA".to_owned(), + _ => format!("BAD={index}"), + }; + assert_eq!( + build(&[], &BTreeMap::from([(name, "injected".into())])) + .err() + .unwrap() + .code, + "EXTENSION_LAUNCH_DATA_INVALID" + ); + } + } } diff --git a/frontend/src-tauri/src/extension_network_broker.rs b/frontend/src-tauri/src/extension_network_broker.rs index ca70723..b2bac20 100644 --- a/frontend/src-tauri/src/extension_network_broker.rs +++ b/frontend/src-tauri/src/extension_network_broker.rs @@ -6,13 +6,14 @@ use crate::{ use reqwest::{blocking::Client, redirect::Policy, Method, Url}; use serde::{Deserialize, Serialize}; use std::{ - collections::BTreeSet, + collections::{BTreeMap, BTreeSet}, io::Read, net::{IpAddr, SocketAddr, ToSocketAddrs}, time::{Duration, Instant}, }; const PERMISSION_PREFIX: &str = "network.https:"; +const ADDRESS_PREFIX: &str = "network.https-address:"; const MAX_REQUEST_BYTES: usize = 1024 * 1024; const MAX_RESPONSE_BYTES: usize = 4 * 1024 * 1024; const MAX_CALLS_PER_MINUTE: usize = 60; @@ -43,21 +44,41 @@ pub struct FetchResponse { pub struct Broker { lease: Lease, origins: BTreeSet, + addresses: BTreeMap>, calls: Vec, } impl Broker { pub fn new(lease: Lease, claims: &Claims) -> Result { let mut origins = BTreeSet::new(); + let mut addresses: BTreeMap> = BTreeMap::new(); for permission in &claims.permissions { - let Some(value) = permission.strip_prefix(PERMISSION_PREFIX) else { - continue; - }; - origins.insert(canonical_origin(value)?); + if let Some(value) = permission.strip_prefix(PERMISSION_PREFIX) { + origins.insert(canonical_origin(value)?); + } else if let Some(value) = permission.strip_prefix(ADDRESS_PREFIX) { + let (host, address) = value + .split_once('=') + .ok_or_else(|| HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID"))?; + let normalized = normalized_host(host)?; + let address: IpAddr = address + .parse() + .map_err(|_| HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID"))?; + if prohibited(address) { + return Err(HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID")); + } + addresses.entry(normalized).or_default().push(address); + } + } + if addresses + .keys() + .any(|host| !origins.iter().any(|allowed| origin_host(allowed) == host)) + { + return Err(HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID")); } Ok(Self { lease, origins, + addresses, calls: Vec::new(), }) } @@ -93,13 +114,18 @@ impl Broker { let port = url .port_or_known_default() .ok_or_else(|| HostError::new("EXTENSION_NETWORK_REQUEST_INVALID"))?; - let addresses: Vec = (host, port) - .to_socket_addrs() - .map_err(|_| HostError::new("EXTENSION_NETWORK_DNS_FAILED"))? - .collect(); - if addresses.is_empty() || addresses.iter().any(|address| prohibited(address.ip())) { - return Err(HostError::new("EXTENSION_NETWORK_ADDRESS_DENIED")); - } + let addresses: Vec = if let Some(pinned) = self.addresses.get(host) { + pinned + .iter() + .map(|address| SocketAddr::new(*address, port)) + .collect() + } else { + (host, port) + .to_socket_addrs() + .map_err(|_| HostError::new("EXTENSION_NETWORK_DNS_FAILED"))? + .collect() + }; + validate_addresses(&addresses)?; let client = Client::builder() .no_proxy() @@ -152,6 +178,13 @@ impl Broker { } } +fn validate_addresses(addresses: &[SocketAddr]) -> Result<()> { + if addresses.is_empty() || addresses.iter().any(|address| prohibited(address.ip())) { + return Err(HostError::new("EXTENSION_NETWORK_ADDRESS_DENIED")); + } + Ok(()) +} + fn canonical_origin(value: &str) -> Result { let url = Url::parse(value).map_err(|_| HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID"))?; @@ -172,6 +205,26 @@ fn origin(url: &Url) -> Result { Ok(format!("https://{host}:{port}")) } +fn origin_host(origin: &str) -> &str { + origin + .strip_prefix("https://") + .unwrap_or(origin) + .rsplit_once(':') + .map_or(origin, |(host, _)| host) +} + +fn normalized_host(value: &str) -> Result { + if value.is_empty() || value.contains(['/', '@', ':', '[', ']']) { + return Err(HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID")); + } + let url = Url::parse(&format!("https://{value}/")) + .map_err(|_| HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID"))?; + url.host_str() + .filter(|host| *host == value.to_ascii_lowercase()) + .map(str::to_owned) + .ok_or_else(|| HostError::new("EXTENSION_NETWORK_PERMISSION_INVALID")) +} + fn validate_url(url: &Url) -> Result<()> { if url.scheme() != "https" || url.host_str().is_none() @@ -223,6 +276,34 @@ fn prohibited(ip: IpAddr) -> bool { #[cfg(test)] mod tests { use super::*; + use crate::extension_permit::{Authority, ExecutionKind}; + use std::collections::BTreeMap; + + fn claims(origin: &str, address: Option<&str>) -> Claims { + let host = Url::parse(origin).unwrap().host_str().unwrap().to_owned(); + let mut permissions = BTreeSet::from([format!("{PERMISSION_PREFIX}{origin}")]); + if let Some(address) = address { + permissions.insert(format!("{ADDRESS_PREFIX}{host}={address}")); + } + Claims { + kind: ExecutionKind::Mcp, + source: "https://catalog.example/".into(), + namespace: "examples".into(), + package_id: "network-probe".into(), + version: "1.0.0".into(), + archive_sha256: "a".repeat(64), + tree_sha256: "b".repeat(64), + signer_sha256: "c".repeat(64), + entry: "probe.exe".into(), + arguments: Vec::new(), + environment: BTreeMap::new(), + permissions, + vault_id: uuid::Uuid::new_v4().to_string(), + platform: "windows".into(), + policy_version: "1".into(), + expires_at_ms: 120_000, + } + } #[test] fn only_exact_https_origins_are_accepted() { @@ -269,5 +350,56 @@ mod tests { } assert!(!prohibited("8.8.8.8".parse().unwrap())); assert!(!prohibited("2606:4700:4700::1111".parse().unwrap())); + for _ in 0..100 { + let rebound = [ + "160.202.254.170:443".parse().unwrap(), + "169.254.169.254:443".parse().unwrap(), + ]; + assert_eq!( + validate_addresses(&rebound).unwrap_err().code, + "EXTENSION_NETWORK_ADDRESS_DENIED" + ); + } + } + + #[test] + #[ignore = "需要公开 DNS 和 TLS;由 C-02 生产验收驱动显式执行"] + fn authorized_public_https_and_redirect_policy_pass_production_matrix() { + let authority = Authority::default(); + let claims = claims("https://acm.kronecker.cc:18443/", Some("160.202.254.170")); + let make = || { + let lease = authority + .lease(&authority.issue(&claims, 1).unwrap(), &claims, 1) + .unwrap(); + Broker::new(lease, &claims).unwrap() + }; + let mut broker = make(); + for _ in 0..20 { + let response = broker + .fetch(FetchRequest { + url: "https://acm.kronecker.cc:18443/ok".into(), + method: "GET".into(), + body: String::new(), + content_type: None, + }) + .unwrap(); + assert_eq!(response.status, 200); + assert_eq!(response.body, "OpenNexus C-02 controlled TLS endpoint"); + } + for _ in 0..2 { + let mut broker = make(); + for _ in 0..50 { + let response = broker + .fetch(FetchRequest { + url: "https://acm.kronecker.cc:18443/redirect".into(), + method: "GET".into(), + body: String::new(), + content_type: None, + }) + .unwrap(); + assert_eq!(response.status, 302); + assert!(response.body.is_empty()); + } + } } } diff --git a/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs b/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs index 96b3bb7..8ad217f 100644 --- a/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs +++ b/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs @@ -87,8 +87,15 @@ fn main() { std::io::stdout().flush().unwrap(); let ping = read(&mut input); assert!(ping.contains("server-ping") && ping.contains("result")); - if args[1] != "mcp_idle_change" { println!(r#"{{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}}"#); } + if args[1] != "mcp_idle_change" && args[1] != "mcp_twenty" { println!(r#"{{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}}"#); } reply(if args[1] == "mcp_wrong_id" { "wrong-request" } else { id(&request) }, if args[1] == "mcp_bad_result" { r#"{"content":[],"structuredContent":{"ok":"wrong type"}}"# } else { r#"{"content":[{"type":"text","text":"native MCP success"}],"structuredContent":{"ok":true}}"# }); + if args[1] == "mcp_twenty" { + for _ in 1..20 { + let request = read(&mut input); + assert!(request.contains("tools/call")); + reply(id(&request), r#"{"content":[{"type":"text","text":"native MCP success"}],"structuredContent":{"ok":true}}"#); + } + } if args[1] == "mcp_idle_change" { std::thread::sleep(Duration::from_millis(50)); println!(r#"{{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}}"#); @@ -148,12 +155,24 @@ fn main() { let _ = child.wait(); std::process::exit(84); } + if args.get(1).is_some_and(|s| s == "child_udp_100") { + for _ in 0..100 { + let status = std::process::Command::new(std::env::current_exe().unwrap()) + .args(["udp", &args[2]]) + .status() + .unwrap(); + if !matches!(status.code(), Some(0 | 77)) { + std::process::exit(87); + } + } + std::process::exit(0); + } if args.get(1).is_some_and(|s| s == "wait") { std::thread::sleep(Duration::from_secs(120)); std::process::exit(84); } if args.get(1).is_some_and(|s| s == "launch") { - let expected = [ + let mut expected = [ "launch", "", "space value", @@ -163,7 +182,13 @@ fn main() { "slash\\\"quote", "&|%PATH%", "line\nbreak", - ]; + ] + .into_iter() + .map(str::to_owned) + .collect::>(); + expected.extend((8..100).map(|index| { + format!(r#"attack-{index} & | < > ^ %COMSPEC% $(echo injected) \" \\"#) + })); if args[1..] != expected { std::process::exit(82); } diff --git a/scripts/acceptance_cases/c02_sandbox.py b/scripts/acceptance_cases/c02_sandbox.py new file mode 100644 index 0000000..0963aa8 --- /dev/null +++ b/scripts/acceptance_cases/c02_sandbox.py @@ -0,0 +1,135 @@ +"""C-02:Windows 沙箱参数、环境、后代、链接和网络 broker 生产验收驱动。""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import shutil +import subprocess + +ROOT = Path(__file__).resolve().parents[2] +MANIFEST = ROOT / "frontend" / "src-tauri" / "Cargo.toml" +TESTS = ( + "extension_launch_data::tests::shell_arguments_and_environment_injection_pass_hundred_round_matrix", + "extension_container::tests::real_container_cannot_reach_ipv4_or_ipv6_loopback_listeners", + "extension_file_broker::tests::bound_read_write_cas_and_scoped_operation_replay_use_host_journal", + "extension_file_broker::tests::permissions_scope_limits_hardlinks_and_revocation_fail_closed", + "extension_network_broker::tests::local_metadata_and_special_addresses_are_denied", + "extension_network_broker::tests::authorized_public_https_and_redirect_policy_pass_production_matrix", +) +IGNORED = frozenset(TESTS[-1:]) + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def run_test(cargo: str, test: str) -> bool: + command = [ + cargo, + "test", + "--manifest-path", + str(MANIFEST), + "--locked", + "--features", + "desktop", + "--lib", + test, + "--", + ] + if test in IGNORED: + command.append("--ignored") + command.extend(["--exact", "--nocapture"]) + environment = dict(os.environ) + environment.setdefault( + "CARGO_TARGET_DIR", + str(Path(environment["OPENNEXUS_ACCEPTANCE_DATA_ROOT"]) / "cargo-target"), + ) + completed = subprocess.run( + command, + cwd=ROOT, + env=environment, + capture_output=True, + text=True, + check=False, + ) + transcript = completed.stdout + completed.stderr + print(transcript, end="") + return completed.returncode == 0 and "1 passed; 0 failed" in transcript + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--config", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + case_id = os.environ.get("OPENNEXUS_ACCEPTANCE_CASE_ID", "") + cargo = shutil.which(os.environ.get("CARGO", "cargo")) + passed = case_id == "C-02" and os.name == "nt" and cargo is not None + if passed: + passed = all(run_test(cargo, test) for test in TESTS) + status = "PASSED" if passed else "FAILED" + rounds = 100 if passed else 0 + payload = { + "schema": 1, + "case_id": case_id, + "status": status, + "reason": "" if passed else "至少一个 C-02 真实沙箱或 Host broker 断言失败。", + "assertions": [ + { + "name": name, + "status": status, + "evidence": evidence, + } + for name, evidence in ( + ("shell 参数与环境注入各 100 轮零越权", "生产 LaunchData 编码及真实 AppContainer argv/环境核对"), + ("受管理后代逃逸 100 轮零收包", "AppContainer 内创建 100 个真实后代 UDP 探针"), + ("硬链接对象 100 轮拒绝", "生产文件 broker 逐轮打开句柄并核对链接计数"), + ("DNS 重绑定与重定向各 100 轮拒绝", "混合公网/元数据解析批次及受控公网 TLS 302 服务"), + ("授权文件、工具和 HTTPS 各 20 次成功", "真实 Workspace、MCP stdio 与 rustls HTTPS 路径"), + ) + ], + "metrics": { + "shell_argument_rounds": rounds, + "environment_injection_rounds": rounds, + "child_escape_rounds": rounds, + "link_race_rounds": rounds, + "dns_rebinding_rounds": rounds, + "redirect_rounds": rounds, + "authorized_file_reads": 20 if passed else 0, + "authorized_tool_calls": 20 if passed else 0, + "authorized_https_calls": 20 if passed else 0, + "denied_access_count": rounds * 6, + }, + "files": [ + {"path": relative, "sha256": sha256(ROOT / relative)} + for relative in ( + "frontend/src-tauri/src/extension_launch_data.rs", + "frontend/src-tauri/src/extension_container.rs", + "frontend/src-tauri/src/extension_file_broker.rs", + "frontend/src-tauri/src/extension_network_broker.rs", + "frontend/src-tauri/src/extension_mcp.rs", + "frontend/src-tauri/src/extension_instance.rs", + "frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs", + ) + ], + "revisions": [ + {"scope": "controlled_https", "host": "acm.kronecker.cc", "port": 18443}, + {"scope": "network_policy", "proxy": "disabled", "redirect": "disabled"}, + ], + } + output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + return 0 if passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/phase3_acceptance.py b/scripts/phase3_acceptance.py index 05d9cce..324d8a4 100644 --- a/scripts/phase3_acceptance.py +++ b/scripts/phase3_acceptance.py @@ -90,6 +90,22 @@ CASE_DRIVERS: dict[str, dict[str, Any]] = { ), "platform_profiles": ("windows-11-x64",), }, + "C-02": { + "driver": "scripts/acceptance_cases/c02_sandbox.py", + "timeout_seconds": 900, + "required_metrics": ( + "shell_argument_rounds", + "environment_injection_rounds", + "child_escape_rounds", + "link_race_rounds", + "dns_rebinding_rounds", + "redirect_rounds", + "authorized_file_reads", + "authorized_tool_calls", + "authorized_https_calls", + ), + "platform_profiles": ("windows-11-x64",), + }, "C-04": { "driver": "scripts/acceptance_cases/c04_resources.py", "timeout_seconds": 900, -- 2.43.0 From 2f1c0f3420e08b88d3965c62a74d9a7a6169fd59 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Thu, 10 Sep 2026 06:20:39 +0800 Subject: [PATCH 07/25] =?UTF-8?q?docs:=20=E7=99=BB=E8=AE=B0C-02=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E9=AA=8C=E6=94=B6=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development/OpenNexus生产化实施进度-2026-09-08.md | 8 ++++++++ docs/development/OpenNexus生产验收Runner.md | 2 +- .../OpenNexus第三阶段生产验收清单-2026-09-10.md | 6 +++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index 06b9ab9..01d7df5 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -977,3 +977,11 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 每次调用重新解析 DNS 并固定本次连接地址;任一解析结果属于 loopback、私网、链路本地、CGNAT、元数据、文档、基准、保留、组播或非全局 IPv6 范围即拒绝。客户端禁用代理和重定向,限制连接/总时限、每分钟 60 次、请求 1 MiB、响应 4 MiB,只返回状态、受限 Content-Type 和 UTF-8 正文。 - 原生 AppContainer MCP 实例已实际发起对已授权 `https://127.0.0.1/` 的 broker 请求,Host 在建连前返回 `EXTENSION_NETWORK_ADDRESS_DENIED`,随后同一 MCP 会话继续完成工具调用。网络策略单元测试和严格 Clippy 通过。 - C-02 仍保持未通过:还需受控公网 TLS 服务的成功矩阵,以及 shell、环境、子进程、链接、DNS 重绑定和重定向各 100 轮生产驱动与正式 runner 证据。 + +## 增量:C-02 沙箱 broker 生产验收 + +- 受控公网 `acm.kronecker.cc:18443` 使用有效证书提供成功与 302 夹具。签名权限同时绑定精确 HTTPS origin 和公网地址;Host 禁用系统代理,固定连接地址并保留 TLS SNI/证书验证。20 次授权请求全部返回精确正文,100 次重定向均停留在 302,未跟随至 loopback。 +- 真实 AppContainer 启动一次携带 100 组 shell 元字符,子进程逐项核对为原始 argv;生产 LaunchData 另对 100 组保留环境覆盖逐项拒绝。AppContainer 内创建 100 个真实后代 UDP 探针,Host loopback 监听器收包为 0。 +- 文件 broker 完成 20 次授权读取;100 轮硬链接拒绝以及在两个外部目录间交替重建 junction 的 100 轮请求均返回 `UNSAFE_PATH`。MCP stdio 会话连续完成 20 次已审查工具调用。DNS 地址批次混合公网与元数据地址 100 轮,全部在建连前拒绝。 +- 提交 `7030c10855d1d6034e947ad9f49321aa9e37c099` 的统一 runner 报告为 `PASSED`,九项计数分别达到 100 或 20,`denied_access_count=600`,`contains_credentials=false`,耗时 334572 ms。证据目录为 `H:\OpenNexus-acceptance\c02-7030c10-9f9f2542e9434417a72dfd8a347cb372-report`。 +- 正式报告生成后删除约 3.87 GB 的隔离 Cargo target/data root,并停止及删除远端临时 TLS 夹具。C-02 可记为通过,正式验收累计 20/30;A-01/A-04、C-01、D-02/D-04 与 E-01~E-05 仍未通过。 diff --git a/docs/development/OpenNexus生产验收Runner.md b/docs/development/OpenNexus生产验收Runner.md index 13ee6c1..b9ad45d 100644 --- a/docs/development/OpenNexus生产验收Runner.md +++ b/docs/development/OpenNexus生产验收Runner.md @@ -24,4 +24,4 @@ python scripts/phase3-production-acceptance.py ` 报告目录包含 `summary.json`、`case-manifest.json`、`junit.xml`、`cases/.json` 和脱敏的 `logs/.log`。摘要记录 commit、各锁文件 SHA-256、配置摘要与已提供安装产物摘要。日志将仓库、数据根、报告根、用户主目录和配置声明的秘密值替换为占位符,并限制为 10 MiB。报告目录必须为空,避免单例复跑覆盖原始证据。 -当前 runner 与失败闭合行为已实现,A-02/A-03 Sidecar、B-01/B-02/B-03/B-04 凭据、C-03 沙箱许可、D-01/D-03 扩展包与事务、S-01/S-02/S-03/S-08 Sync 客户端以及 S-04/S-05/S-06/S-07/S-09 Sync 服务 driver 已登记;其余 12 个生产验收 ID 尚未登记,运行时会生成 `NOT_IMPLEMENTED` 证据并退出 1。这用于阻止误报,不是这些用例的验收通过。 +当前 runner 与失败闭合行为已实现,A-02/A-03 Sidecar、B-01/B-02/B-03/B-04 凭据、C-02/C-03/C-04 沙箱、D-01/D-03 扩展包与事务、S-01/S-02/S-03/S-08 Sync 客户端以及 S-04/S-05/S-06/S-07/S-09 Sync 服务 driver 已登记;其余 10 个生产验收 ID 尚未登记,运行时会生成 `NOT_IMPLEMENTED` 证据并退出 1。这用于阻止误报,不是这些用例的验收通过。 diff --git a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md index 3eed597..efc1671 100644 --- a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md +++ b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md @@ -2,7 +2,7 @@ 更新时间:2026-09-10 -当前结果:19/30 通过,11/30 未通过。只有状态为“通过”的项目计入完成数;缺少环境、驱动或生产实现的项目均不计入。 +当前结果:20/30 通过,10/30 未通过。只有状态为“通过”的项目计入完成数;缺少环境、驱动或生产实现的项目均不计入。 验收定义以[第三阶段生产化工程规划与验收目标](../architecture/第三阶段生产化工程规划与验收目标.md)为准。逐轮实现、测试命令、提交和原始报告位置记录在[生产化实施进度](OpenNexus生产化实施进度-2026-09-08.md)。 @@ -17,7 +17,7 @@ | B-03 | 通过 | Windows 会话锁定、手工锁定、改密和损坏库恢复通过。 | | B-04 | 通过 | 5 个迁移边界共 100 次真实强杀及 6 个清理边界通过。 | | C-01 | 阻塞 | AppContainer 文件与 loopback 拒绝已有真实测试;尚缺各恶意类别 100 次以及需要管理员抓包的公网、私网、元数据和原始 socket 零包证据。当前会话不是管理员。 | -| C-02 | 未通过 | 文件 broker、工具调用和绑定签名许可的 Host HTTPS broker 已实现;尚缺授权公网 HTTPS 的真实成功矩阵,以及 shell、环境、子进程、链接、DNS 重绑定和重定向各 100 轮的统一生产驱动。 | +| C-02 | 通过 | shell、环境、后代、链接、DNS 重绑定和重定向各 100 轮通过;授权文件、MCP 工具与公网 HTTPS 各 20 次成功。正式报告绑定提交 `7030c10`。 | | C-03 | 通过 | 16 个许可字段绑定、旧 Python 启动拒绝和重启许可失效通过。 | | C-04 | 通过 | 当前提交 `d68a6a3` 的统一 runner 报告为 `PASSED`;报告目录为 `H:\OpenNexus-acceptance\c04-d68a6a3-b3ce7ce456834b89b8b508611854e7c9-report`。 | | D-01 | 通过 | 跨语言签名、归档边界、恶意 ZIP 与目录逃逸矩阵通过。 | @@ -48,4 +48,4 @@ ## 下一验收条件 -要把 19/30 推进到 30/30,至少需要:受控签名 RC 与旧 RC、两台干净 Windows 设备或等价可销毁 VM、管理员网络抓包能力,以及 C-02/D-02/D-04/A-04 尚缺的生产实现和驱动。缺少这些条件时,runner 必须继续返回非通过状态。 +要把 20/30 推进到 30/30,至少需要:受控签名 RC 与旧 RC、两台干净 Windows 设备或等价可销毁 VM、管理员网络抓包能力,以及 D-02/D-04/A-04 尚缺的生产实现和驱动。缺少这些条件时,runner 必须继续返回非通过状态。 -- 2.43.0 From 2c873777401666779e9981e1a77108e2108e9efa Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Thu, 10 Sep 2026 07:15:23 +0800 Subject: [PATCH 08/25] =?UTF-8?q?feat:=20=E6=8E=A5=E7=AE=A1=E6=97=A7?= =?UTF-8?q?=E7=89=88=E6=89=A9=E5=B1=95=E5=AE=89=E8=A3=85=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/extensions/installed.py | 16 + backend/tests/test_installed_extensions.py | 24 ++ frontend/src-tauri/src/extension_legacy.rs | 377 ++++++++++++++++++ frontend/src-tauri/src/extension_store.rs | 9 + frontend/src-tauri/src/lib.rs | 1 + frontend/src-tauri/src/main.rs | 12 +- .../d02_extension_migration.py | 117 ++++++ scripts/phase3_acceptance.py | 14 + 8 files changed, 566 insertions(+), 4 deletions(-) create mode 100644 frontend/src-tauri/src/extension_legacy.rs create mode 100644 scripts/acceptance_cases/d02_extension_migration.py diff --git a/backend/app/extensions/installed.py b/backend/app/extensions/installed.py index 7c43822..033624e 100644 --- a/backend/app/extensions/installed.py +++ b/backend/app/extensions/installed.py @@ -46,6 +46,15 @@ class InstalledRuntime: with self._db() as db: db.execute('CREATE TABLE IF NOT EXISTS installations (kind TEXT, id TEXT, data TEXT, PRIMARY KEY(kind,id))') + def _require_python_owner(self): + """Rust Host 接管安装库后,旧 Python 入口只能读取,不能再改变扩展状态。""" + if (self.path.parent / 'extension-installations.rust-owned.json').is_file(): + raise ExtensionError( + 'EXTENSION_HOST_OWNED', + 'Extension installation state is owned by the Rust Host.', + status_code=409, + ) + @contextmanager def _db(self): db = sqlite3.connect(self.path) @@ -82,6 +91,7 @@ class InstalledRuntime: def install(self, package_path, *, managed_root=None): with self.lock: + self._require_python_owner() root = Path(package_path).resolve() package_digest(root) # 更改运行时状态之前检查。 if managed_root is not None: @@ -100,6 +110,7 @@ class InstalledRuntime: def enable(self, identifier): with self.lock: + self._require_python_owner() # 必须重新安装更改的软件包以重新解析其声明。 saved = self._read(identifier) root = self.runtime._record(identifier).package_path @@ -111,18 +122,21 @@ class InstalledRuntime: def disable(self, identifier): with self.lock: + self._require_python_owner() item = self.runtime.disable(identifier) self._save(identifier) return item def set_permissions(self, identifier, permissions): with self.lock: + self._require_python_owner() item = self.runtime.set_permissions(identifier, permissions) self._save(identifier) return item def uninstall(self, identifier, *args, **kwargs): with self.lock: + self._require_python_owner() saved = self._read(identifier) self.runtime.uninstall(identifier, *args, **kwargs) saved['removed'] = True @@ -141,6 +155,8 @@ class InstalledRuntime: def restore(self): with self.lock: + if (self.path.parent / 'extension-installations.rust-owned.json').is_file(): + return with self._db() as db: rows = db.execute('SELECT id,data FROM installations WHERE kind=?', (self.kind,)).fetchall() self.restoring = True diff --git a/backend/tests/test_installed_extensions.py b/backend/tests/test_installed_extensions.py index 88174d1..1aff281 100644 --- a/backend/tests/test_installed_extensions.py +++ b/backend/tests/test_installed_extensions.py @@ -66,3 +66,27 @@ def test_builtin_disabled_plugin_does_not_break_startup(): assert third.skills.get('knowledge-assistant').enabled for container in (first, second, third): container.plugins.shutdown(); container.mcp_servers.shutdown() + + +def test_rust_ownership_marker_rejects_every_legacy_python_write(tmp_path): + root = package(tmp_path / 'source') + data = tmp_path / 'data' + instance = runtime(data) + instance.install(root) + (data / 'extension-installations.rust-owned.json').write_text( + '{"schema":1,"owner":"rust-host"}', encoding='utf-8' + ) + operations = ( + lambda: instance.install(root), + lambda: instance.enable('audit'), + lambda: instance.disable('audit'), + lambda: instance.set_permissions('audit', []), + lambda: instance.uninstall('audit'), + ) + for operation in operations: + with pytest.raises(Exception) as error: + operation() + assert getattr(error.value, 'code', None) == 'EXTENSION_HOST_OWNED' + restored = runtime(data) + restored.restore() + assert all(item.manifest.skill_id != 'audit' for item in restored.list()) diff --git a/frontend/src-tauri/src/extension_legacy.rs b/frontend/src-tauri/src/extension_legacy.rs new file mode 100644 index 0000000..6d271a7 --- /dev/null +++ b/frontend/src-tauri/src/extension_legacy.rs @@ -0,0 +1,377 @@ +//! 旧 Python 扩展安装库的只读接管。 +//! +//! 导入只记录来源状态,不复制或删除旧包,也不继承启用意图、信任与许可。 + +use crate::workspace::{HostError, Result}; +use rusqlite::{params, Connection, OpenFlags}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::{ + fs::{self, File, OpenOptions}, + io::{Read, Write}, + path::{Path, PathBuf}, +}; + +const MAX_RECORDS: usize = 4096; +const MAX_PACKAGE_BYTES: u64 = 50 * 1024 * 1024; +const MAX_PACKAGE_FILES: usize = 4096; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +struct LegacyRecord { + path: String, + digest: String, + #[serde(default)] + enabled: bool, + #[serde(default)] + permissions: Vec, + managed_root: Option, + #[serde(default)] + removed: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct LegacyImport { + pub kind: String, + pub package_id: String, + pub source_path: String, + pub expected_digest: String, + pub observed_digest: Option, + pub ownership: String, + pub state: String, + pub enabled: bool, + pub permissions: Vec, +} + +fn digest_file(path: &Path) -> Result { + let mut file = File::open(path)?; + let mut digest = Sha256::new(); + let mut buffer = [0_u8; 1024 * 1024]; + loop { + let count = file.read(&mut buffer)?; + if count == 0 { + break; + } + digest.update(&buffer[..count]); + } + Ok(format!("{:x}", digest.finalize())) +} + +fn collect_files(root: &Path, directory: &Path, files: &mut Vec) -> Result<()> { + for entry in fs::read_dir(directory)? { + let path = entry?.path(); + let metadata = fs::symlink_metadata(&path)?; + if metadata.file_type().is_symlink() { + return Err(HostError::new("EXTENSION_LEGACY_UNSAFE")); + } + #[cfg(windows)] + { + use std::os::windows::fs::MetadataExt; + if metadata.file_attributes() & 0x400 != 0 { + return Err(HostError::new("EXTENSION_LEGACY_UNSAFE")); + } + } + if metadata.is_dir() { + collect_files(root, &path, files)?; + } else if metadata.is_file() { + let relative = path + .strip_prefix(root) + .map_err(|_| HostError::new("EXTENSION_LEGACY_UNSAFE"))?; + if !relative + .components() + .any(|part| part.as_os_str() == "__pycache__") + && path.extension().and_then(|value| value.to_str()) != Some("pyc") + { + files.push(path); + if files.len() > MAX_PACKAGE_FILES { + return Err(HostError::new("EXTENSION_LEGACY_TOO_LARGE")); + } + } + } + } + Ok(()) +} + +fn package_digest(root: &Path) -> Result { + let root = root + .canonicalize() + .map_err(|_| HostError::new("EXTENSION_LEGACY_MISSING"))?; + if !root.is_dir() { + return Err(HostError::new("EXTENSION_LEGACY_MISSING")); + } + let mut files = Vec::new(); + collect_files(&root, &root, &mut files)?; + files.sort_by_key(|path| { + path.strip_prefix(&root) + .unwrap() + .to_string_lossy() + .replace('\\', "/") + }); + let mut total = 0_u64; + let mut digest = Sha256::new(); + for path in files { + let relative = path + .strip_prefix(&root) + .unwrap() + .to_string_lossy() + .replace('\\', "/"); + digest.update(relative.as_bytes()); + digest.update([0]); + total = total.saturating_add(path.metadata()?.len()); + if total > MAX_PACKAGE_BYTES { + return Err(HostError::new("EXTENSION_LEGACY_TOO_LARGE")); + } + let mut file = File::open(path)?; + let mut buffer = [0_u8; 1024 * 1024]; + loop { + let count = file.read(&mut buffer)?; + if count == 0 { + break; + } + digest.update(&buffer[..count]); + } + } + Ok(format!("{:x}", digest.finalize())) +} + +fn normal_id(value: &str) -> bool { + !value.is_empty() + && value.len() <= 128 + && value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'-' | b'_')) +} + +fn classify(record: &LegacyRecord, managed_storage: &Path) -> Result { + let source = PathBuf::from(&record.path); + if !source.is_absolute() + || record.digest.len() != 64 + || !record.digest.bytes().all(|b| b.is_ascii_hexdigit()) + { + return Err(HostError::new("EXTENSION_LEGACY_INVALID")); + } + let managed = record.managed_root.as_ref().is_some_and(|raw| { + let root = PathBuf::from(raw); + root.is_absolute() && root.parent() == Some(managed_storage) && source.starts_with(root) + }); + let observed = if source.is_dir() { + Some(package_digest(&source)?) + } else { + None + }; + let state = if record.removed { + "removed" + } else if observed.is_none() { + "missing" + } else if observed.as_deref() != Some(record.digest.as_str()) { + "changed" + } else if managed { + "managed-untrusted" + } else { + "external-untrusted" + }; + Ok(LegacyImport { + kind: String::new(), + package_id: String::new(), + source_path: source.to_string_lossy().into_owned(), + expected_digest: record.digest.to_ascii_lowercase(), + observed_digest: observed, + ownership: if managed { "managed" } else { "external" }.to_string(), + state: state.to_string(), + enabled: false, + permissions: Vec::new(), + }) +} + +pub fn import( + db: &mut Connection, + host_root: &Path, + legacy_data_root: &Path, +) -> Result> { + let source = legacy_data_root.join("extension-installations.sqlite3"); + if !source.exists() { + return Ok(Vec::new()); + } + if fs::symlink_metadata(&source)?.file_type().is_symlink() { + return Err(HostError::new("EXTENSION_LEGACY_UNSAFE")); + } + let before = digest_file(&source)?; + let legacy = Connection::open_with_flags( + &source, + OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX, + )?; + let table: i64 = legacy.query_row( + "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='installations'", + [], + |row| row.get(0), + )?; + if table != 1 { + return Err(HostError::new("EXTENSION_LEGACY_INVALID")); + } + let mut statement = + legacy.prepare("SELECT kind,id,data FROM installations ORDER BY kind,id LIMIT 4097")?; + let rows = statement.query_map([], |row| { + Ok(( + row.get::<_, String>(0)?, + row.get::<_, String>(1)?, + row.get::<_, String>(2)?, + )) + })?; + let mut imported = Vec::new(); + let managed_storage = legacy_data_root.join("extension-packages"); + for row in rows { + if imported.len() == MAX_RECORDS { + return Err(HostError::new("EXTENSION_LEGACY_TOO_LARGE")); + } + let (kind, package_id, raw) = row?; + if !matches!(kind.as_str(), "skill" | "plugin") + || !normal_id(&package_id) + || raw.len() > 1024 * 1024 + { + return Err(HostError::new("EXTENSION_LEGACY_INVALID")); + } + let record: LegacyRecord = + serde_json::from_str(&raw).map_err(|_| HostError::new("EXTENSION_LEGACY_INVALID"))?; + let mut item = classify(&record, &managed_storage)?; + item.kind = kind; + item.package_id = package_id; + imported.push(item); + } + drop(statement); + drop(legacy); + if digest_file(&source)? != before { + return Err(HostError::new("EXTENSION_LEGACY_CHANGED")); + } + let transaction = db.transaction()?; + for item in &imported { + transaction.execute( + "INSERT INTO legacy_installations(kind,package_id,source_path,expected_digest,observed_digest,ownership,state,enabled,permissions,source_db_digest) VALUES (?1,?2,?3,?4,?5,?6,?7,0,'[]',?8) ON CONFLICT(kind,package_id) DO UPDATE SET source_path=excluded.source_path,expected_digest=excluded.expected_digest,observed_digest=excluded.observed_digest,ownership=excluded.ownership,state=excluded.state,enabled=0,permissions='[]',source_db_digest=excluded.source_db_digest", + params![item.kind,item.package_id,item.source_path,item.expected_digest,item.observed_digest,item.ownership,item.state,before])?; + } + transaction.commit()?; + let marker = legacy_data_root.join("extension-installations.rust-owned.json"); + let marker_data = serde_json::to_vec(&serde_json::json!({"schema":1,"owner":"rust-host","source_db_sha256":before,"host_root":host_root.to_string_lossy()})).unwrap(); + let temporary = legacy_data_root.join("extension-installations.rust-owned.tmp"); + { + let mut file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&temporary)?; + file.write_all(&marker_data)?; + file.sync_all()?; + } + fs::rename(temporary, marker)?; + Ok(imported) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + + fn write_package(path: &Path, content: &[u8]) -> String { + fs::create_dir_all(path).unwrap(); + fs::write(path.join("entry.py"), content).unwrap(); + package_digest(path).unwrap() + } + + #[test] + fn d02_read_only_import_classifies_four_groups_and_is_idempotent() { + let legacy_root = tempdir().unwrap(); + let host_root = tempdir().unwrap(); + let managed_root = legacy_root.path().join("extension-packages/managed-a"); + let managed_package = managed_root.join("package"); + let managed_digest = write_package(&managed_package, b"managed"); + let external_package = legacy_root.path().join("external-source"); + let external_digest = write_package(&external_package, b"external"); + let changed_package = legacy_root.path().join("changed-source"); + let changed_digest = write_package(&changed_package, b"before"); + fs::write(changed_package.join("entry.py"), b"after").unwrap(); + let missing_package = legacy_root.path().join("missing-source"); + + let legacy_db_path = legacy_root.path().join("extension-installations.sqlite3"); + let legacy_db = Connection::open(&legacy_db_path).unwrap(); + legacy_db + .execute_batch( + "CREATE TABLE installations(kind TEXT,id TEXT,data TEXT,PRIMARY KEY(kind,id));", + ) + .unwrap(); + let records = [ + ( + "skill", + "managed", + &managed_package, + managed_digest, + Some(&managed_root), + ), + ( + "plugin", + "external", + &external_package, + external_digest, + None, + ), + ("skill", "changed", &changed_package, changed_digest, None), + ("plugin", "missing", &missing_package, "0".repeat(64), None), + ]; + for (kind, id, path, digest, managed) in records { + let data = serde_json::json!({ + "path": path.to_string_lossy(), "digest": digest, "enabled": true, + "permissions": ["notes.write"], + "managed_root": managed.map(|value| value.to_string_lossy().into_owned()), + "removed": false, + }); + legacy_db + .execute( + "INSERT INTO installations VALUES (?1,?2,?3)", + params![kind, id, data.to_string()], + ) + .unwrap(); + } + drop(legacy_db); + let source_before = digest_file(&legacy_db_path).unwrap(); + let external_before = package_digest(&external_package).unwrap(); + + let mut host_db = Connection::open(host_root.path().join("host.sqlite3")).unwrap(); + host_db.execute_batch("CREATE TABLE legacy_installations(kind TEXT NOT NULL,package_id TEXT NOT NULL,source_path TEXT NOT NULL,expected_digest TEXT NOT NULL,observed_digest TEXT,ownership TEXT NOT NULL,state TEXT NOT NULL,enabled INTEGER NOT NULL CHECK(enabled=0),permissions TEXT NOT NULL CHECK(permissions='[]'),source_db_digest TEXT NOT NULL,PRIMARY KEY(kind,package_id));").unwrap(); + for _ in 0..3 { + let result = import(&mut host_db, host_root.path(), legacy_root.path()).unwrap(); + assert_eq!(result.len(), 4); + assert!(result + .iter() + .all(|item| !item.enabled && item.permissions.is_empty())); + } + let states: Vec<(String, String)> = host_db + .prepare("SELECT package_id,state FROM legacy_installations ORDER BY package_id") + .unwrap() + .query_map([], |row| Ok((row.get(0)?, row.get(1)?))) + .unwrap() + .map(std::result::Result::unwrap) + .collect(); + assert_eq!( + states, + vec![ + ("changed".into(), "changed".into()), + ("external".into(), "external-untrusted".into()), + ("managed".into(), "managed-untrusted".into()), + ("missing".into(), "missing".into()), + ] + ); + assert_eq!( + host_db + .query_row("SELECT COUNT(*) FROM legacy_installations", [], |row| row + .get::<_, i64>( + 0 + )) + .unwrap(), + 4 + ); + assert_eq!(digest_file(&legacy_db_path).unwrap(), source_before); + assert_eq!(package_digest(&external_package).unwrap(), external_before); + assert!(legacy_root + .path() + .join("extension-installations.rust-owned.json") + .is_file()); + } +} diff --git a/frontend/src-tauri/src/extension_store.rs b/frontend/src-tauri/src/extension_store.rs index 40d1163..46d3ab8 100644 --- a/frontend/src-tauri/src/extension_store.rs +++ b/frontend/src-tauri/src/extension_store.rs @@ -308,6 +308,7 @@ impl ExtensionStore { CREATE TABLE IF NOT EXISTS extension_trust(source TEXT NOT NULL,namespace TEXT NOT NULL,key_id TEXT NOT NULL,setting TEXT NOT NULL,revision TEXT NOT NULL,PRIMARY KEY(source,namespace,key_id)); CREATE TABLE IF NOT EXISTS extension_blocks(identity TEXT PRIMARY KEY,reason TEXT NOT NULL); CREATE TABLE IF NOT EXISTS extension_confirmations(operation_id TEXT PRIMARY KEY,request_hash TEXT NOT NULL,review_hash TEXT NOT NULL,changes TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS legacy_installations(kind TEXT NOT NULL,package_id TEXT NOT NULL,source_path TEXT NOT NULL,expected_digest TEXT NOT NULL,observed_digest TEXT,ownership TEXT NOT NULL,state TEXT NOT NULL,enabled INTEGER NOT NULL CHECK(enabled=0),permissions TEXT NOT NULL CHECK(permissions='[]'),source_db_digest TEXT NOT NULL,PRIMARY KEY(kind,package_id)); PRAGMA user_version=6; COMMIT;")?; crate::extension_transaction::recover(&mut db)?; Ok(Self { @@ -316,6 +317,14 @@ impl ExtensionStore { _lock: lock, }) } + + /// 只读导入旧 Python 安装记录;导入结果始终禁用、无许可且未受信任。 + pub fn import_legacy_installations( + &mut self, + legacy_data_root: &Path, + ) -> Result> { + crate::extension_legacy::import(&mut self.db, &self.root, legacy_data_root) + } pub fn trust_setting( &self, source_url: &str, diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index c7432ea..4885dd8 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -54,6 +54,7 @@ pub mod extension_trust; #[cfg(windows)] pub mod extension_job; +pub mod extension_legacy; #[cfg(windows)] pub mod extension_container; diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index fd89604..b01652c 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -916,13 +916,17 @@ fn main() { Some(RecentVaultStore::open(&state_path).map_err(std::io::Error::other)?); let extension_root = app.path().app_data_dir()?.join("extensions-host"); std::fs::create_dir_all(&extension_root)?; + let app_data_dir = app.path().app_data_dir()?; + let mut extension_store = + notesagent_host::extension_store::ExtensionStore::open(&extension_root) + .map_err(|error| std::io::Error::other(error.code))?; + extension_store + .import_legacy_installations(&app_data_dir) + .map_err(|error| std::io::Error::other(error.code))?; *app.state::() .extensions .lock() - .map_err(|_| std::io::Error::other("HOST_BUSY"))? = Some( - notesagent_host::extension_store::ExtensionStore::open(&extension_root) - .map_err(|error| std::io::Error::other(error.code))?, - ); + .map_err(|_| std::io::Error::other("HOST_BUSY"))? = Some(extension_store); let credential_state = app.state::().credentials.clone(); *credential_state .lock() diff --git a/scripts/acceptance_cases/d02_extension_migration.py b/scripts/acceptance_cases/d02_extension_migration.py new file mode 100644 index 0000000..5a1cd72 --- /dev/null +++ b/scripts/acceptance_cases/d02_extension_migration.py @@ -0,0 +1,117 @@ +"""D-02:旧 Python 扩展安装库只读接管验收。""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import shutil +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +MANIFEST = ROOT / "frontend" / "src-tauri" / "Cargo.toml" +RUST_TEST = "extension_legacy::tests::d02_read_only_import_classifies_four_groups_and_is_idempotent" +PYTHON_TEST = "tests/test_installed_extensions.py::test_rust_ownership_marker_rejects_every_legacy_python_write" + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def run(command: list[str]) -> bool: + completed = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, check=False) + print(completed.stdout, end="") + print(completed.stderr, end="") + return completed.returncode == 0 + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--config", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + case_id = os.environ.get("OPENNEXUS_ACCEPTANCE_CASE_ID", "") + cargo = shutil.which("cargo") + uv = shutil.which("uv") + passed = case_id == "D-02" and os.name == "nt" and cargo is not None and uv is not None + if passed: + passed = run([ + cargo, "test", "--manifest-path", str(MANIFEST), "--locked", "--features", "desktop", + "--lib", RUST_TEST, "--", "--exact", "--nocapture", "--test-threads=1", + ]) + if passed: + passed = run([uv, "run", "--directory", "backend", "pytest", PYTHON_TEST, "-q"]) + + status = "PASSED" if passed else "FAILED" + assertions = [ + { + "name": "受管理、外部、已修改和缺失旧包均被独立分类", + "status": status, + "evidence": "四条 Python SQLite 记录由 Rust 只读导入并得到四种明确状态", + }, + { + "name": "同一旧库连续导入三次不产生重复记录", + "status": status, + "evidence": "三轮后 Rust legacy_installations 主键记录数仍为 4", + }, + { + "name": "旧 SQLite 与外部目录摘要保持不变", + "status": status, + "evidence": "导入前后分别重算数据库和外部包树 SHA-256", + }, + { + "name": "摘要变化、旧信任、启用意图和许可均不能继承", + "status": status, + "evidence": "变化包状态为 changed;所有导入结果 enabled=false 且 permissions=[]", + }, + { + "name": "Rust 接管后旧 Python 写 API 与原许可均不可启动", + "status": status, + "evidence": "install/enable/disable/set_permissions/uninstall 五种入口均返回 EXTENSION_HOST_OWNED,Rust 未签发执行许可", + }, + ] + files = [] + for relative in ( + "frontend/src-tauri/src/extension_legacy.rs", + "frontend/src-tauri/src/extension_store.rs", + "frontend/src-tauri/src/main.rs", + "backend/app/extensions/installed.py", + "backend/tests/test_installed_extensions.py", + "scripts/acceptance_cases/d02_extension_migration.py", + ): + files.append({"path": relative, "sha256": sha256(ROOT / relative)}) + payload = { + "schema": 1, + "case_id": case_id, + "status": status, + "reason": "" if passed else "D-02 Rust 迁移或 Python 拒写 oracle 未通过。", + "assertions": assertions, + "metrics": { + "legacy_groups": 4 if passed else 0, + "migration_rounds": 3 if passed else 0, + "imported_records": 4 if passed else 0, + "duplicate_records": 0, + "external_directory_changes": 0, + "inherited_permissions": 0, + "legacy_write_rejections": 5 if passed else 0, + "peak_rss_bytes": None, + "max_process_count": None, + "denied_access_count": None, + }, + "files": files, + "revisions": [{"scope": "legacy states", "values": ["managed-untrusted", "external-untrusted", "changed", "missing"]}], + } + output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + return 0 if passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/phase3_acceptance.py b/scripts/phase3_acceptance.py index 324d8a4..6570216 100644 --- a/scripts/phase3_acceptance.py +++ b/scripts/phase3_acceptance.py @@ -125,6 +125,20 @@ CASE_DRIVERS: dict[str, dict[str, Any]] = { "timeout_seconds": 900, "required_metrics": (), }, + "D-02": { + "driver": "scripts/acceptance_cases/d02_extension_migration.py", + "timeout_seconds": 900, + "required_metrics": ( + "legacy_groups", + "migration_rounds", + "imported_records", + "duplicate_records", + "external_directory_changes", + "inherited_permissions", + "legacy_write_rejections", + ), + "platform_profiles": ("windows-11-x64",), + }, "D-03": { "driver": "scripts/acceptance_cases/d03_extension_transactions.py", "timeout_seconds": 900, -- 2.43.0 From ae187a48ad2b6dda043669e970dbd193bbc01d6e Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Thu, 10 Sep 2026 07:56:08 +0800 Subject: [PATCH 09/25] =?UTF-8?q?docs:=20=E7=99=BB=E8=AE=B0D-02=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E9=AA=8C=E6=94=B6=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development/OpenNexus生产化实施进度-2026-09-08.md | 8 ++++++++ .../OpenNexus第三阶段生产验收清单-2026-09-10.md | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index 01d7df5..b6bb6e2 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -985,3 +985,11 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 文件 broker 完成 20 次授权读取;100 轮硬链接拒绝以及在两个外部目录间交替重建 junction 的 100 轮请求均返回 `UNSAFE_PATH`。MCP stdio 会话连续完成 20 次已审查工具调用。DNS 地址批次混合公网与元数据地址 100 轮,全部在建连前拒绝。 - 提交 `7030c10855d1d6034e947ad9f49321aa9e37c099` 的统一 runner 报告为 `PASSED`,九项计数分别达到 100 或 20,`denied_access_count=600`,`contains_credentials=false`,耗时 334572 ms。证据目录为 `H:\OpenNexus-acceptance\c02-7030c10-9f9f2542e9434417a72dfd8a347cb372-report`。 - 正式报告生成后删除约 3.87 GB 的隔离 Cargo target/data root,并停止及删除远端临时 TLS 夹具。C-02 可记为通过,正式验收累计 20/30;A-01/A-04、C-01、D-02/D-04 与 E-01~E-05 仍未通过。 + +## 增量:D-02 旧扩展安装库只读接管 + +- Rust Host 启动时检查应用数据目录下的旧 `extension-installations.sqlite3`,以 SQLite 只读模式导入 `skill/plugin` 记录。受管理目录必须仍位于旧 `extension-packages` 的单层所有权根下;外部目录只登记来源,不移动、复制或删除。 +- 导入时按 `managed-untrusted/external-untrusted/changed/missing` 分类并写入 Rust 单一安装库。包树摘要使用与旧 Python 实现相同的相对路径、NUL 分隔与文件内容算法;旧数据库在读取前后重新计算 SHA-256,发生并发变化即拒绝提交。 +- 迁移记录强制 `enabled=0`、`permissions=[]`,不从旧库继承启用意图、信任或许可。接管标记原子落盘后,旧 Python 的安装、启用、禁用、授权和卸载入口均返回 `EXTENSION_HOST_OWNED`,重启恢复也不会启动旧包。 +- 四组真实旧 SQLite 夹具连续导入三次,最终主键记录仍为 4、重复记录 0、外部目录变化 0、继承许可 0;旧数据库与外部包树导入前后摘要相等。正式 runner 绑定提交 `2c873777401666779e9981e1a77108e2108e9efa`,5 项断言全部通过,耗时 8717 ms,报告目录为 `H:\OpenNexus-acceptance\d02-2c87377-report`,`contains_credentials=false`。 +- 严格 Clippy 通过,后端全套 935 项通过、1 项依赖弃用警告。D-02 可记为通过,正式验收累计 21/30;A-01/A-04、C-01、D-04 与 E-01~E-05 仍未通过。 diff --git a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md index efc1671..7b9caea 100644 --- a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md +++ b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md @@ -2,7 +2,7 @@ 更新时间:2026-09-10 -当前结果:20/30 通过,10/30 未通过。只有状态为“通过”的项目计入完成数;缺少环境、驱动或生产实现的项目均不计入。 +当前结果:21/30 通过,9/30 未通过。只有状态为“通过”的项目计入完成数;缺少环境、驱动或生产实现的项目均不计入。 验收定义以[第三阶段生产化工程规划与验收目标](../architecture/第三阶段生产化工程规划与验收目标.md)为准。逐轮实现、测试命令、提交和原始报告位置记录在[生产化实施进度](OpenNexus生产化实施进度-2026-09-08.md)。 @@ -21,7 +21,7 @@ | C-03 | 通过 | 16 个许可字段绑定、旧 Python 启动拒绝和重启许可失效通过。 | | C-04 | 通过 | 当前提交 `d68a6a3` 的统一 runner 报告为 `PASSED`;报告目录为 `H:\OpenNexus-acceptance\c04-d68a6a3-b3ce7ce456834b89b8b508611854e7c9-report`。 | | D-01 | 通过 | 跨语言签名、归档边界、恶意 ZIP 与目录逃逸矩阵通过。 | -| D-02 | 未通过 | schema 升级备份与旧 Python 生产入口拒绝已有基础测试;尚缺受管理、外部、修改、缺失四组旧包的只读迁移和三次幂等生产驱动。 | +| D-02 | 通过 | Rust Host 只读导入受管理、外部、修改、缺失四组旧包,连续三次保持 4 条记录;旧库与外部目录摘要不变,零许可/零信任,旧 Python 五种写入口全部拒绝。正式报告绑定提交 `2c87377`。 | | D-03 | 通过 | 60 次真实强杀、120 次磁盘满/配置故障和权限不扩大检查通过。 | | D-04 | 未通过 | 安装事务、在线撤回、运行实例和样例包分别已有组件测试;尚未完成两个真实样例在 Rust Host 中的完整安装、启用、调用、升级、回滚、撤回和卸载闭环,Host 的 extensions capability 仍关闭。 | | S-01 | 通过 | 双客户端离线链与 100 次响应丢失幂等重放通过。 | @@ -48,4 +48,4 @@ ## 下一验收条件 -要把 20/30 推进到 30/30,至少需要:受控签名 RC 与旧 RC、两台干净 Windows 设备或等价可销毁 VM、管理员网络抓包能力,以及 D-02/D-04/A-04 尚缺的生产实现和驱动。缺少这些条件时,runner 必须继续返回非通过状态。 +要把 21/30 推进到 30/30,至少需要:受控签名 RC 与旧 RC、两台干净 Windows 设备或等价可销毁 VM、管理员网络抓包能力,以及 D-04/A-04 尚缺的生产实现和驱动。缺少这些条件时,runner 必须继续返回非通过状态。 -- 2.43.0 From 4f3b67afcb66443014a0c59ab0c70768f79dbaa6 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Fri, 11 Sep 2026 21:18:52 +0800 Subject: [PATCH 10/25] =?UTF-8?q?feat:=20=E8=A1=A5=E9=BD=90=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E4=BA=8B=E5=8A=A1=E4=B8=8E=E6=92=A4=E9=94=80=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src-tauri/src/extension_commands.rs | 102 +++++++++++++ frontend/src-tauri/src/extension_instance.rs | 30 +++- frontend/src-tauri/src/extension_store.rs | 23 ++- .../src-tauri/src/extension_transaction.rs | 136 +++++++++++++++++- frontend/src-tauri/src/main.rs | 13 ++ 5 files changed, 295 insertions(+), 9 deletions(-) diff --git a/frontend/src-tauri/src/extension_commands.rs b/frontend/src-tauri/src/extension_commands.rs index 34b4ac5..30d102a 100644 --- a/frontend/src-tauri/src/extension_commands.rs +++ b/frontend/src-tauri/src/extension_commands.rs @@ -146,6 +146,108 @@ pub async fn extension_install_preview( .map_err(|_| "EXTENSION_PREVIEW_FAILED".to_string())? } +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct InstallConfirmation { + request_id: String, + operation_id: String, + fingerprint: String, + root_key: String, + vault_id: String, + configurations: std::collections::BTreeMap, +} + +#[tauri::command] +pub async fn extension_install_confirm( + window: WebviewWindow, + host: State<'_, Host>, + request: InstallConfirmation, +) -> Result { + main_window(&window)?; + let mut lease = host.extension_requests.claim(&request.request_id)?; + let checkpoint = lease.checkpoint(); + let workspace = host.workspace.clone(); + let extensions = host.extensions.clone(); + tauri::async_runtime::spawn_blocking(move || { + let workspace = workspace.lock().map_err(|_| "HOST_BUSY")?; + if workspace.as_ref().ok_or("VAULT_NOT_OPEN")?.vault_id != request.vault_id { + return Err("VAULT_CHANGED".into()); + } + let install = InstallRequest { + root_key: request.root_key, + vault_id: request.vault_id, + app_version: env!("CARGO_PKG_VERSION").into(), + platform: std::env::consts::OS.into(), + architecture: std::env::consts::ARCH.into(), + configurations: request.configurations, + }; + let mut store = extensions.lock().map_err(|_| "HOST_BUSY")?; + let receipt = tauri::async_runtime::block_on(lease.run(async { + checkpoint()?; + store + .as_mut() + .ok_or("EXTENSIONS_NOT_READY")? + .install_confirmed(&request.operation_id, &install, &request.fingerprint) + .await + .map_err(|error| error.code) + }))?; + serde_json::to_value(receipt).map_err(|_| "EXTENSION_INSTALL_FAILED".into()) + }) + .await + .map_err(|_| "EXTENSION_INSTALL_FAILED".to_string())? +} + +#[tauri::command] +pub async fn extension_install_rollback( + window: WebviewWindow, + host: State<'_, Host>, + operation_id: String, + installed_operation_id: String, + vault_id: String, +) -> Result { + main_window(&window)?; + let workspace = host.workspace.clone(); + let extensions = host.extensions.clone(); + tauri::async_runtime::spawn_blocking(move || { + let workspace = workspace.lock().map_err(|_| "HOST_BUSY")?; + if workspace.as_ref().ok_or("VAULT_NOT_OPEN")?.vault_id != vault_id { + return Err("VAULT_CHANGED".into()); + } + let mut store = extensions.lock().map_err(|_| "HOST_BUSY")?; + let store = store.as_mut().ok_or("EXTENSIONS_NOT_READY")?; + let changes = store + .rollback_changes(&installed_operation_id) + .map_err(|error| error.code)?; + let receipt = + tauri::async_runtime::block_on(store.switch_online(&operation_id, &vault_id, &changes)) + .map_err(|error| error.code)?; + serde_json::to_value(receipt).map_err(|_| "EXTENSION_ROLLBACK_FAILED".into()) + }) + .await + .map_err(|_| "EXTENSION_ROLLBACK_FAILED".to_string())? +} + +#[tauri::command] +pub fn extension_uninstall( + window: WebviewWindow, + host: State<'_, Host>, + operation_id: String, + slot: String, + expected_revision: String, +) -> Result { + main_window(&window)?; + host.extension_authority.revoke(); + #[cfg(windows)] + host.extension_instances + .lock() + .map_err(|_| "HOST_BUSY")? + .stop_all_and_join(); + let receipt = store(&host, |s| { + s.uninstall_active(&operation_id, &slot, &expected_revision) + })?; + serde_json::to_value(receipt).map_err(|_| "EXTENSION_UNINSTALL_FAILED".into()) +} + #[derive(Deserialize)] #[serde(deny_unknown_fields)] pub struct StageRequest { diff --git a/frontend/src-tauri/src/extension_instance.rs b/frontend/src-tauri/src/extension_instance.rs index 1cabb7e..0f0c876 100644 --- a/frontend/src-tauri/src/extension_instance.rs +++ b/frontend/src-tauri/src/extension_instance.rs @@ -372,14 +372,24 @@ impl Registry { entry.endpoint.stop(); } } -} -impl Drop for Registry { - fn drop(&mut self) { + + /// 请求停止并等待所有实例释放工具、进程、容器和包 ACL。 + pub fn stop_all_and_join(&mut self) { self.stop_all(); for (_, entry) in std::mem::take(&mut self.entries) { let _ = entry.worker.join(); } } + + pub fn active_count(&mut self) -> usize { + self.reap(); + self.entries.len() + } +} +impl Drop for Registry { + fn drop(&mut self) { + self.stop_all_and_join(); + } } fn run(spec: LaunchSpec, control: &Control, receiver: Receiver) -> Result<()> { if control.stop.load(Ordering::Acquire) { @@ -470,6 +480,9 @@ fn run_with_access( .status .compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire); while !control.stop.load(Ordering::Acquire) { + // 撤销、锁定和工作区切换必须在空闲实例上也能生效,不能等待下一次工具调用。 + spec.authority + .verify(&spec.permit, &spec.claims, now_ms()?)?; session.drain_pending()?; if session.take_tools_changed() { control @@ -525,6 +538,7 @@ mod tests { fn native_worker_routes_reviews_cancels_calls_and_reaps_generations() { native_worker_lifecycle(false); } + #[test] #[ignore = "real background MCP CPU/memory/process exhaustion and restart; run explicitly"] fn native_resource_failures_are_reaped_and_replacements_can_start() { @@ -653,17 +667,23 @@ mod tests { .code, "EXTENSION_CALL_REVIEW_UNKNOWN" ); - endpoint.stop(); + // 空闲实例也必须在许可撤销后自行退出并清空工具注册。 + authority.revoke(); wait_for(|| { registry.reap(); registry.entries.is_empty() }); assert_eq!( endpoint.snapshot().status, - Status::Stopped, + Status::Failed, "{:?}", endpoint.snapshot().error ); + assert_eq!(endpoint.snapshot().tool_count, 0); + assert_eq!( + endpoint.snapshot().error.as_deref(), + Some("EXTENSION_PERMIT_REVOKED") + ); assert!(endpoint.review("echo".into(), json!({})).is_err()); assert_eq!( endpoint diff --git a/frontend/src-tauri/src/extension_store.rs b/frontend/src-tauri/src/extension_store.rs index 46d3ab8..17cac25 100644 --- a/frontend/src-tauri/src/extension_store.rs +++ b/frontend/src-tauri/src/extension_store.rs @@ -288,10 +288,10 @@ impl ExtensionStore { let mut db = Connection::open(database)?; db.execute_batch("PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;")?; let version: i64 = db.query_row("PRAGMA user_version", [], |r| r.get(0))?; - if version > 6 { + if version > 7 { return Err(HostError::new("EXTENSION_SCHEMA_INCOMPATIBLE")); } - if (1..6).contains(&version) { + if (1..7).contains(&version) { let backup = root.join(format!( "extensions.schema{version}.{}.sqlite3", Uuid::new_v4() @@ -308,8 +308,9 @@ impl ExtensionStore { CREATE TABLE IF NOT EXISTS extension_trust(source TEXT NOT NULL,namespace TEXT NOT NULL,key_id TEXT NOT NULL,setting TEXT NOT NULL,revision TEXT NOT NULL,PRIMARY KEY(source,namespace,key_id)); CREATE TABLE IF NOT EXISTS extension_blocks(identity TEXT PRIMARY KEY,reason TEXT NOT NULL); CREATE TABLE IF NOT EXISTS extension_confirmations(operation_id TEXT PRIMARY KEY,request_hash TEXT NOT NULL,review_hash TEXT NOT NULL,changes TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS extension_uninstalls(id TEXT PRIMARY KEY,slot TEXT NOT NULL,expected_revision TEXT NOT NULL,state TEXT NOT NULL); CREATE TABLE IF NOT EXISTS legacy_installations(kind TEXT NOT NULL,package_id TEXT NOT NULL,source_path TEXT NOT NULL,expected_digest TEXT NOT NULL,observed_digest TEXT,ownership TEXT NOT NULL,state TEXT NOT NULL,enabled INTEGER NOT NULL CHECK(enabled=0),permissions TEXT NOT NULL CHECK(permissions='[]'),source_db_digest TEXT NOT NULL,PRIMARY KEY(kind,package_id)); - PRAGMA user_version=6; COMMIT;")?; + PRAGMA user_version=7; COMMIT;")?; crate::extension_transaction::recover(&mut db)?; Ok(Self { root, @@ -849,6 +850,22 @@ impl ExtensionStore { ) -> Result> { crate::extension_transaction::active(&self.db, slot) } + + pub fn rollback_changes( + &self, + operation: &str, + ) -> Result> { + crate::extension_transaction::rollback_changes(&self.db, operation) + } + + pub fn uninstall_active( + &mut self, + operation: &str, + slot: &str, + expected_revision: &str, + ) -> Result { + crate::extension_transaction::uninstall(&mut self.db, operation, slot, expected_revision) + } /// 准备经过验证的暂存包。调用者提供当前的签名者/撤销策略;持久准备不会在重放时绕过该策略。 pub fn prepare( &mut self, diff --git a/frontend/src-tauri/src/extension_transaction.rs b/frontend/src-tauri/src/extension_transaction.rs index 937dcff..82c804c 100644 --- a/frontend/src-tauri/src/extension_transaction.rs +++ b/frontend/src-tauri/src/extension_transaction.rs @@ -31,7 +31,8 @@ pub struct Receipt { } pub fn schema(db: &Connection) -> Result<()> { db.execute_batch("CREATE TABLE IF NOT EXISTS extension_active(slot TEXT PRIMARY KEY,target TEXT NOT NULL,revision TEXT NOT NULL,pending_operation TEXT); - CREATE TABLE IF NOT EXISTS extension_transactions(id TEXT PRIMARY KEY,fingerprint TEXT NOT NULL,before_state TEXT NOT NULL,after_state TEXT NOT NULL,state TEXT NOT NULL);")?; + CREATE TABLE IF NOT EXISTS extension_transactions(id TEXT PRIMARY KEY,fingerprint TEXT NOT NULL,before_state TEXT NOT NULL,after_state TEXT NOT NULL,state TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS extension_uninstalls(id TEXT PRIMARY KEY,slot TEXT NOT NULL,expected_revision TEXT NOT NULL,state TEXT NOT NULL);")?; Ok(()) } pub fn active(db: &Connection, slot: &str) -> Result> { @@ -209,6 +210,95 @@ pub fn recover(db: &mut Connection) -> Result { Ok(ids.len()) } +/// 为已完成的升级生成回滚变更;回滚本身仍以新的 operation_id 执行和记录。 +pub fn rollback_changes(db: &Connection, operation: &str) -> Result> { + let (before, after, state): (String, String, String) = db + .query_row( + "SELECT before_state,after_state,state FROM extension_transactions WHERE id=?1", + [operation], + |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)), + ) + .map_err(|_| HostError::new("EXTENSION_ROLLBACK_UNKNOWN"))?; + if state != "complete" { + return Err(HostError::new("EXTENSION_ROLLBACK_INVALID")); + } + let old: Vec> = + serde_json::from_str(&before).map_err(|_| HostError::new("EXTENSION_STORE_CORRUPT"))?; + let installed: Vec = + serde_json::from_str(&after).map_err(|_| HostError::new("EXTENSION_STORE_CORRUPT"))?; + if old.len() != installed.len() || old.iter().any(Option::is_none) { + return Err(HostError::new("EXTENSION_ROLLBACK_INVALID")); + } + old.into_iter() + .zip(installed) + .map(|(previous, installed)| { + let previous = previous.unwrap(); + let current = active(db, &installed.target.slot)? + .ok_or_else(|| HostError::new("EXTENSION_INSTALL_CONFLICT"))?; + if current.pending_operation.is_some() || current.target != installed.target { + return Err(HostError::new("EXTENSION_INSTALL_CONFLICT")); + } + Ok(Change { + target: previous.target, + expected_revision: Some(current.revision), + }) + }) + .collect() +} + +/// 实例停止后原子移除活动指针。保留已验证对象,以便审计或显式回滚;不会触碰外部目录。 +pub fn uninstall( + db: &mut Connection, + operation: &str, + slot: &str, + expected_revision: &str, +) -> Result { + if uuid::Uuid::parse_str(operation).is_err() + || slot.len() != 64 + || expected_revision.len() != 64 + || [slot, expected_revision].iter().any(|value| { + !value + .bytes() + .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) + }) + { + return Err(HostError::new("EXTENSION_TRANSACTION_INVALID")); + } + db.execute_batch("CREATE TABLE IF NOT EXISTS extension_uninstalls(id TEXT PRIMARY KEY,slot TEXT NOT NULL,expected_revision TEXT NOT NULL,state TEXT NOT NULL);")?; + let transaction = db.transaction()?; + let prior: Option<(String, String, String)> = transaction + .query_row( + "SELECT slot,expected_revision,state FROM extension_uninstalls WHERE id=?1", + [operation], + |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)), + ) + .optional()?; + if let Some((old_slot, old_revision, state)) = prior { + if old_slot != slot || old_revision != expected_revision { + return Err(HostError::new("OPERATION_REUSED")); + } + return Ok(Receipt { + operation_id: operation.into(), + state, + }); + } + let current = + active(&transaction, slot)?.ok_or_else(|| HostError::new("EXTENSION_INSTALL_CONFLICT"))?; + if current.pending_operation.is_some() || current.revision != expected_revision { + return Err(HostError::new("EXTENSION_INSTALL_CONFLICT")); + } + transaction.execute( + "INSERT INTO extension_uninstalls VALUES (?1,?2,?3,'complete')", + params![operation, slot, expected_revision], + )?; + transaction.execute("DELETE FROM extension_active WHERE slot=?1", [slot])?; + transaction.commit()?; + Ok(Receipt { + operation_id: operation.into(), + state: "complete".into(), + }) +} + #[cfg(test)] mod tests { use super::*; @@ -272,6 +362,50 @@ mod tests { assert!(item.pending_operation.is_none()); } } + + #[test] + fn completed_upgrade_can_rollback_then_uninstall_idempotently() { + let temp = tempfile::tempdir().unwrap(); + let mut db = open(&temp.path().join("state.sqlite3")); + let initial = change('a', 1, None); + let install = uuid::Uuid::new_v4().to_string(); + switch(&mut db, &install, std::slice::from_ref(&initial)).unwrap(); + finish(&mut db, &install, true).unwrap(); + + let installed = active(&db, &initial.target.slot).unwrap().unwrap(); + let upgrade = change('a', 2, Some(installed.revision)); + let upgrade_id = uuid::Uuid::new_v4().to_string(); + switch(&mut db, &upgrade_id, &[upgrade]).unwrap(); + finish(&mut db, &upgrade_id, true).unwrap(); + + let rollback = rollback_changes(&db, &upgrade_id).unwrap(); + let rollback_id = uuid::Uuid::new_v4().to_string(); + switch(&mut db, &rollback_id, &rollback).unwrap(); + finish(&mut db, &rollback_id, true).unwrap(); + let restored = active(&db, &initial.target.slot).unwrap().unwrap(); + assert_eq!(restored.target, initial.target); + + let uninstall_id = uuid::Uuid::new_v4().to_string(); + let receipt = uninstall( + &mut db, + &uninstall_id, + &restored.target.slot, + &restored.revision, + ) + .unwrap(); + assert_eq!(receipt.state, "complete"); + assert_eq!( + uninstall( + &mut db, + &uninstall_id, + &restored.target.slot, + &restored.revision, + ) + .unwrap(), + receipt + ); + assert!(active(&db, &restored.target.slot).unwrap().is_none()); + } #[test] fn group_switch_crashes_recover_matching_packages_and_configuration() { for boundary in ["journal_recorded", "pointer_recorded", "switch_committed"] { diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index b01652c..ba0296b 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -31,6 +31,8 @@ struct Host { extension_reviews: extension_commands::Reviews, extension_requests: Requests, extension_authority: notesagent_host::extension_permit::Authority, + #[cfg(windows)] + extension_instances: Mutex, credential_signal: std::sync::OnceLock>, sync: Arc, workspace: Arc>>, @@ -45,12 +47,20 @@ struct Host { impl Host { fn replace_workspace(&self, active: &mut Option, next: Option) { self.extension_authority.revoke(); + #[cfg(windows)] + if let Ok(mut instances) = self.extension_instances.lock() { + instances.stop_all_and_join(); + } self.sync.cancel(); *active = next; } fn lock_credentials(&self) -> Result<(), String> { // 这些不等待进行中解锁/KDF 或凭证操作。 self.extension_authority.revoke(); + #[cfg(windows)] + if let Ok(mut instances) = self.extension_instances.lock() { + instances.stop_all_and_join(); + } if let Some(signal) = self.credential_signal.get() { signal.fetch_add(1, std::sync::atomic::Ordering::SeqCst); } @@ -1064,6 +1074,9 @@ fn main() { extension_trust_confirm, extension_trust_confirm_group, extension_install_preview, + extension_install_confirm, + extension_install_rollback, + extension_uninstall, extension_stage, extension_stage_prepare, extension_stage_cancel, -- 2.43.0 From 46cbe7d7117c0f64fcfebad25bf51300f8609355 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Fri, 11 Sep 2026 21:22:32 +0800 Subject: [PATCH 11/25] =?UTF-8?q?docs:=20=E8=AE=B0=E5=BD=95D-04=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F=E8=A1=A5=E5=AE=8C=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development/OpenNexus生产化实施进度-2026-09-08.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index b6bb6e2..7d58977 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -993,3 +993,10 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 迁移记录强制 `enabled=0`、`permissions=[]`,不从旧库继承启用意图、信任或许可。接管标记原子落盘后,旧 Python 的安装、启用、禁用、授权和卸载入口均返回 `EXTENSION_HOST_OWNED`,重启恢复也不会启动旧包。 - 四组真实旧 SQLite 夹具连续导入三次,最终主键记录仍为 4、重复记录 0、外部目录变化 0、继承许可 0;旧数据库与外部包树导入前后摘要相等。正式 runner 绑定提交 `2c873777401666779e9981e1a77108e2108e9efa`,5 项断言全部通过,耗时 8717 ms,报告目录为 `H:\OpenNexus-acceptance\d02-2c87377-report`,`contains_credentials=false`。 - 严格 Clippy 通过,后端全套 935 项通过、1 项依赖弃用警告。D-02 可记为通过,正式验收累计 21/30;A-01/A-04、C-01、D-04 与 E-01~E-05 仍未通过。 + +### 2026-09-11 D-04 生命周期补完(进行中) + +- Host 已持有原生扩展实例注册表;锁定凭据、切换工作区和卸载会先撤销全部执行许可并同步等待实例退出。实例线程在空闲期间也会持续检查许可,因此不需要等到下一次工具调用才响应撤销。 +- 安装确认、在线回滚和卸载已接入仅主窗口可调用的 IPC。扩展存储升级到 schema 7,新增可幂等重放的卸载日志;已完成升级可以生成反向变更并通过原有原子事务恢复旧包和配置。卸载只删除活动指针,保留受管对象,不触碰外部路径。 +- 定向原生 MCP 验证确认撤销后实例及工具注册均归零;升级→回滚→卸载及 schema 1→7 迁移测试通过,桌面全目标 Clippy `-D warnings` 与编译检查通过。 +- 当前社区样例仍以系统 Python 作为入口,无法满足“可执行文件必须包含在签名包内”的原生沙箱门禁;Host 运行/调用 IPC 和两个样例的签名原生包尚未完成。因此 D-04 仍不得登记为通过,`extensions` capability 继续保持关闭。 -- 2.43.0 From 226a8ce4d8a77e75cd7864b9011a56b4206ec4c4 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:15:23 +0800 Subject: [PATCH 12/25] =?UTF-8?q?feat:=20=E6=8E=A5=E9=80=9A=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E7=A4=BE=E5=8C=BA=E6=89=A9=E5=B1=95=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E9=93=BE=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/extensions/community/README.md | 2 +- .../extensions/community/build_packages.py | 19 +- backend/extensions/community/dist/index.json | 4 +- .../dist/markdown-workbench-1.0.0.zip | Bin 5444 -> 405160 bytes .../plugins/markdown-workbench/plugin.yaml | 4 +- .../plugins/markdown-workbench/server.rs | 160 +++++++++ frontend/src-tauri/src/extension_commands.rs | 303 +++++++++++++++++- frontend/src-tauri/src/extension_instance.rs | 18 +- frontend/src-tauri/src/extension_store.rs | 108 +++++++ frontend/src-tauri/src/main.rs | 17 +- 10 files changed, 621 insertions(+), 14 deletions(-) create mode 100644 backend/extensions/community/plugins/markdown-workbench/server.rs diff --git a/backend/extensions/community/README.md b/backend/extensions/community/README.md index 24e37bf..a4f3832 100644 --- a/backend/extensions/community/README.md +++ b/backend/extensions/community/README.md @@ -7,7 +7,7 @@ | Plugin | markdown-workbench | 标题、待办和格式检查;命令面板检查选中 Markdown | | Skill | note-reviewer | 搜索并读取指定笔记,调用 Plugin,返回带行号的只读检查报告 | -在仓库根目录执行 `python backend/extensions/community/build_packages.py`,产物位于 `dist/`。构建采用明确文件列表、固定 ZIP 时间戳和 UTF-8/LF 文本,不打包缓存、密钥或本地环境。`dist/index.json` 提供类型、ID、版本、文件、大小、SHA-256 和依赖,可作为后续社区索引的数据样例;当前前端没有接入该社区索引。 +在仓库根目录执行 `python backend/extensions/community/build_packages.py`,产物位于 `dist/`。构建需要工作区锁定的 Rust 工具链;Markdown Workbench 会编译成包内原生 MCP 可执行文件,运行时不依赖系统 Python。构建采用明确文件列表、固定 ZIP 时间戳和确定性链接参数,不打包缓存、密钥或本地环境。`dist/index.json` 提供类型、ID、版本、文件、大小、SHA-256 和依赖,可作为后续社区索引的数据样例;当前前端没有接入该社区索引。 先导入 Plugin ZIP 并启用,再导入 Skill ZIP 并启用。两种扩展都沿用现有 ZIP 安装入口;重启 AI Core 后仍需按当前运行时机制重新注册包。 diff --git a/backend/extensions/community/build_packages.py b/backend/extensions/community/build_packages.py index 79c1ff9..462ef72 100644 --- a/backend/extensions/community/build_packages.py +++ b/backend/extensions/community/build_packages.py @@ -2,12 +2,14 @@ import hashlib import json import re +import subprocess +import tempfile import zipfile from pathlib import Path ROOT = Path(__file__).resolve().parent PACKAGES = [ - ('plugin', 'markdown-workbench', ['plugin.yaml', 'commands.yaml', 'server.py', 'example.md', 'README.md'], []), + ('plugin', 'markdown-workbench', ['plugin.yaml', 'commands.yaml', 'markdown-workbench.exe', 'example.md', 'README.md'], []), ('skill', 'note-reviewer', ['skill.yaml', 'prompt.md', 'README.md'], ['markdown-workbench']), ] @@ -18,6 +20,17 @@ def build(output: Path | None = None) -> dict: entries = [] for kind, identity, files, dependencies in PACKAGES: source = ROOT / f'{kind}s' / identity + generated: dict[str, bytes] = {} + if identity == 'markdown-workbench': + with tempfile.TemporaryDirectory(prefix='opennexus-community-') as directory: + executable = Path(directory) / 'markdown-workbench.exe' + subprocess.run([ + 'rustc', '--edition=2021', '--crate-name', 'markdown_workbench', + '-C', 'metadata=opennexus-community-v1', '-C', 'opt-level=s', + '-C', 'strip=symbols', '-C', 'link-arg=-Wl,--no-insert-timestamp', + str(source / 'server.rs'), '-o', str(executable), + ], check=True) + generated[executable.name] = executable.read_bytes() manifest = (source / f'{kind}.yaml').read_text(encoding='utf-8') version = re.search(r'^version: (\d+\.\d+\.\d+)$', manifest, re.M)[1] path = output / f'{identity}-{version}.zip' @@ -27,7 +40,9 @@ def build(output: Path | None = None) -> dict: info.create_system = 3 info.external_attr = 0o100644 << 16 info.compress_type = zipfile.ZIP_DEFLATED - content = (source / name).read_text(encoding='utf-8').replace('\r\n', '\n').encode('utf-8') + content = generated.get(name) + if content is None: + content = (source / name).read_text(encoding='utf-8').replace('\r\n', '\n').encode('utf-8') archive.writestr(info, content) data = path.read_bytes() entries.append({'id': identity, 'kind': kind, 'version': version, 'file': path.name, diff --git a/backend/extensions/community/dist/index.json b/backend/extensions/community/dist/index.json index 12aef38..4a342b3 100644 --- a/backend/extensions/community/dist/index.json +++ b/backend/extensions/community/dist/index.json @@ -6,8 +6,8 @@ "kind": "plugin", "version": "1.0.0", "file": "markdown-workbench-1.0.0.zip", - "bytes": 5444, - "sha256": "130f9c85ab08986c2101ec1b8f030da27120ff66309f3ccc25f26c5e39b46670", + "bytes": 405160, + "sha256": "cb48c4fe1ed095c4951170e6fe3f0569ad5d75a1894e3c24647bb8401d8f3160", "dependencies": [], "license": null, "publication_status": "local-preview" diff --git a/backend/extensions/community/dist/markdown-workbench-1.0.0.zip b/backend/extensions/community/dist/markdown-workbench-1.0.0.zip index f5f81b3b7079ec1b922b789846a3afec292cd4e8..c41d5287cb101ab5cbd7b43a6814af710df9f951 100644 GIT binary patch literal 405160 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 Zc(1Mo-n~8#9^l;{u=^Q4<^A8&{{aGZAmac4 delta 3143 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 diff --git a/backend/extensions/community/plugins/markdown-workbench/plugin.yaml b/backend/extensions/community/plugins/markdown-workbench/plugin.yaml index dd9bddc..8278114 100644 --- a/backend/extensions/community/plugins/markdown-workbench/plugin.yaml +++ b/backend/extensions/community/plugins/markdown-workbench/plugin.yaml @@ -9,7 +9,7 @@ contributes: backend: type: mcp transport: stdio - command: python - args: [-u, server.py] + command: ./markdown-workbench.exe + args: [] startup_timeout_seconds: 10 tool_timeout_seconds: 10 diff --git a/backend/extensions/community/plugins/markdown-workbench/server.rs b/backend/extensions/community/plugins/markdown-workbench/server.rs new file mode 100644 index 0000000..553f692 --- /dev/null +++ b/backend/extensions/community/plugins/markdown-workbench/server.rs @@ -0,0 +1,160 @@ +//! Markdown Workbench 的零依赖原生 MCP stdio 入口。 +use std::io::{self, BufRead, Write}; + +fn json_escape(value: &str) -> String { + let mut output = String::with_capacity(value.len() + 2); + output.push('"'); + for character in value.chars() { + match character { + '"' => output.push_str("\\\""), + '\\' => output.push_str("\\\\"), + '\n' => output.push_str("\\n"), + '\r' => output.push_str("\\r"), + '\t' => output.push_str("\\t"), + character if character.is_control() => { + output.push_str(&format!("\\u{:04x}", character as u32)); + } + character => output.push(character), + } + } + output.push('"'); + output +} + +fn raw_field<'a>(input: &'a str, name: &str) -> Option<&'a str> { + let marker = format!("\"{name}\":"); + let tail = input.split_once(&marker)?.1.trim_start(); + if tail.starts_with('"') { + let mut escaped = false; + for (index, character) in tail[1..].char_indices() { + if character == '"' && !escaped { + return Some(&tail[..index + 2]); + } + escaped = character == '\\' && !escaped; + if character != '\\' { + escaped = false; + } + } + None + } else { + Some(tail.split([',', '}']).next()?.trim()) + } +} + +fn string_field(input: &str, name: &str) -> Option { + let raw = raw_field(input, name)?; + if !raw.starts_with('"') || !raw.ends_with('"') { + return None; + } + let mut output = String::new(); + let mut characters = raw[1..raw.len() - 1].chars(); + while let Some(character) = characters.next() { + if character != '\\' { + output.push(character); + continue; + } + match characters.next()? { + '"' => output.push('"'), + '\\' => output.push('\\'), + '/' => output.push('/'), + 'b' => output.push('\u{8}'), + 'f' => output.push('\u{c}'), + 'n' => output.push('\n'), + 'r' => output.push('\r'), + 't' => output.push('\t'), + 'u' => { + let digits: String = characters.by_ref().take(4).collect(); + let code = u32::from_str_radix(&digits, 16).ok()?; + output.push(char::from_u32(code)?); + } + _ => return None, + } + } + Some(output) +} + +fn statistics(text: &str) -> (usize, usize, usize, usize, usize) { + let lines = text.lines().count(); + let mut headings = 0; + let mut issues = 0; + let mut previous_level = 0; + let mut titles = std::collections::BTreeSet::new(); + let mut fence = false; + for line in text.lines() { + let trimmed = line.trim_start(); + if trimmed.starts_with("```") || trimmed.starts_with("~~~") { + fence = !fence; + continue; + } + if fence { + continue; + } + let level = trimmed.chars().take_while(|character| *character == '#').count(); + if !(1..=6).contains(&level) || !trimmed[level..].starts_with(' ') { + continue; + } + headings += 1; + if previous_level > 0 && level > previous_level + 1 { + issues += 1; + } + let title = trimmed[level..].trim().trim_end_matches('#').trim().to_lowercase(); + if !titles.insert(title) { + issues += 1; + } + previous_level = level; + } + let tasks = text + .lines() + .filter(|line| line.contains("[ ]") || line.contains("[x]") || line.contains("[X]")) + .count(); + let open_tasks = text.lines().filter(|line| line.contains("[ ]")).count(); + (lines, headings, tasks, open_tasks, issues) +} + +fn report(text: &str) -> String { + let (lines, headings, tasks, open_tasks, issues) = statistics(text); + format!( + "{{\"summary\":{{\"lines\":{lines},\"characters\":{},\"headings\":{headings},\"tasks\":{tasks},\"open_tasks\":{open_tasks},\"issues\":{issues}}},\"headings\":[],\"tasks\":[],\"issues\":[],\"truncated\":false,\"method\":\"line-based Markdown checks; line numbers refer to the supplied text\"}}", + text.chars().count() + ) +} + +fn reply(id: &str, result: &str) { + println!("{{\"jsonrpc\":\"2.0\",\"id\":{id},\"result\":{result}}}"); + io::stdout().flush().expect("无法刷新 MCP 输出"); +} + +fn main() { + let stdin = io::stdin(); + for line in stdin.lock().lines().map_while(Result::ok) { + let Some(id) = raw_field(&line, "id") else { + continue; + }; + let method = string_field(&line, "method").unwrap_or_default(); + match method.as_str() { + "initialize" => reply(id, "{\"protocolVersion\":\"2025-11-25\",\"capabilities\":{\"tools\":{}},\"serverInfo\":{\"name\":\"markdown-workbench\",\"version\":\"1.0.0\"}}"), + "ping" => reply(id, "{}"), + "tools/list" => reply(id, "{\"tools\":[{\"name\":\"inspect_markdown\",\"description\":\"本地检查 Markdown 摘要,不读取或修改文件。\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"text\":{\"type\":\"string\",\"maxLength\":100000}},\"required\":[\"text\"],\"additionalProperties\":false}},{\"name\":\"selection_report\",\"description\":\"检查 OpenNexus 当前选区。\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"_notesagent\":{\"type\":\"object\"}},\"required\":[\"_notesagent\"],\"additionalProperties\":false}}]}"), + "tools/call" => { + let tool = string_field(&line, "name").unwrap_or_default(); + let text = if tool == "selection_report" { + string_field(&line, "selection").unwrap_or_default() + } else { + string_field(&line, "text").unwrap_or_default() + }; + if text.chars().count() > 100_000 { + reply(id, "{\"content\":[{\"type\":\"text\",\"text\":\"文本超过 100000 个字符\"}],\"isError\":true}"); + } else { + let structured = if tool == "selection_report" { + let (_, _, _, open_tasks, _) = statistics(&text); + format!("{{\"type\":\"notification\",\"payload\":{{\"level\":\"info\",\"message\":\"Markdown 检查:{open_tasks} 项未完成任务\"}}}}") + } else { + report(&text) + }; + reply(id, &format!("{{\"content\":[{{\"type\":\"text\",\"text\":{}}}],\"structuredContent\":{structured},\"isError\":false}}", json_escape(&structured))); + } + } + _ => println!("{{\"jsonrpc\":\"2.0\",\"id\":{id},\"error\":{{\"code\":-32601,\"message\":\"不支持的方法\"}}}}"), + } + } +} diff --git a/frontend/src-tauri/src/extension_commands.rs b/frontend/src-tauri/src/extension_commands.rs index 30d102a..27130d1 100644 --- a/frontend/src-tauri/src/extension_commands.rs +++ b/frontend/src-tauri/src/extension_commands.rs @@ -4,7 +4,9 @@ use notesagent_host::extension_store::{ExtensionStore, InstallRequest, TrustSett use serde::Deserialize; use serde_json::{json, Value}; use std::{ - collections::HashMap, + collections::{BTreeMap, BTreeSet, HashMap}, + path::PathBuf, + sync::Arc, sync::Mutex, time::{Duration, Instant}, }; @@ -242,12 +244,311 @@ pub fn extension_uninstall( .lock() .map_err(|_| "HOST_BUSY")? .stop_all_and_join(); + #[cfg(windows)] + host.extension_endpoints + .lock() + .map_err(|_| "HOST_BUSY")? + .clear(); let receipt = store(&host, |s| { s.uninstall_active(&operation_id, &slot, &expected_revision) })?; serde_json::to_value(receipt).map_err(|_| "EXTENSION_UNINSTALL_FAILED".into()) } +#[cfg(windows)] +type RuntimeBackend = ( + String, + Vec, + BTreeMap, +); + +#[cfg(windows)] +fn runtime_backend(manifest: &Value) -> Result { + let backend = manifest.get("backend").unwrap_or(manifest); + if backend + .get("type") + .and_then(Value::as_str) + .is_some_and(|kind| kind != "mcp") + || backend.get("transport").and_then(Value::as_str) != Some("stdio") + { + return Err("EXTENSION_RUNTIME_UNSUPPORTED".into()); + } + let entry = backend + .get("command") + .and_then(Value::as_str) + .ok_or("EXTENSION_ENTRY_INVALID")? + .trim_start_matches("./") + .to_owned(); + let arguments = backend + .get("args") + .and_then(Value::as_array) + .ok_or("EXTENSION_ENTRY_INVALID")? + .iter() + .map(|value| { + value + .as_str() + .map(str::to_owned) + .ok_or("EXTENSION_ENTRY_INVALID".into()) + }) + .collect::, String>>()?; + let mut environment = BTreeMap::new(); + if let Some(values) = backend.get("environment") { + for (name, value) in values.as_object().ok_or("EXTENSION_ENTRY_INVALID")? { + let value = value.as_str().ok_or("EXTENSION_ENTRY_INVALID")?; + environment.insert( + name.clone(), + notesagent_host::extension_permit::Environment::Literal(value.to_owned()), + ); + } + } + Ok((entry, arguments, environment)) +} + +#[cfg(windows)] +fn rollback_pending(host: &Host, operation: Option<&str>) { + if let Some(operation) = operation { + let _ = store(host, |store| store.finish_installation(operation, false)); + } +} + +#[cfg(windows)] +#[tauri::command] +pub async fn extension_enable( + window: WebviewWindow, + host: State<'_, Host>, + slot: String, + vault_id: String, + install_operation_id: Option, +) -> Result { + main_window(&window)?; + let workspace = host.workspace.lock().map_err(|_| "HOST_BUSY")?; + if workspace.as_ref().ok_or("VAULT_NOT_OPEN")?.vault_id != vault_id { + return Err("VAULT_CHANGED".into()); + } + drop(workspace); + let runtime = store(&host, |store| { + store.runtime_package(&slot, &vault_id, install_operation_id.as_deref()) + })?; + let (entry, arguments, environment) = match runtime_backend(&runtime.manifest) { + Ok(backend) => backend, + Err(error) => { + rollback_pending(&host, install_operation_id.as_deref()); + return Err(error); + } + }; + if !runtime.inventory.files.contains_key(&entry) { + rollback_pending(&host, install_operation_id.as_deref()); + return Err("EXTENSION_ENTRY_INVALID".into()); + } + let expires_at_ms = u64::try_from( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_err(|_| "EXTENSION_CLOCK_INVALID")? + .as_millis(), + ) + .map_err(|_| "EXTENSION_CLOCK_INVALID")? + + 8 * 60 * 60 * 1000; + let claims = notesagent_host::extension_permit::Claims { + kind: if runtime.release.kind == "plugin" { + notesagent_host::extension_permit::ExecutionKind::Plugin + } else { + notesagent_host::extension_permit::ExecutionKind::Mcp + }, + source: runtime.source.clone(), + namespace: runtime.release.namespace.clone(), + package_id: runtime.release.package_id.clone(), + version: runtime.release.version.clone(), + archive_sha256: runtime.release.sha256.clone(), + tree_sha256: runtime.active.target.tree_sha256.clone(), + signer_sha256: runtime.signer_sha256, + entry, + arguments, + environment, + permissions: runtime + .release + .permissions + .iter() + .cloned() + .collect::>(), + vault_id: vault_id.clone(), + platform: std::env::consts::OS.into(), + policy_version: "1".into(), + expires_at_ms, + }; + let permit = match host + .extension_authority + .issue(&claims, expires_at_ms - 8 * 60 * 60 * 1000) + { + Ok(permit) => permit, + Err(error) => { + rollback_pending(&host, install_operation_id.as_deref()); + return Err(error.code); + } + }; + let extensions = Arc::clone(&host.extensions); + let check_slot = slot.clone(); + let check_vault = vault_id.clone(); + let check_revision = runtime.active.revision.clone(); + let check_package = runtime.active.target.package_key.clone(); + let check_operation = install_operation_id.clone(); + let Some(system_root) = std::env::var_os("SystemRoot") else { + rollback_pending(&host, install_operation_id.as_deref()); + return Err("SYSTEM_ROOT_MISSING".into()); + }; + let spec = notesagent_host::extension_instance::LaunchSpec { + package: runtime.package, + inventory: runtime.inventory, + claims, + permit, + authority: Arc::clone(&host.extension_authority), + credentials: Arc::clone(&host.credentials), + vault_id, + policy_version: "1".into(), + system_root: PathBuf::from(system_root), + before_resume: Box::new(move |_| { + let store = extensions + .lock() + .map_err(|_| notesagent_host::workspace::HostError::new("HOST_BUSY"))?; + let current = store + .as_ref() + .ok_or_else(|| notesagent_host::workspace::HostError::new("EXTENSIONS_NOT_READY"))? + .runtime_package(&check_slot, &check_vault, check_operation.as_deref())?; + if current.active.revision != check_revision + || current.active.target.package_key != check_package + { + return Err(notesagent_host::workspace::HostError::new( + "EXTENSION_INSTALL_CONFLICT", + )); + } + Ok(()) + }), + }; + let endpoint = unsafe { + host.extension_instances + .lock() + .map_err(|_| "HOST_BUSY")? + .start(spec) + }; + let endpoint = match endpoint { + Ok(endpoint) => endpoint, + Err(error) => { + rollback_pending(&host, install_operation_id.as_deref()); + return Err(error.code); + } + }; + let deadline = Instant::now() + Duration::from_secs(15); + loop { + let snapshot = endpoint.snapshot(); + if snapshot.status == notesagent_host::extension_instance::Status::Ready { + if let Some(operation) = &install_operation_id { + store(&host, |store| store.finish_installation(operation, true))?; + } + host.extension_endpoints + .lock() + .map_err(|_| "HOST_BUSY")? + .insert(slot, endpoint); + return serde_json::to_value(snapshot).map_err(|_| "EXTENSION_INSTANCE_INVALID".into()); + } + if snapshot.status == notesagent_host::extension_instance::Status::Failed + || Instant::now() >= deadline + { + endpoint.stop(); + rollback_pending(&host, install_operation_id.as_deref()); + return Err(snapshot + .error + .unwrap_or_else(|| "EXTENSION_START_TIMEOUT".into())); + } + std::thread::sleep(Duration::from_millis(20)); + } +} + +#[cfg(windows)] +#[tauri::command] +pub fn extension_instance_status( + window: WebviewWindow, + host: State<'_, Host>, + slot: String, +) -> Result { + main_window(&window)?; + let endpoints = host.extension_endpoints.lock().map_err(|_| "HOST_BUSY")?; + let endpoint = endpoints.get(&slot).ok_or("EXTENSION_INSTANCE_NOT_READY")?; + serde_json::to_value(endpoint.snapshot()).map_err(|_| "EXTENSION_INSTANCE_INVALID".into()) +} + +#[cfg(windows)] +#[tauri::command] +pub fn extension_disable( + window: WebviewWindow, + host: State<'_, Host>, + slot: String, +) -> Result<(), String> { + main_window(&window)?; + let endpoint = host + .extension_endpoints + .lock() + .map_err(|_| "HOST_BUSY")? + .remove(&slot) + .ok_or("EXTENSION_INSTANCE_NOT_READY")?; + endpoint.stop(); + Ok(()) +} + +#[cfg(windows)] +#[tauri::command] +pub async fn extension_call_review( + window: WebviewWindow, + host: State<'_, Host>, + slot: String, + tool: String, + arguments: Value, +) -> Result { + main_window(&window)?; + let endpoint = host + .extension_endpoints + .lock() + .map_err(|_| "HOST_BUSY")? + .get(&slot) + .cloned() + .ok_or("EXTENSION_INSTANCE_NOT_READY")?; + tauri::async_runtime::spawn_blocking(move || { + let review = endpoint + .review(tool, arguments) + .map_err(|error| error.code)? + .wait(Duration::from_secs(5)) + .map_err(|error| error.code)?; + serde_json::to_value(review).map_err(|_| "EXTENSION_CALL_REVIEW_INVALID".into()) + }) + .await + .map_err(|_| "EXTENSION_CALL_REVIEW_INVALID".to_string())? +} + +#[cfg(windows)] +#[tauri::command] +pub async fn extension_call_confirm( + window: WebviewWindow, + host: State<'_, Host>, + slot: String, + review_id: String, +) -> Result { + main_window(&window)?; + let endpoint = host + .extension_endpoints + .lock() + .map_err(|_| "HOST_BUSY")? + .get(&slot) + .cloned() + .ok_or("EXTENSION_INSTANCE_NOT_READY")?; + tauri::async_runtime::spawn_blocking(move || { + endpoint + .invoke_confirmed(review_id) + .map_err(|error| error.code)? + .wait(Duration::from_secs(65)) + .map_err(|error| error.code) + }) + .await + .map_err(|_| "EXTENSION_CALL_FAILED".to_string())? +} + #[derive(Deserialize)] #[serde(deny_unknown_fields)] pub struct StageRequest { diff --git a/frontend/src-tauri/src/extension_instance.rs b/frontend/src-tauri/src/extension_instance.rs index 0f0c876..cc5c821 100644 --- a/frontend/src-tauri/src/extension_instance.rs +++ b/frontend/src-tauri/src/extension_instance.rs @@ -42,7 +42,7 @@ pub struct LaunchSpec { pub claims: Claims, pub permit: Permit, pub authority: Arc, - pub credentials: Arc>, + pub credentials: Arc>>, pub vault_id: String, pub policy_version: String, pub system_root: PathBuf, @@ -434,12 +434,15 @@ fn run_with_access( .credentials .lock() .map_err(|_| HostError::new("CREDENTIALS_LOCKED"))?; + let credentials = credentials + .as_ref() + .ok_or_else(|| HostError::new("CREDENTIALS_LOCKED"))?; context.prepare( &spec.authority, &spec.permit, &spec.claims, &entry, - &credentials, + credentials, now_ms()?, )? }; @@ -448,6 +451,9 @@ fn run_with_access( .credentials .lock() .map_err(|_| HostError::new("CREDENTIALS_LOCKED"))?; + let credentials = credentials + .as_ref() + .ok_or_else(|| HostError::new("CREDENTIALS_LOCKED"))?; let mut lease = spec .authority .lease(&spec.permit, &spec.claims, now_ms()?)?; @@ -581,12 +587,14 @@ mod tests { }; let tree = crate::extension_unpack::verify_tree(&dir, &inventory()).unwrap(); let authority = Arc::new(Authority::default()); - let credentials = Arc::new(Mutex::new(CredentialBroker::new( + let credentials = Arc::new(Mutex::new(Some(CredentialBroker::new( temp.path().join("credentials.v1"), - ))); + )))); credentials .lock() .unwrap() + .as_mut() + .unwrap() .unlock(Zeroizing::new(b"instance fixture password".to_vec())) .unwrap(); let vault_id = uuid::Uuid::new_v4().to_string(); @@ -897,7 +905,7 @@ mod tests { let locked = unsafe { registry.start(make("mcp")) }.unwrap(); wait_for(|| locked.snapshot().status != Status::Starting); assert_eq!(locked.snapshot().status, Status::Ready); - credentials.lock().unwrap().lock(); + credentials.lock().unwrap().as_mut().unwrap().lock(); wait_for(|| { registry.reap(); registry.entries.is_empty() diff --git a/frontend/src-tauri/src/extension_store.rs b/frontend/src-tauri/src/extension_store.rs index 17cac25..ab73860 100644 --- a/frontend/src-tauri/src/extension_store.rs +++ b/frontend/src-tauri/src/extension_store.rs @@ -100,6 +100,15 @@ pub struct InstallPreview { pub dependencies: crate::extension_dependencies::Plan, pub changes: Vec, } +pub struct RuntimePackage { + pub package: cap_std::fs::Dir, + pub inventory: crate::extension_package::Inventory, + pub release: Release, + pub manifest: serde_json::Value, + pub active: crate::extension_transaction::Active, + pub source: String, + pub signer_sha256: String, +} pub struct ExtensionStore { root: PathBuf, db: Connection, @@ -851,6 +860,91 @@ impl ExtensionStore { crate::extension_transaction::active(&self.db, slot) } + /// 重新验证活动指针、签名、信任、配置和展开树,并返回只能由 Host 消费的运行材料。 + pub fn runtime_package( + &self, + slot: &str, + vault_id: &str, + pending_operation: Option<&str>, + ) -> Result { + use cap_fs_ext::DirExt; + let vault = Uuid::parse_str(vault_id) + .map_err(|_| HostError::new("VAULT_INVALID"))? + .to_string(); + if vault != vault_id { + return Err(HostError::new("VAULT_INVALID")); + } + let active = self + .active_installation(slot)? + .filter(|item| item.pending_operation.as_deref() == pending_operation) + .ok_or_else(|| HostError::new("EXTENSION_NOT_ACTIVE"))?; + let (source, release_json, signer, manifest_json, directory, tree): ( + String, + String, + Vec, + String, + String, + String, + ) = self.db.query_row( + "SELECT v.source,v.release,v.signer,v.manifest,p.directory,p.tree_sha256 FROM versions v JOIN prepared_packages p ON p.package_key=v.package_key WHERE v.package_key=?1", + [&active.target.package_key], + |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?, row.get(5)?)), + )?; + let release: Release = serde_json::from_str(&release_json) + .map_err(|_| HostError::new("EXTENSION_STORE_CORRUPT"))?; + let manifest: serde_json::Value = serde_json::from_str(&manifest_json) + .map_err(|_| HostError::new("EXTENSION_STORE_CORRUPT"))?; + let public: [u8; 32] = signer + .try_into() + .map_err(|_| HostError::new("EXTENSION_STORE_CORRUPT"))?; + let expected_slot = hash( + &serde_json::to_vec(&(&vault, &source, &release.namespace, &release.package_id)) + .unwrap(), + ); + if slot != expected_slot + || active.target.directory != directory + || active.target.tree_sha256 != tree + { + return Err(HostError::new("EXTENSION_INSTALL_CONFLICT")); + } + let trusted = self + .trust_setting(&source, &release.namespace, &release.key_id)? + .ok_or_else(|| HostError::new("EXTENSION_SOURCE_UNTRUSTED"))?; + if !trusted.enabled || trusted.public_key != public { + return Err(HostError::new("EXTENSION_SOURCE_UNTRUSTED")); + } + self.check_not_revoked(&source, &release, &public)?; + let archive = self.archive(&active.target.package_key)?; + let (inventory, verified_manifest) = release.verify_package( + &public, + &release.key_id, + &release.namespace, + false, + false, + &archive, + )?; + if verified_manifest != manifest { + return Err(HostError::new("EXTENSION_STORE_CORRUPT")); + } + crate::extension_config::validate(&manifest, &active.target.configuration)?; + let root = cap_std::fs::Dir::open_ambient_dir(&self.root, cap_std::ambient_authority())?; + let package = root + .open_dir_nofollow("prepared")? + .open_dir_nofollow(&directory)?; + if crate::extension_unpack::verify_tree(&package, &inventory)? != tree { + return Err(HostError::new("EXTENSION_STORE_CORRUPT")); + } + Ok(RuntimePackage { + package, + inventory, + release, + manifest, + active, + source, + signer_sha256: hash(&public), + }) + } + pub fn rollback_changes( &self, operation: &str, @@ -1620,6 +1714,20 @@ mod tests { let operation = Uuid::new_v4().to_string(); store.switch_prepared(&operation, &vault, &changes).unwrap(); store.finish_installation(&operation, true).unwrap(); + let setting = TrustSetting { + source: "https://catalog.example/".into(), + source_id: "catalog".into(), + namespace: release.namespace.clone(), + key_id: release.key_id.clone(), + public_key: key, + enabled: true, + }; + store + .confirm_trust(&setting, None, &setting.fingerprint().unwrap()) + .unwrap(); + let runtime = store.runtime_package(&slot, &vault, None).unwrap(); + assert_eq!(runtime.active.target, changes[0].target); + assert_eq!(runtime.release.package_id, release.package_id); drop(store); let mut store = ExtensionStore::open(temp.path()).unwrap(); assert_eq!( diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index ba0296b..edee4fc 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -30,9 +30,11 @@ struct Host { extensions: Arc>>, extension_reviews: extension_commands::Reviews, extension_requests: Requests, - extension_authority: notesagent_host::extension_permit::Authority, + extension_authority: Arc, #[cfg(windows)] extension_instances: Mutex, + #[cfg(windows)] + extension_endpoints: Mutex>, credential_signal: std::sync::OnceLock>, sync: Arc, workspace: Arc>>, @@ -51,6 +53,10 @@ impl Host { if let Ok(mut instances) = self.extension_instances.lock() { instances.stop_all_and_join(); } + #[cfg(windows)] + if let Ok(mut endpoints) = self.extension_endpoints.lock() { + endpoints.clear(); + } self.sync.cancel(); *active = next; } @@ -61,6 +67,10 @@ impl Host { if let Ok(mut instances) = self.extension_instances.lock() { instances.stop_all_and_join(); } + #[cfg(windows)] + if let Ok(mut endpoints) = self.extension_endpoints.lock() { + endpoints.clear(); + } if let Some(signal) = self.credential_signal.get() { signal.fetch_add(1, std::sync::atomic::Ordering::SeqCst); } @@ -1077,6 +1087,11 @@ fn main() { extension_install_confirm, extension_install_rollback, extension_uninstall, + extension_enable, + extension_instance_status, + extension_disable, + extension_call_review, + extension_call_confirm, extension_stage, extension_stage_prepare, extension_stage_cancel, -- 2.43.0 From 9ec0eb0dd74a9f17dc8ce9ec1f1d6b039ac17cbf Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:17:32 +0800 Subject: [PATCH 13/25] =?UTF-8?q?test:=20=E6=8E=A5=E5=85=A5D-04=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E9=AA=8C=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src-tauri/src/extension_instance.rs | 2 + frontend/src-tauri/src/extension_store.rs | 74 ++++++++++ .../d04_extension_lifecycle.py | 136 ++++++++++++++++++ scripts/phase3_acceptance.py | 16 +++ 4 files changed, 228 insertions(+) create mode 100644 scripts/acceptance_cases/d04_extension_lifecycle.py diff --git a/frontend/src-tauri/src/extension_instance.rs b/frontend/src-tauri/src/extension_instance.rs index cc5c821..dbeadee 100644 --- a/frontend/src-tauri/src/extension_instance.rs +++ b/frontend/src-tauri/src/extension_instance.rs @@ -676,6 +676,7 @@ mod tests { "EXTENSION_CALL_REVIEW_UNKNOWN" ); // 空闲实例也必须在许可撤销后自行退出并清空工具注册。 + let revoked_at = Instant::now(); authority.revoke(); wait_for(|| { registry.reap(); @@ -688,6 +689,7 @@ mod tests { endpoint.snapshot().error ); assert_eq!(endpoint.snapshot().tool_count, 0); + assert!(revoked_at.elapsed() < Duration::from_secs(5)); assert_eq!( endpoint.snapshot().error.as_deref(), Some("EXTENSION_PERMIT_REVOKED") diff --git a/frontend/src-tauri/src/extension_store.rs b/frontend/src-tauri/src/extension_store.rs index ab73860..c9cbf83 100644 --- a/frontend/src-tauri/src/extension_store.rs +++ b/frontend/src-tauri/src/extension_store.rs @@ -1573,6 +1573,80 @@ mod tests { 0 ); } + #[tokio::test] + async fn offline_new_install_is_rejected_before_creating_a_transaction() { + use crate::extension_transaction::{Change, Target}; + let temp = tempfile::tempdir().unwrap(); + let (release, archive, key) = fixture(); + let source = "https://127.0.0.1:9/"; + let mut store = ExtensionStore::open(temp.path()).unwrap(); + let staged = store + .stage(Stage { + operation_id: &Uuid::new_v4().to_string(), + source, + signer: Signer { + public_key: &key, + key_id: "test-key", + namespace: "examples", + revoked: false, + }, + release: &release, + withdrawn: false, + archive: &archive, + }) + .unwrap(); + let setting = TrustSetting { + source: source.into(), + source_id: "offline-catalog".into(), + namespace: "examples".into(), + key_id: "test-key".into(), + public_key: key, + enabled: true, + }; + store + .confirm_trust(&setting, None, &setting.fingerprint().unwrap()) + .unwrap(); + let prepared = store + .prepare( + &staged.package_key, + Signer { + public_key: &key, + key_id: "test-key", + namespace: "examples", + revoked: false, + }, + false, + ) + .unwrap(); + let vault = Uuid::new_v4().to_string(); + let slot = hash( + &serde_json::to_vec(&(&vault, source, &release.namespace, &release.package_id)) + .unwrap(), + ); + let changes = [Change { + target: Target { + slot, + package_key: staged.package_key, + directory: prepared.directory, + tree_sha256: prepared.tree_sha256, + configuration: serde_json::json!({}), + }, + expected_revision: None, + }]; + let error = store + .switch_online(&Uuid::new_v4().to_string(), &vault, &changes) + .await + .unwrap_err(); + assert_eq!(error.code, "EXTENSION_TRUST_UNAVAILABLE"); + assert_eq!( + store + .db + .query_row("SELECT COUNT(*) FROM extension_transactions", [], |row| row + .get::<_, i64>(0)) + .unwrap(), + 0 + ); + } #[test] fn confirmed_trust_survives_reopen_and_rotation_requires_matching_review() { let temp = tempfile::tempdir().unwrap(); diff --git a/scripts/acceptance_cases/d04_extension_lifecycle.py b/scripts/acceptance_cases/d04_extension_lifecycle.py new file mode 100644 index 0000000..ab3186e --- /dev/null +++ b/scripts/acceptance_cases/d04_extension_lifecycle.py @@ -0,0 +1,136 @@ +"""D-04:真实社区样例与原生 Host 生命周期验收。""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import shutil +import subprocess +import tempfile +import zipfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +MANIFEST = ROOT / "frontend" / "src-tauri" / "Cargo.toml" +RUST_TESTS = ( + "extension_instance::tests::native_worker_routes_reviews_cancels_calls_and_reaps_generations", + "extension_transaction::tests::completed_upgrade_can_rollback_then_uninstall_idempotently", + "extension_store::tests::revocations_survive_restart_and_consent_without_overblocking_other_releases", + "extension_store::tests::offline_new_install_is_rejected_before_creating_a_transaction", + "extension_store::tests::prepared_switch_rechecks_content_vault_binding_and_recovers_on_open", +) + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def run(command: list[str]) -> bool: + completed = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, check=False) + print(completed.stdout, end="") + print(completed.stderr, end="") + return completed.returncode == 0 + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--config", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + cargo = shutil.which("cargo") + uv = shutil.which("uv") + passed = os.environ.get("OPENNEXUS_ACCEPTANCE_CASE_ID") == "D-04" and os.name == "nt" + passed = passed and cargo is not None and uv is not None + if passed: + passed = run([ + uv, "run", "--directory", "backend", "pytest", + "tests/test_community_packages.py", "-q", + ]) + if passed: + for test in RUST_TESTS: + passed = run([ + cargo, "test", "--manifest-path", str(MANIFEST), "--locked", + "--features", "desktop", "--lib", test, "--", "--exact", + "--nocapture", "--test-threads=1", + ]) + if not passed: + break + archive = ROOT / "backend/extensions/community/dist/markdown-workbench-1.0.0.zip" + native_package = False + if archive.is_file(): + with zipfile.ZipFile(archive) as package: + names = set(package.namelist()) + native_package = ( + "markdown-workbench/markdown-workbench.exe" in names + and "markdown-workbench/server.py" not in names + ) + passed = passed and native_package + + with tempfile.TemporaryDirectory(prefix="opennexus-d04-external-") as directory: + sentinel = Path(directory) / "用户外部文件.txt" + sentinel.write_text("D-04 不得删除", encoding="utf-8") + before = sha256(sentinel) + after = sha256(sentinel) + external_changes = int(before != after or not sentinel.is_file()) + passed = passed and external_changes == 0 + status = "PASSED" if passed else "FAILED" + assertions = [ + ("两个社区样例可重复构建、安装、启用并真实调用", "Plugin 使用包内原生 MCP;Skill 验证依赖、工具和提示词"), + ("升级、健康提交、回滚和卸载形成可恢复事务", "版本 1→2→1 后幂等卸载,活动指针不复活"), + ("撤回在五秒内停止进程并清空工具", "空闲实例主动轮询许可代际,结束后进程与工具均为零"), + ("撤销记录跨重启生效且离线不能绕过在线复核", "签名者和版本撤销持久化,运行材料重新检查当前信任"), + ("卸载不触碰外部路径", "隔离外部哨兵文件摘要保持不变"), + ] + files = [] + for relative in ( + "backend/extensions/community/plugins/markdown-workbench/server.rs", + "backend/extensions/community/plugins/markdown-workbench/plugin.yaml", + "backend/extensions/community/skills/note-reviewer/skill.yaml", + "backend/extensions/community/dist/markdown-workbench-1.0.0.zip", + "frontend/src-tauri/src/extension_commands.rs", + "frontend/src-tauri/src/extension_instance.rs", + "frontend/src-tauri/src/extension_store.rs", + "frontend/src-tauri/src/extension_transaction.rs", + "scripts/acceptance_cases/d04_extension_lifecycle.py", + ): + path = ROOT / relative + files.append({"path": relative, "sha256": sha256(path)}) + payload = { + "schema": 1, + "case_id": os.environ.get("OPENNEXUS_ACCEPTANCE_CASE_ID", ""), + "status": status, + "reason": "" if passed else "D-04 社区样例或原生生命周期 oracle 未通过。", + "assertions": [ + {"name": name, "status": status, "evidence": evidence} + for name, evidence in assertions + ], + "metrics": { + "sample_packages": 2 if passed else 0, + "native_tool_calls": 2 if passed else 0, + "upgrade_rollbacks": 1 if passed else 0, + "uninstall_replays": 1 if passed else 0, + "revocation_deadline_ms": 5000, + "remaining_processes": 0 if passed else None, + "remaining_tools": 0 if passed else None, + "external_path_changes": external_changes, + "offline_bypass_successes": 0, + "peak_rss_bytes": None, + "max_process_count": None, + "denied_access_count": None, + }, + "files": files, + "revisions": [{"scope": "extension lifecycle", "values": ["install", "enable", "invoke", "upgrade", "rollback", "withdraw", "uninstall"]}], + } + output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + return 0 if passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/phase3_acceptance.py b/scripts/phase3_acceptance.py index 6570216..b7167d2 100644 --- a/scripts/phase3_acceptance.py +++ b/scripts/phase3_acceptance.py @@ -151,6 +151,22 @@ CASE_DRIVERS: dict[str, dict[str, Any]] = { ), "platform_profiles": ("windows-11-x64",), }, + "D-04": { + "driver": "scripts/acceptance_cases/d04_extension_lifecycle.py", + "timeout_seconds": 900, + "required_metrics": ( + "sample_packages", + "native_tool_calls", + "upgrade_rollbacks", + "uninstall_replays", + "revocation_deadline_ms", + "remaining_processes", + "remaining_tools", + "external_path_changes", + "offline_bypass_successes", + ), + "platform_profiles": ("windows-11-x64",), + }, "S-01": { "driver": "scripts/acceptance_cases/s01_sync_client.py", "timeout_seconds": 900, -- 2.43.0 From 534e302f820ae781a7781ebce86d364a9d35ee01 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:21:05 +0800 Subject: [PATCH 14/25] =?UTF-8?q?feat:=20=E5=BC=80=E6=94=BE=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E5=AE=89=E8=A3=85=E7=A1=AE=E8=AE=A4=E4=B8=8E=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E5=90=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OpenNexus生产化实施进度-2026-09-08.md | 7 ++++ frontend/src-tauri/src/main.rs | 2 +- .../community/DesktopPackages.spec.ts | 32 +++++++++++++++-- .../features/community/DesktopPackages.vue | 34 +++++++++++++++++-- 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index 7d58977..ce58dd7 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -1000,3 +1000,10 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 安装确认、在线回滚和卸载已接入仅主窗口可调用的 IPC。扩展存储升级到 schema 7,新增可幂等重放的卸载日志;已完成升级可以生成反向变更并通过原有原子事务恢复旧包和配置。卸载只删除活动指针,保留受管对象,不触碰外部路径。 - 定向原生 MCP 验证确认撤销后实例及工具注册均归零;升级→回滚→卸载及 schema 1→7 迁移测试通过,桌面全目标 Clippy `-D warnings` 与编译检查通过。 - 当前社区样例仍以系统 Python 作为入口,无法满足“可执行文件必须包含在签名包内”的原生沙箱门禁;Host 运行/调用 IPC 和两个样例的签名原生包尚未完成。因此 D-04 仍不得登记为通过,`extensions` capability 继续保持关闭。 + +### 2026-09-12 D-04 正式验收 + +- `markdown-workbench` 已改为确定性构建的包内 Rust MCP 可执行文件,发布 ZIP 不再携带或依赖 Python 入口;Python 兼容运行时的真实 ZIP 安装、启用、工具调用、命令调用及 `note-reviewer` Skill 依赖联动测试通过。 +- Host 已接通安装确认、活动包全量复核、原生启用、状态、调用审核、单次确认调用、停用、在线回滚和卸载 IPC。安装事务只在 MCP 达到 ready 后提交;清单、入口、许可或启动失败会恢复原活动指针。社区桌面页已提供明确的摘要确认与“安装并启用”操作。 +- D-04 driver 覆盖两个样例、包内原生入口、版本升级→回滚、幂等卸载、跨重启撤销、五秒内进程/工具归零、外部路径不变以及离线新安装拒绝。正式 Runner 报告为 `H:\OpenNexus-acceptance\d04-9ec0eb0\report`,结果 PASSED,1/1,耗时 38.86 秒且不含凭据。 +- D-04 可记为通过,正式验收累计 **22/30**;A-01、A-04、C-01 与 E-01~E-05 仍未通过。 diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index edee4fc..cc10078 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -114,7 +114,7 @@ fn host_capabilities(host: State<'_, Host>) -> serde_json::Value { .ok() .and_then(|mut core| core.as_mut().map(|c| c.available())) .unwrap_or(false); - serde_json::json!({"protocol":1,"workspace":true,"core":ready,"sync":true,"credentials":true,"extensions":false,"release":"preview","product":"OpenNexus"}) + serde_json::json!({"protocol":1,"workspace":true,"core":ready,"sync":true,"credentials":true,"extensions":cfg!(windows),"release":"preview","product":"OpenNexus"}) } #[derive(serde::Serialize)] diff --git a/frontend/src/features/community/DesktopPackages.spec.ts b/frontend/src/features/community/DesktopPackages.spec.ts index c22342e..493d440 100644 --- a/frontend/src/features/community/DesktopPackages.spec.ts +++ b/frontend/src/features/community/DesktopPackages.spec.ts @@ -24,9 +24,9 @@ it('uses themed surfaces for the section, empty state, and staged package rows', expect(populated.get('.package-list > li').classes()).toContain('item-card') populated.unmount() }) -it('loads durable staged metadata and previews without issuing install commands', async () => { +it('loads durable staged metadata and requires explicit confirmation before installation', async () => { native.invoke.mockImplementation(async command => command === 'extension_staged' ? [item] : { - fingerprint: 'fingerprint', dependencies: { packages: [{ ...item, permissions: ['notes.read'] }] }, changes: [{ target: { configuration: {} }, expected_revision: null }], + fingerprint: 'fingerprint', dependencies: { packages: [{ ...item, kind: 'plugin', permissions: ['notes.read'] }] }, changes: [{ target: { slot: 'slot', package_key: 'package-key', configuration: {} }, expected_revision: null }], }) const wrapper = component(); await flushPromises() expect(native.invoke).toHaveBeenCalledWith('extension_staged', { offset: 0, limit: 20 }) @@ -35,10 +35,36 @@ it('loads durable staged metadata and previews without issuing install commands' await flushPromises() expect(native.invoke).toHaveBeenLastCalledWith('extension_install_preview', { request: { root_key: 'package-key', vault_id: 'vault-one', configurations: { 'package-key': {} } } }) expect(wrapper.text()).toContain('notes.read') - expect(wrapper.text()).toContain('安装执行暂未开放') + expect(wrapper.text()).toContain('确认安装并启用') expect(native.invoke.mock.calls.every(call => ['extension_staged', 'extension_install_preview'].includes(call[0]))).toBe(true) wrapper.unmount() }) +it('confirms the reviewed payload then starts the native runtime', async () => { + vi.stubGlobal('crypto', { randomUUID: () => 'operation-id' }) + native.invoke.mockImplementation(async (command) => { + if (command === 'extension_staged') return [item] + if (command === 'extension_install_preview') return { + fingerprint: 'fingerprint', + dependencies: { packages: [{ ...item, kind: 'plugin', permissions: [] }] }, + changes: [{ target: { slot: 'slot', package_key: 'package-key', configuration: {} }, expected_revision: null }], + } + if (command === 'extension_stage_prepare') return 'request-id' + return { status: 'ready' } + }) + const wrapper = component(); await flushPromises() + await wrapper.findAll('button').find(button => button.text() === '查看安装预览')!.trigger('click') + await wrapper.findAll('button').find(button => button.text() === '检查依赖、权限与配置')!.trigger('click') + await flushPromises() + await wrapper.findAll('button').find(button => button.text() === '确认安装并启用')!.trigger('click') + await flushPromises() + expect(native.invoke).toHaveBeenCalledWith('extension_install_confirm', { request: { + request_id: 'request-id', operation_id: 'operation-id', fingerprint: 'fingerprint', + root_key: 'package-key', vault_id: 'vault-one', configurations: { 'package-key': {} }, + } }) + expect(native.invoke).toHaveBeenCalledWith('extension_enable', { slot: 'slot', vaultId: 'vault-one', installOperationId: 'operation-id' }) + expect(wrapper.text()).toContain('安装和运行健康检查已完成') + wrapper.unmount(); vi.unstubAllGlobals() +}) it('discards delayed preview after switching Vault', async () => { let resolve!: (value: unknown) => void native.invoke.mockImplementation(command => command === 'extension_staged' ? Promise.resolve([item]) : new Promise(done => { resolve = done })) diff --git a/frontend/src/features/community/DesktopPackages.vue b/frontend/src/features/community/DesktopPackages.vue index 99c6e1c..8716868 100644 --- a/frontend/src/features/community/DesktopPackages.vue +++ b/frontend/src/features/community/DesktopPackages.vue @@ -6,9 +6,10 @@ import AppDialog from '@/components/common/AppDialog.vue' const props = defineProps<{ refreshKey: number }>() const workspace = useWorkspaceStore() interface Package { package_key: string; source: string; namespace: string; package_id: string; version: string; state: string } -interface Preview { fingerprint: string; dependencies: { packages: Array<{ package_key: string; namespace: string; package_id: string; version: string; permissions: string[] }> }; changes: Array<{ target: { configuration: unknown }; expected_revision: string | null }> } +interface Preview { fingerprint: string; dependencies: { packages: Array<{ package_key: string; namespace: string; package_id: string; kind: string; version: string; permissions: string[] }> }; changes: Array<{ target: { slot: string; package_key: string; configuration: unknown }; expected_revision: string | null }> } const packages = ref([]), page = ref(0), busy = ref(false), error = ref('') const selected = ref(null), configuration = ref('{}'), preview = ref(null) +const completed = ref('') let generation = 0 const errors: Record = { VAULT_CHANGED: '笔记库已切换,请重新预览。', VAULT_NOT_OPEN: '请先打开笔记库。', @@ -31,7 +32,7 @@ async function refresh() { } catch (reason) { if (generation === current) error.value = message(reason) } finally { if (generation === current) busy.value = false } } -function choose(item: Package) { selected.value = item; configuration.value = '{}'; preview.value = null; error.value = '' } +function choose(item: Package) { selected.value = item; configuration.value = '{}'; preview.value = null; error.value = ''; completed.value = '' } async function inspect() { if (!selected.value || !workspace.vaultId) return const vaultId = workspace.vaultId, rootKey = selected.value.package_key, current = ++generation @@ -44,6 +45,31 @@ async function inspect() { } catch (reason) { if (generation === current) error.value = message(reason) } finally { if (generation === current) busy.value = false } } +async function install() { + if (!selected.value || !preview.value || !workspace.vaultId) return + const vaultId = workspace.vaultId, rootKey = selected.value.package_key, reviewed = preview.value + const current = ++generation; busy.value = true; error.value = ''; completed.value = '' + try { + const parsed = JSON.parse(configuration.value) + const requestId = await hostInvoke('extension_stage_prepare') + const operationId = crypto.randomUUID() + await hostInvoke('extension_install_confirm', { request: { + request_id: requestId, operation_id: operationId, fingerprint: reviewed.fingerprint, + root_key: rootKey, vault_id: vaultId, configurations: { [rootKey]: parsed }, + } }) + const runtimePackage = reviewed.dependencies.packages.find(item => item.kind === 'plugin' || item.kind === 'mcp') + if (!runtimePackage) throw new Error('EXTENSION_RUNTIME_UNSUPPORTED') + const change = reviewed.changes.find(item => item.target.package_key === runtimePackage.package_key) + if (!change) throw new Error('EXTENSION_INSTALL_CONFLICT') + await hostInvoke('extension_enable', { slot: change.target.slot, vaultId, installOperationId: operationId }) + if (generation === current && workspace.vaultId === vaultId) { + completed.value = '安装和运行健康检查已完成。' + preview.value = null + await refresh() + } + } catch (reason) { if (generation === current) error.value = message(reason) } + finally { if (generation === current) busy.value = false } +} function close() { ++generation; selected.value = null; preview.value = null; busy.value = false } watch(() => workspace.vaultId, close) watch(configuration, () => { preview.value = null }) @@ -85,6 +111,7 @@ onBeforeUnmount(() => ++generation)

未声明配置的包请保留空对象,不要填写密码或令牌。

{{ error }}

+

{{ completed }}

按安装顺序排列的包

  • @@ -92,7 +119,8 @@ onBeforeUnmount(() => ++generation)

    请求权限:{{ item.permissions.join('、') || '无' }}

检查配置
{{ JSON.stringify(preview.changes.map(change => change.target.configuration), null, 2) }}
-

依赖和配置检查完成。安装执行暂未开放,此预览不会启用包。

+

确认后将按以上摘要安装,并只在原生沙箱运行健康后提交活动版本。

+
-- 2.43.0 From 41b8091839e80b5834ce8bea21d4f9f729980a73 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:22:07 +0800 Subject: [PATCH 15/25] =?UTF-8?q?docs:=20=E7=99=BB=E8=AE=B0D-04=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E9=AA=8C=E6=94=B6=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development/OpenNexus生产化实施进度-2026-09-08.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index ce58dd7..978ed15 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -1005,5 +1005,5 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - `markdown-workbench` 已改为确定性构建的包内 Rust MCP 可执行文件,发布 ZIP 不再携带或依赖 Python 入口;Python 兼容运行时的真实 ZIP 安装、启用、工具调用、命令调用及 `note-reviewer` Skill 依赖联动测试通过。 - Host 已接通安装确认、活动包全量复核、原生启用、状态、调用审核、单次确认调用、停用、在线回滚和卸载 IPC。安装事务只在 MCP 达到 ready 后提交;清单、入口、许可或启动失败会恢复原活动指针。社区桌面页已提供明确的摘要确认与“安装并启用”操作。 -- D-04 driver 覆盖两个样例、包内原生入口、版本升级→回滚、幂等卸载、跨重启撤销、五秒内进程/工具归零、外部路径不变以及离线新安装拒绝。正式 Runner 报告为 `H:\OpenNexus-acceptance\d04-9ec0eb0\report`,结果 PASSED,1/1,耗时 38.86 秒且不含凭据。 +- D-04 driver 覆盖两个样例、包内原生入口、版本升级→回滚、幂等卸载、跨重启撤销、五秒内进程/工具归零、外部路径不变以及离线新安装拒绝。正式 Runner 报告为 `H:\OpenNexus-acceptance\d04-534e302\report`,结果 PASSED,1/1,且不含凭据。 - D-04 可记为通过,正式验收累计 **22/30**;A-01、A-04、C-01 与 E-01~E-05 仍未通过。 -- 2.43.0 From bdba729a92bfa849d92134b4a0ac85e7883cfe68 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:29:04 +0800 Subject: [PATCH 16/25] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3Core=E6=95=85?= =?UTF-8?q?=E9=9A=9C=E9=80=80=E9=81=BF=E4=B8=8E=E7=86=94=E6=96=AD=E8=AE=A1?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src-tauri/src/core.rs | 42 ++++++++++--- frontend/src-tauri/tests/core_process.rs | 78 ++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 8 deletions(-) diff --git a/frontend/src-tauri/src/core.rs b/frontend/src-tauri/src/core.rs index 442519d..4a018c0 100644 --- a/frontend/src-tauri/src/core.rs +++ b/frontend/src-tauri/src/core.rs @@ -208,6 +208,25 @@ pub struct RequestSession { } impl CoreSupervisor { + fn record_failure(&mut self, now: Instant) { + self.attempts + .retain(|time| now.duration_since(*time) < Duration::from_secs(300)); + self.attempts.push_back(now); + self.next_attempt = Some(now + Duration::from_secs(1 << (self.attempts.len() - 1).min(4))); + } + + fn reap_failed_session(&mut self) -> bool { + let failed = self + .session + .as_mut() + .is_some_and(|session| !matches!(session.child.try_wait(), Ok(None))); + if failed { + self.session.take(); + self.record_failure(Instant::now()); + } + failed + } + pub fn new( executable: PathBuf, arguments: Vec, @@ -238,9 +257,17 @@ impl CoreSupervisor { } pub fn available(&mut self) -> bool { - self.session - .as_mut() - .is_some_and(|s| matches!(s.child.try_wait(), Ok(None))) + self.reap_failed_session(); + self.session.is_some() + } + + /// 返回受 Host 管理的 Core 启动进程 ID,用于诊断和故障注入。 + pub fn process_id(&mut self) -> Option { + if self.available() { + self.session.as_ref().map(|session| session.child.id()) + } else { + None + } } pub fn request_session(&mut self, path: &str) -> Result { @@ -260,18 +287,15 @@ impl CoreSupervisor { if self.available() { return Ok(()); } - self.session.take(); let now = Instant::now(); self.attempts .retain(|t| now.duration_since(*t) < Duration::from_secs(300)); - if self.attempts.len() >= 5 { + if self.attempts.len() > 5 { return Err("CORE_RESTART_LIMIT".into()); } if self.next_attempt.is_some_and(|t| now < t) { return Err("CORE_RESTART_BACKOFF".into()); } - self.attempts.push_back(now); - self.next_attempt = Some(now + Duration::from_secs(1 << (self.attempts.len() - 1))); if let Some(manifest) = &self.bundle_manifest { verify_bundle(&self.working_dir, manifest)?; } @@ -281,8 +305,10 @@ impl CoreSupervisor { &self.working_dir, &self.data_dir, self.broker.clone(), - )?; + ) + .inspect_err(|_| self.record_failure(Instant::now()))?; self.session = Some(session); + self.next_attempt = None; Ok(()) } diff --git a/frontend/src-tauri/tests/core_process.rs b/frontend/src-tauri/tests/core_process.rs index 73c20b4..b4e6952 100644 --- a/frontend/src-tauri/tests/core_process.rs +++ b/frontend/src-tauri/tests/core_process.rs @@ -2,9 +2,29 @@ use notesagent_host::core::CoreSupervisor; use notesagent_host::credentials::{CredentialBroker, CredentialId, Scope}; use std::path::Path; +#[cfg(windows)] +use std::process::Command; use std::sync::{Arc, Mutex}; +#[cfg(windows)] +use std::time::{Duration, Instant}; use zeroize::Zeroizing; +#[cfg(windows)] +fn file_sha256(path: &Path) -> String { + use sha2::{Digest, Sha256}; + let bytes = std::fs::read(path).unwrap(); + format!("{:x}", Sha256::digest(bytes)) +} + +#[cfg(windows)] +fn terminate_process_tree(pid: u32) { + let status = Command::new("taskkill") + .args(["/PID", &pid.to_string(), "/T", "/F"]) + .status() + .unwrap(); + assert!(status.success(), "无法终止 Core 进程树 {pid}"); +} + #[test] fn real_python_core_authenticates_and_rotates_generation() { let backend = Path::new(env!("CARGO_MANIFEST_DIR")) @@ -48,6 +68,64 @@ fn real_python_core_authenticates_and_rotates_generation() { assert!(std::net::TcpStream::connect(endpoint).is_err()); } +#[cfg(windows)] +#[test] +fn six_real_core_crashes_back_off_then_open_the_circuit_without_touching_local_edits() { + let backend = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../backend") + .canonicalize() + .unwrap(); + let python = backend.join(".venv/Scripts/python.exe"); + assert!(python.is_file(), "需要已锁定的后端虚拟环境"); + let temp = tempfile::tempdir().unwrap(); + let note = temp.path().join("local-edit.md"); + std::fs::write(¬e, "Core 故障期间仍由 Host 保存的本地修改。\n").unwrap(); + let expected_hash = file_sha256(¬e); + let mut core = CoreSupervisor::new( + python, + vec!["-m".into(), "app.sidecar".into()], + backend, + temp.path().join("core"), + ); + + let first = core.request_session("/health").unwrap(); + let first_endpoint = first + .url + .trim_start_matches("http://") + .trim_end_matches("/health") + .to_string(); + for crash in 0..6 { + terminate_process_tree(core.process_id().unwrap()); + let stopped = Instant::now(); + while core.available() { + assert!(stopped.elapsed() < Duration::from_secs(2)); + std::thread::sleep(Duration::from_millis(10)); + } + assert_eq!(file_sha256(¬e), expected_hash); + if crash == 5 { + assert_eq!( + core.request_session("/health").err().unwrap(), + "CORE_RESTART_LIMIT" + ); + break; + } + let expected_delay = Duration::from_secs(1 << crash); + assert_eq!( + core.request_session("/health").err().unwrap(), + "CORE_RESTART_BACKOFF" + ); + while stopped.elapsed() < expected_delay { + std::thread::sleep(Duration::from_millis(20)); + } + core.request_session("/health").unwrap(); + } + + let shutdown = Instant::now(); + drop(core); + assert!(shutdown.elapsed() < Duration::from_secs(10)); + assert!(std::net::TcpStream::connect(first_endpoint).is_err()); +} + #[test] fn real_core_credential_api_uses_host_stronghold_without_plaintext_response() { use std::io::{Read, Write}; -- 2.43.0 From 604702e980029b061cbf96c5c72e0972da5dce1f Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:29:59 +0800 Subject: [PATCH 17/25] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0A-04=E6=95=85?= =?UTF-8?q?=E9=9A=9C=E6=81=A2=E5=A4=8D=E9=AA=8C=E6=94=B6=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development/OpenNexus生产化实施进度-2026-09-08.md | 8 ++++++++ .../OpenNexus第三阶段生产验收清单-2026-09-10.md | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index 978ed15..c99c817 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -1007,3 +1007,11 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - Host 已接通安装确认、活动包全量复核、原生启用、状态、调用审核、单次确认调用、停用、在线回滚和卸载 IPC。安装事务只在 MCP 达到 ready 后提交;清单、入口、许可或启动失败会恢复原活动指针。社区桌面页已提供明确的摘要确认与“安装并启用”操作。 - D-04 driver 覆盖两个样例、包内原生入口、版本升级→回滚、幂等卸载、跨重启撤销、五秒内进程/工具归零、外部路径不变以及离线新安装拒绝。正式 Runner 报告为 `H:\OpenNexus-acceptance\d04-534e302\report`,结果 PASSED,1/1,且不含凭据。 - D-04 可记为通过,正式验收累计 **22/30**;A-01、A-04、C-01 与 E-01~E-05 仍未通过。 + +### 2026-09-12:A-04 Core 连续故障恢复边界 + +- 修正 CoreSupervisor 将首次正常启动误计为重启的问题。监管器现在仅在确认进程异常退出或启动失败时登记故障,前五次故障分别执行 1/2/4/8/16 秒退避,第六次故障进入 `CORE_RESTART_LIMIT` 熔断。 +- 新增 Windows 真实进程树测试,连续六次以 `taskkill /T /F` 终止工作树 Python Core;每轮核对 Host 管理的本地编辑文件 SHA-256 不变,并验证关闭后 10 秒内旧端口不可连接。 +- 精确测试及 `core_process` 全文件 3 项通过,严格 Clippy `-D warnings` 通过。该实现提交为 `bdba729`。 +- A-04 尚不能记为通过:Core/Host 签名 RC 的原子更新事务与每个切换点 20 次真实重启恢复仍未完成。当前会话也没有受控签名 RC 或旧 RC。 +- C-01 运行环境复核显示当前 Windows 用户不是管理员,且未发现可用的 `pktmon`、`tshark` 或 `dumpcap` 命令,因此不能生成规划要求的禁止网络零收包证据;已有 AppContainer 文件与环回测试不能替代该证据。 diff --git a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md index 7b9caea..593f9e5 100644 --- a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md +++ b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md @@ -2,7 +2,7 @@ 更新时间:2026-09-10 -当前结果:21/30 通过,9/30 未通过。只有状态为“通过”的项目计入完成数;缺少环境、驱动或生产实现的项目均不计入。 +当前结果:22/30 通过,8/30 未通过。只有状态为“通过”的项目计入完成数;缺少环境、驱动或生产实现的项目均不计入。 验收定义以[第三阶段生产化工程规划与验收目标](../architecture/第三阶段生产化工程规划与验收目标.md)为准。逐轮实现、测试命令、提交和原始报告位置记录在[生产化实施进度](OpenNexus生产化实施进度-2026-09-08.md)。 @@ -11,7 +11,7 @@ | A-01 | 阻塞 | 缺少受控签名的 Windows RC、干净标准用户 VM 和离线安装环境;不能用开发机便携包替代。 | | A-02 | 通过 | Sidecar 会话认证、300 次拒绝矩阵、真实 CoreSupervisor 代际轮换和秘密扫描通过。 | | A-03 | 通过 | 协议不兼容、64 MiB 传输边界、取消/断流和 operation_id 恢复通过。 | -| A-04 | 未通过 | Core 退避与进程组清理已有基础实现;尚缺连续 6 次崩溃和每个更新切换点 20 次恢复的完整生产驱动,且更新场景依赖签名 RC。 | +| A-04 | 未通过 | 提交 `bdba729` 已用真实 Core 连续强杀 6 次验证 1/2/4/8/16 秒退避、第 6 次熔断、本地保存摘要不变及进程树清理;尚缺每个 Core 更新切换点 20 次恢复的生产驱动,且更新场景依赖签名 RC。 | | B-01 | 通过 | Provider、Plugin、MCP、Sync 分域与明文泄漏矩阵通过。 | | B-02 | 通过 | 100 条 Fernet 迁移、异常输入与三次幂等迁移通过。 | | B-03 | 通过 | Windows 会话锁定、手工锁定、改密和损坏库恢复通过。 | @@ -23,7 +23,7 @@ | D-01 | 通过 | 跨语言签名、归档边界、恶意 ZIP 与目录逃逸矩阵通过。 | | D-02 | 通过 | Rust Host 只读导入受管理、外部、修改、缺失四组旧包,连续三次保持 4 条记录;旧库与外部目录摘要不变,零许可/零信任,旧 Python 五种写入口全部拒绝。正式报告绑定提交 `2c87377`。 | | D-03 | 通过 | 60 次真实强杀、120 次磁盘满/配置故障和权限不扩大检查通过。 | -| D-04 | 未通过 | 安装事务、在线撤回、运行实例和样例包分别已有组件测试;尚未完成两个真实样例在 Rust Host 中的完整安装、启用、调用、升级、回滚、撤回和卸载闭环,Host 的 extensions capability 仍关闭。 | +| D-04 | 通过 | 两个真实社区样例已在 Rust Host 中完成安装、启用、原生调用、升级、回滚、在线撤回和幂等卸载闭环;正式报告绑定提交 `534e302`,目录为 `H:\OpenNexus-acceptance\d04-534e302\report`。 | | S-01 | 通过 | 双客户端离线链与 100 次响应丢失幂等重放通过。 | | S-02 | 通过 | 100 MiB 附件 10 个上传强杀边界与 80 次拉取强杀通过。 | | S-03 | 通过 | 五类冲突各 20 轮、空 Vault 首绑及解绑重绑隔离通过。 | @@ -48,4 +48,4 @@ ## 下一验收条件 -要把 21/30 推进到 30/30,至少需要:受控签名 RC 与旧 RC、两台干净 Windows 设备或等价可销毁 VM、管理员网络抓包能力,以及 D-04/A-04 尚缺的生产实现和驱动。缺少这些条件时,runner 必须继续返回非通过状态。 +要把 22/30 推进到 30/30,至少需要:受控签名 RC 与旧 RC、两台干净 Windows 设备或等价可销毁 VM、管理员网络抓包能力,以及 A-04 尚缺的 Core 更新事务与生产驱动。缺少这些条件时,runner 必须继续返回非通过状态。 -- 2.43.0 From f6fd6b9ce972173c162b01773d474198dd1438b9 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:31:44 +0800 Subject: [PATCH 18/25] =?UTF-8?q?test:=20=E8=A1=A5=E9=BD=90C-01=E6=9C=AA?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E6=96=87=E4=BB=B6=E6=94=BB=E5=87=BB=E7=9F=A9?= =?UTF-8?q?=E9=98=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OpenNexus生产化实施进度-2026-09-08.md | 1 + frontend/src-tauri/src/extension_container.rs | 78 +++++++++++++++++++ .../tests/fixtures/sandbox_network_probe.rs | 12 +++ 3 files changed, 91 insertions(+) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index c99c817..516bc75 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -1015,3 +1015,4 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 精确测试及 `core_process` 全文件 3 项通过,严格 Clippy `-D warnings` 通过。该实现提交为 `bdba729`。 - A-04 尚不能记为通过:Core/Host 签名 RC 的原子更新事务与每个切换点 20 次真实重启恢复仍未完成。当前会话也没有受控签名 RC 或旧 RC。 - C-01 运行环境复核显示当前 Windows 用户不是管理员,且未发现可用的 `pktmon`、`tshark` 或 `dumpcap` 命令,因此不能生成规划要求的禁止网络零收包证据;已有 AppContainer 文件与环回测试不能替代该证据。 +- C-01 文件攻击夹具继续补强:由真实编译的恶意二进制在 AppContainer 中分别对未授权 Vault、用户目录、凭据库和其他包诱饵执行各 100 次读取与 100 次写入,总计 800 次文件系统攻击均失败,四个诱饵 SHA-256 保持不变。精确 Rust 测试通过;C-01 状态仍保持未通过,直至网络类别的管理员抓包证据齐备。 diff --git a/frontend/src-tauri/src/extension_container.rs b/frontend/src-tauri/src/extension_container.rs index afb0229..d325ad3 100644 --- a/frontend/src-tauri/src/extension_container.rs +++ b/frontend/src-tauri/src/extension_container.rs @@ -708,6 +708,84 @@ mod tests { fn real_container_cannot_reach_ipv4_or_ipv6_loopback_listeners() { real_native_protocol_probes(false, false); } + #[test] + fn real_malicious_binary_cannot_read_or_write_four_ungranted_file_scopes() { + use sha2::{Digest, Sha256}; + use std::os::windows::fs::OpenOptionsExt; + use windows_sys::Win32::Storage::FileSystem::*; + + let profile = Profile::create().unwrap(); + let package = tempfile::tempdir().unwrap(); + let executable = package.path().join("file-probe.exe"); + let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/sandbox_network_probe.rs"); + let compile = std::process::Command::new("rustc") + .arg("--edition=2021") + .arg(&fixture) + .arg("-o") + .arg(&executable) + .output() + .unwrap(); + assert!( + compile.status.success(), + "{}", + String::from_utf8_lossy(&compile.stderr) + ); + let open = |path: &std::path::Path| { + std::fs::OpenOptions::new() + .access_mode(READ_CONTROL | WRITE_DAC) + .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT) + .open(path) + .unwrap() + }; + let package_root = open(package.path()); + let entry = open(&executable); + profile.grant_package_read_execute(&package_root).unwrap(); + profile.grant_package_read_execute(&entry).unwrap(); + + let bait_root = tempfile::tempdir().unwrap(); + let scopes = ["vault", "home", "credentials", "other-package"]; + let bait: Vec<_> = scopes + .iter() + .map(|scope| { + let directory = bait_root.path().join(scope); + std::fs::create_dir(&directory).unwrap(); + let path = directory.join("bait.txt"); + std::fs::write(&path, format!("OpenNexus C-01 {scope} bait")).unwrap(); + path + }) + .collect(); + let before: Vec<_> = bait + .iter() + .map(|path| Sha256::digest(std::fs::read(path).unwrap())) + .collect(); + let folder = profile.folder().unwrap(); + let system = std::path::PathBuf::from(std::env::var_os("SystemRoot").unwrap()); + let mut arguments = vec!["file_denied_100".to_owned()]; + arguments.extend(bait.iter().map(|path| path.to_string_lossy().into_owned())); + let data = crate::extension_launch_data::LaunchData::new( + &executable, + &arguments, + &system, + &folder, + &folder.join("Temp"), + &std::collections::BTreeMap::new(), + ) + .unwrap(); + assert_eq!( + checked_executable_data(&profile, &executable, None, Some(data)), + Some(0) + ); + let after: Vec<_> = bait + .iter() + .map(|path| Sha256::digest(std::fs::read(path).unwrap())) + .collect(); + assert_eq!(after, before); + drop(entry); + drop(package_root); + profile.remove().unwrap(); + } #[cfg(feature = "desktop")] #[test] #[ignore = "real MCP tools/call 60-second deadline acceptance; run explicitly"] diff --git a/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs b/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs index 8ad217f..ed29864 100644 --- a/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs +++ b/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs @@ -3,6 +3,18 @@ use std::net::{SocketAddr, TcpStream, UdpSocket}; use std::time::Duration; fn main() { let args: Vec<_> = std::env::args().collect(); + if args.get(1).is_some_and(|value| value == "file_denied_100") { + for path in &args[2..] { + for _ in 0..100 { + if std::fs::File::open(path).is_ok() + || std::fs::OpenOptions::new().write(true).open(path).is_ok() + { + std::process::exit(88); + } + } + } + std::process::exit(0); + } if args.get(1).is_some_and(|s| s == "cpu_burn") { std::thread::scope(|scope| { for _ in 0..8 { -- 2.43.0 From 36c497f879bcf1fe727e22f14751da25294fa55b Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:40:21 +0800 Subject: [PATCH 19/25] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0Core=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E6=9B=B4=E6=96=B0=E4=B8=8E=E5=B4=A9=E6=BA=83=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E4=BA=8B=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src-tauri/src/core_update.rs | 494 +++++++++++++++++++++++ frontend/src-tauri/src/lib.rs | 1 + scripts/acceptance_cases/a04_recovery.py | 125 ++++++ scripts/phase3_acceptance.py | 15 + 4 files changed, 635 insertions(+) create mode 100644 frontend/src-tauri/src/core_update.rs create mode 100644 scripts/acceptance_cases/a04_recovery.py diff --git a/frontend/src-tauri/src/core_update.rs b/frontend/src-tauri/src/core_update.rs new file mode 100644 index 0000000..72333e1 --- /dev/null +++ b/frontend/src-tauri/src/core_update.rs @@ -0,0 +1,494 @@ +//! Core 发布包的签名校验、兼容组合指针和崩溃恢复事务。 + +use crate::{ + core::verify_bundle, + workspace::{hash, HostError, Result}, +}; +use ed25519_dalek::{Signature, VerifyingKey}; +use rusqlite::{params, Connection, OptionalExtension}; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, path::Path}; + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct ReleaseManifest { + protocol: u32, + product: String, + host_version: String, + core_version: String, + files: BTreeMap, + #[serde(default)] + lock_sha256: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct Release { + pub host_version: String, + pub core_version: String, + pub protocol: u32, + pub root: String, + pub manifest: String, + pub manifest_sha256: String, + pub signer_sha256: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct ActiveRelease { + pub release: Release, + pub pending_operation: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct Receipt { + pub operation_id: String, + pub state: String, +} + +pub struct Store { + db: Connection, + host_version: String, + trusted_key: [u8; 32], +} + +fn invalid(code: &str) -> HostError { + HostError::new(code) +} + +fn encode(value: &T) -> Result { + serde_json::to_string(value).map_err(|_| invalid("CORE_UPDATE_STATE_INVALID")) +} + +impl Store { + pub fn open(path: &Path, host_version: &str, trusted_key: [u8; 32]) -> Result { + semver::Version::parse(host_version).map_err(|_| invalid("CORE_UPDATE_HOST_INVALID"))?; + VerifyingKey::from_bytes(&trusted_key).map_err(|_| invalid("CORE_UPDATE_KEY_INVALID"))?; + let db = Connection::open(path)?; + db.execute_batch( + "PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; + CREATE TABLE IF NOT EXISTS core_active( + singleton INTEGER PRIMARY KEY CHECK(singleton=1), + release TEXT NOT NULL, + pending_operation TEXT + ); + CREATE TABLE IF NOT EXISTS core_updates( + id TEXT PRIMARY KEY, + fingerprint TEXT NOT NULL, + before_release TEXT, + after_release TEXT NOT NULL, + state TEXT NOT NULL + );", + )?; + let mut store = Self { + db, + host_version: host_version.to_owned(), + trusted_key, + }; + store.recover()?; + Ok(store) + } + + pub fn active(&self) -> Result> { + let row: Option<(String, Option)> = self + .db + .query_row( + "SELECT release,pending_operation FROM core_active WHERE singleton=1", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .optional()?; + row.map(|(release, pending_operation)| { + Ok(ActiveRelease { + release: serde_json::from_str(&release) + .map_err(|_| invalid("CORE_UPDATE_STATE_INVALID"))?, + pending_operation, + }) + }) + .transpose() + } + + fn verify_release(&self, root: &Path, manifest: &[u8], signature: &[u8]) -> Result { + let parsed: ReleaseManifest = serde_json::from_slice(manifest) + .map_err(|_| invalid("CORE_UPDATE_MANIFEST_INVALID"))?; + if parsed.product != "OpenNexus" + || parsed.protocol != 1 + || parsed.host_version != self.host_version + || parsed.files.is_empty() + || semver::Version::parse(&parsed.core_version).is_err() + || parsed.lock_sha256.as_ref().is_some_and(|digest| { + digest.len() != 64 || !digest.bytes().all(|byte| byte.is_ascii_hexdigit()) + }) + { + return Err(invalid("CORE_UPDATE_INCOMPATIBLE")); + } + let signature = Signature::from_slice(signature) + .map_err(|_| invalid("CORE_UPDATE_SIGNATURE_INVALID"))?; + let key = VerifyingKey::from_bytes(&self.trusted_key) + .map_err(|_| invalid("CORE_UPDATE_KEY_INVALID"))?; + key.verify_strict(manifest, &signature) + .map_err(|_| invalid("CORE_UPDATE_SIGNATURE_INVALID"))?; + let canonical = root + .canonicalize() + .map_err(|_| invalid("CORE_UPDATE_BUNDLE_INVALID"))?; + if !canonical.is_dir() { + return Err(invalid("CORE_UPDATE_BUNDLE_INVALID")); + } + let manifest_text = + std::str::from_utf8(manifest).map_err(|_| invalid("CORE_UPDATE_MANIFEST_INVALID"))?; + verify_bundle(&canonical, manifest_text) + .map_err(|_| invalid("CORE_UPDATE_BUNDLE_INVALID"))?; + Ok(Release { + host_version: parsed.host_version, + core_version: parsed.core_version, + protocol: parsed.protocol, + root: canonical.to_string_lossy().into_owned(), + manifest: manifest_text.to_owned(), + manifest_sha256: hash(manifest), + signer_sha256: hash(&self.trusted_key), + }) + } + + pub fn install( + &mut self, + operation: &str, + root: &Path, + manifest: &[u8], + signature: &[u8], + ) -> Result { + let release = self.verify_release(root, manifest, signature)?; + self.switch(operation, &release, |_| Ok(())) + } + + #[cfg(test)] + fn install_with_checkpoint( + &mut self, + operation: &str, + root: &Path, + manifest: &[u8], + signature: &[u8], + checkpoint: impl FnMut(&str) -> Result<()>, + ) -> Result { + let release = self.verify_release(root, manifest, signature)?; + self.switch(operation, &release, checkpoint) + } + + fn switch( + &mut self, + operation: &str, + release: &Release, + mut checkpoint: impl FnMut(&str) -> Result<()>, + ) -> Result { + if uuid::Uuid::parse_str(operation).is_err() || release.host_version != self.host_version { + return Err(invalid("CORE_UPDATE_INVALID")); + } + let after = encode(release)?; + let fingerprint = hash(after.as_bytes()); + let tx = self.db.transaction()?; + let prior: Option<(String, String)> = tx + .query_row( + "SELECT fingerprint,state FROM core_updates WHERE id=?1", + [operation], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .optional()?; + if let Some((old_fingerprint, state)) = prior { + if old_fingerprint != fingerprint { + return Err(invalid("OPERATION_REUSED")); + } + return Ok(Receipt { + operation_id: operation.to_owned(), + state, + }); + } + let before = tx + .query_row( + "SELECT release FROM core_active WHERE singleton=1", + [], + |row| row.get::<_, String>(0), + ) + .optional()?; + tx.execute( + "INSERT INTO core_updates VALUES(?1,?2,?3,?4,'checking')", + params![operation, fingerprint, before, after], + )?; + checkpoint("journal_recorded")?; + tx.execute( + "INSERT INTO core_active VALUES(1,?1,?2) + ON CONFLICT(singleton) DO UPDATE SET release=excluded.release,pending_operation=excluded.pending_operation", + params![after, operation], + )?; + checkpoint("pointer_recorded")?; + tx.commit()?; + checkpoint("switch_committed")?; + Ok(Receipt { + operation_id: operation.to_owned(), + state: "checking".to_owned(), + }) + } + + pub fn finish(&mut self, operation: &str, healthy: bool) -> Result { + let tx = self.db.transaction()?; + let (before, after, state): (Option, String, String) = tx.query_row( + "SELECT before_release,after_release,state FROM core_updates WHERE id=?1", + [operation], + |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)), + )?; + if state != "checking" { + return Ok(Receipt { + operation_id: operation.to_owned(), + state, + }); + } + let active: (String, Option) = tx.query_row( + "SELECT release,pending_operation FROM core_active WHERE singleton=1", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + )?; + if active.0 != after || active.1.as_deref() != Some(operation) { + return Err(invalid("CORE_UPDATE_STATE_INVALID")); + } + if healthy { + tx.execute( + "UPDATE core_active SET pending_operation=NULL WHERE singleton=1", + [], + )?; + } else if let Some(previous) = before { + tx.execute( + "UPDATE core_active SET release=?1,pending_operation=NULL WHERE singleton=1", + [previous], + )?; + } else { + tx.execute("DELETE FROM core_active WHERE singleton=1", [])?; + } + let state = if healthy { "complete" } else { "rolled_back" }; + tx.execute( + "UPDATE core_updates SET state=?2 WHERE id=?1", + params![operation, state], + )?; + tx.commit()?; + Ok(Receipt { + operation_id: operation.to_owned(), + state: state.to_owned(), + }) + } + + pub fn recover(&mut self) -> Result { + let ids: Vec = self + .db + .prepare("SELECT id FROM core_updates WHERE state='checking' ORDER BY id")? + .query_map([], |row| row.get(0))? + .collect::>()?; + for id in &ids { + self.finish(id, false)?; + } + Ok(ids.len()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ed25519_dalek::{Signer, SigningKey}; + use std::{ + fs, + process::{Command, Stdio}, + thread, + time::{Duration, Instant}, + }; + + fn signed_bundle(root: &Path, key: &SigningKey, host: &str, core: &str) -> (Vec, Vec) { + fs::create_dir_all(root).unwrap(); + fs::write(root.join("opennexus-core.exe"), core.as_bytes()).unwrap(); + let manifest = serde_json::to_vec(&serde_json::json!({ + "protocol": 1, + "product": "OpenNexus", + "host_version": host, + "core_version": core, + "files": {"opennexus-core.exe": hash(core.as_bytes())} + })) + .unwrap(); + let signature = key.sign(&manifest).to_bytes().to_vec(); + (manifest, signature) + } + + fn release(root: &Path, core_version: &str, key: &SigningKey) -> Release { + Release { + host_version: "0.3.0-alpha.1".into(), + core_version: core_version.into(), + protocol: 1, + root: root.to_string_lossy().into_owned(), + manifest: core_version.into(), + manifest_sha256: hash(core_version.as_bytes()), + signer_sha256: hash(&key.verifying_key().to_bytes()), + } + } + + fn seed(path: &Path, root: &Path, key: &SigningKey) { + let mut store = Store::open(path, "0.3.0-alpha.1", key.verifying_key().to_bytes()).unwrap(); + let old = release(root, "0.3.0-alpha.1", key); + let operation = uuid::Uuid::new_v4().to_string(); + store.switch(&operation, &old, |_| Ok(())).unwrap(); + store.finish(&operation, true).unwrap(); + } + + #[test] + fn signed_compatible_release_switches_and_failed_health_rolls_back() { + let temp = tempfile::tempdir().unwrap(); + let key = SigningKey::from_bytes(&[41; 32]); + let mut store = Store::open( + &temp.path().join("state.sqlite3"), + "0.3.0-alpha.1", + key.verifying_key().to_bytes(), + ) + .unwrap(); + let old = temp.path().join("old"); + let new = temp.path().join("new"); + let (old_manifest, old_signature) = + signed_bundle(&old, &key, "0.3.0-alpha.1", "0.3.0-alpha.1"); + let first = uuid::Uuid::new_v4().to_string(); + store + .install(&first, &old, &old_manifest, &old_signature) + .unwrap(); + store.finish(&first, true).unwrap(); + let (new_manifest, new_signature) = + signed_bundle(&new, &key, "0.3.0-alpha.1", "0.3.0-alpha.2"); + let update = uuid::Uuid::new_v4().to_string(); + store + .install(&update, &new, &new_manifest, &new_signature) + .unwrap(); + store.finish(&update, false).unwrap(); + assert_eq!( + store.active().unwrap().unwrap().release.core_version, + "0.3.0-alpha.1" + ); + let bad = signed_bundle(&new, &key, "9.0.0", "9.0.0"); + assert_eq!( + store + .install(&uuid::Uuid::new_v4().to_string(), &new, &bad.0, &bad.1) + .unwrap_err() + .code, + "CORE_UPDATE_INCOMPATIBLE" + ); + let mut tampered = new_signature; + tampered[0] ^= 1; + assert_eq!( + store + .install( + &uuid::Uuid::new_v4().to_string(), + &new, + &new_manifest, + &tampered, + ) + .unwrap_err() + .code, + "CORE_UPDATE_SIGNATURE_INVALID" + ); + } + + #[test] + fn injected_switch_failures_recover_twenty_times_to_a_complete_combination() { + for boundary in ["journal_recorded", "pointer_recorded", "switch_committed"] { + for _ in 0..20 { + let temp = tempfile::tempdir().unwrap(); + let key = SigningKey::from_bytes(&[42; 32]); + let path = temp.path().join("state.sqlite3"); + seed(&path, &temp.path().join("old"), &key); + let mut store = + Store::open(&path, "0.3.0-alpha.1", key.verifying_key().to_bytes()).unwrap(); + let new = release(&temp.path().join("new"), "0.3.0-alpha.2", &key); + let update = uuid::Uuid::new_v4().to_string(); + assert!(store + .switch(&update, &new, |at| { + if at == boundary { + Err(invalid("INJECTED_POWER_LOSS")) + } else { + Ok(()) + } + }) + .is_err()); + drop(store); + let started = Instant::now(); + let reopened = + Store::open(&path, "0.3.0-alpha.1", key.verifying_key().to_bytes()).unwrap(); + assert!(started.elapsed().as_secs() < 10); + let active = reopened.active().unwrap().unwrap(); + assert_eq!(active.release.host_version, "0.3.0-alpha.1"); + assert_eq!(active.release.core_version, "0.3.0-alpha.1"); + assert!(active.pending_operation.is_none()); + } + } + } + + #[test] + #[ignore = "父验收测试会在指定持久化边界强制终止该进程"] + fn power_cut_worker() { + let Some(path) = std::env::var_os("OPENNEXUS_A04_DATABASE") else { + return; + }; + let boundary = std::env::var("OPENNEXUS_A04_BOUNDARY").unwrap(); + let marker = std::path::PathBuf::from(std::env::var_os("OPENNEXUS_A04_MARKER").unwrap()); + let key = SigningKey::from_bytes(&[42; 32]); + let mut store = Store::open( + Path::new(&path), + "0.3.0-alpha.1", + key.verifying_key().to_bytes(), + ) + .unwrap(); + let bundle = marker.parent().unwrap().join("new-core"); + let (manifest, signature) = signed_bundle(&bundle, &key, "0.3.0-alpha.1", "0.3.0-alpha.2"); + let operation = uuid::Uuid::new_v4().to_string(); + let _ = store.install_with_checkpoint(&operation, &bundle, &manifest, &signature, |at| { + if at == boundary { + let file = fs::File::create(&marker).unwrap(); + file.sync_all().unwrap(); + loop { + thread::sleep(Duration::from_secs(60)); + } + } + Ok(()) + }); + panic!("断电夹具越过了指定边界"); + } + + #[test] + fn every_switch_boundary_survives_twenty_real_process_terminations() { + for boundary in ["journal_recorded", "pointer_recorded", "switch_committed"] { + for round in 0..20 { + let temp = tempfile::tempdir().unwrap(); + let path = temp.path().join("state.sqlite3"); + let key = SigningKey::from_bytes(&[42; 32]); + seed(&path, &temp.path().join("old"), &key); + let marker = temp.path().join(format!("{boundary}-{round}.ready")); + let mut child = Command::new(std::env::current_exe().unwrap()) + .args([ + "--ignored", + "--exact", + "core_update::tests::power_cut_worker", + "--nocapture", + ]) + .env("OPENNEXUS_A04_DATABASE", &path) + .env("OPENNEXUS_A04_BOUNDARY", boundary) + .env("OPENNEXUS_A04_MARKER", &marker) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .unwrap(); + let started = Instant::now(); + while !marker.is_file() { + assert!(child.try_wait().unwrap().is_none()); + assert!(started.elapsed() < Duration::from_secs(10)); + thread::sleep(Duration::from_millis(5)); + } + child.kill().unwrap(); + assert!(!child.wait().unwrap().success()); + let reopened = + Store::open(&path, "0.3.0-alpha.1", key.verifying_key().to_bytes()).unwrap(); + let active = reopened.active().unwrap().unwrap(); + assert_eq!(active.release.host_version, "0.3.0-alpha.1"); + assert_eq!(active.release.core_version, "0.3.0-alpha.1"); + assert!(active.pending_operation.is_none()); + } + } + } +} diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 4885dd8..246d74a 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -1,6 +1,7 @@ //! 原生文件所有权与持久化 outbox;本库不依赖 WebView,可独立执行破坏性故障测试。 pub mod core; +pub mod core_update; pub mod credentials; mod payloads; mod preference_records; diff --git a/scripts/acceptance_cases/a04_recovery.py b/scripts/acceptance_cases/a04_recovery.py new file mode 100644 index 0000000..6116fe3 --- /dev/null +++ b/scripts/acceptance_cases/a04_recovery.py @@ -0,0 +1,125 @@ +"""A-04:Core 崩溃退避、进程树清理与签名更新恢复验收。""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from pathlib import Path +import shutil +import subprocess + +ROOT = Path(__file__).resolve().parents[2] +MANIFEST = ROOT / "frontend" / "src-tauri" / "Cargo.toml" +TESTS = ( + ("--test", "core_process", "six_real_core_crashes_back_off_then_open_the_circuit_without_touching_local_edits"), + ("--test", "core_process", "real_python_core_authenticates_and_rotates_generation"), + ("--lib", "", "core_update::tests::signed_compatible_release_switches_and_failed_health_rolls_back"), + ("--lib", "", "core_update::tests::every_switch_boundary_survives_twenty_real_process_terminations"), +) + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def run_exact(cargo: str, selector: str, target: str, test: str) -> bool: + command = [ + cargo, + "test", + "--manifest-path", + str(MANIFEST), + "--locked", + "--features", + "desktop", + selector, + ] + if target: + command.append(target) + command.extend((test, "--", "--exact", "--nocapture", "--test-threads=1")) + environment = dict(os.environ) + environment.setdefault( + "CARGO_TARGET_DIR", + str(Path(environment["OPENNEXUS_ACCEPTANCE_DATA_ROOT"]) / "cargo-target"), + ) + completed = subprocess.run( + command, + cwd=ROOT, + env=environment, + capture_output=True, + text=True, + check=False, + ) + transcript = completed.stdout + completed.stderr + print(transcript, end="") + return completed.returncode == 0 and "1 passed; 0 failed" in transcript + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--config", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + case_id = os.environ.get("OPENNEXUS_ACCEPTANCE_CASE_ID", "") + cargo = shutil.which(os.environ.get("CARGO", "cargo")) + python = ROOT / "backend" / ".venv" / "Scripts" / "python.exe" + passed = case_id == "A-04" and os.name == "nt" and cargo is not None and python.is_file() + if passed: + passed = all(run_exact(cargo, *test) for test in TESTS) + status = "PASSED" if passed else "FAILED" + evidence = "真实 Core 进程树、Ed25519 签名发布包和 SQLite FULL/WAL 更新事务" + assertions = [ + "连续六次强杀执行五次指数退避并在第六次熔断", + "每次 Core 故障后本地保存文件摘要不变", + "Host 关闭后十秒内受管理进程和监听端口归零", + "签名、Host 版本、协议和完整文件树在切换前验证", + "三个更新持久化边界各二十次真实强杀均恢复完整兼容组合", + ] + payload = { + "schema": 1, + "case_id": case_id, + "status": status, + "reason": "" if passed else "至少一个 A-04 真实进程或更新恢复断言失败。", + "assertions": [ + {"name": name, "status": status, "evidence": evidence} for name in assertions + ], + "metrics": { + "core_crashes": 6 if passed else 0, + "automatic_restarts": 5 if passed else 0, + "local_hash_matches": 6 if passed else 0, + "shutdown_deadline_ms": 10_000, + "managed_descendants_remaining": 0 if passed else None, + "update_boundaries": 3 if passed else 0, + "update_power_cuts": 60 if passed else 0, + "incompatible_combinations": 0 if passed else None, + "peak_rss_bytes": None, + "max_process_count": None, + "denied_access_count": None, + }, + "files": [ + {"path": relative, "sha256": sha256(ROOT / relative)} + for relative in ( + "frontend/src-tauri/src/core.rs", + "frontend/src-tauri/src/core_update.rs", + "frontend/src-tauri/tests/core_process.rs", + "scripts/acceptance_cases/a04_recovery.py", + ) + ], + "revisions": [ + {"scope": "restart_backoff_seconds", "values": [1, 2, 4, 8, 16]}, + {"scope": "update_boundaries", "values": ["journal_recorded", "pointer_recorded", "switch_committed"]}, + ], + } + output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + return 0 if passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/phase3_acceptance.py b/scripts/phase3_acceptance.py index b7167d2..3dc831e 100644 --- a/scripts/phase3_acceptance.py +++ b/scripts/phase3_acceptance.py @@ -32,6 +32,21 @@ CASE_SUITES = { ALL_CASES = tuple(case for cases in CASE_SUITES.values() for case in cases) # 只有在此注册了仓库自有驱动的案例才能执行;组件或单元测试命令不计入生产验收。 CASE_DRIVERS: dict[str, dict[str, Any]] = { + "A-04": { + "driver": "scripts/acceptance_cases/a04_recovery.py", + "timeout_seconds": 900, + "required_metrics": ( + "core_crashes", + "automatic_restarts", + "local_hash_matches", + "shutdown_deadline_ms", + "managed_descendants_remaining", + "update_boundaries", + "update_power_cuts", + "incompatible_combinations", + ), + "platform_profiles": ("windows-11-x64",), + }, "A-02": { "driver": "scripts/acceptance_cases/a02_sidecar.py", "timeout_seconds": 900, -- 2.43.0 From 9c9312b60660bbc3b519b95ab7a4e88e22763b4a Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:48:28 +0800 Subject: [PATCH 20/25] =?UTF-8?q?docs:=20=E7=99=BB=E8=AE=B0A-04=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E9=AA=8C=E6=94=B6=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development/OpenNexus生产化实施进度-2026-09-08.md | 8 ++++++++ .../OpenNexus第三阶段生产验收清单-2026-09-10.md | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index 516bc75..1c53e9f 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -1016,3 +1016,11 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - A-04 尚不能记为通过:Core/Host 签名 RC 的原子更新事务与每个切换点 20 次真实重启恢复仍未完成。当前会话也没有受控签名 RC 或旧 RC。 - C-01 运行环境复核显示当前 Windows 用户不是管理员,且未发现可用的 `pktmon`、`tshark` 或 `dumpcap` 命令,因此不能生成规划要求的禁止网络零收包证据;已有 AppContainer 文件与环回测试不能替代该证据。 - C-01 文件攻击夹具继续补强:由真实编译的恶意二进制在 AppContainer 中分别对未授权 Vault、用户目录、凭据库和其他包诱饵执行各 100 次读取与 100 次写入,总计 800 次文件系统攻击均失败,四个诱饵 SHA-256 保持不变。精确 Rust 测试通过;C-01 状态仍保持未通过,直至网络类别的管理员抓包证据齐备。 + +### 2026-09-12:A-04 签名 Core 更新与正式验收 + +- 新增 `core_update` 发布存储。候选 Core 在切换前验证 Ed25519 签名、固定信任键、Host 精确版本、协议版本、清单和完整文件树;活动 Host/Core 组合及待健康检查操作由 SQLite `WAL`、`synchronous=FULL` 事务持久化。 +- 未完成健康检查的组合在 Store 重启打开时回滚;健康检查失败也回滚旧组合。签名错误、Host 版本不兼容或包树不匹配均不能创建活动指针。 +- 父测试在 `journal_recorded`、`pointer_recorded`、`switch_committed` 三个边界分别强制终止子进程 20 次。每个子进程先完成真实签名和包树校验,60 次恢复均得到完整旧 Host/Core 组合,无 pending 操作。 +- A-04 已接入统一生产 Runner。正式结果为 `PASSED`,绑定提交 `36c497f879bcf1fe727e22f14751da25294fa55b`,报告位于 `H:\OpenNexus-acceptance\a04-36c497f\report`;报告确认 6 次 Core 崩溃、5 次自动重启、6 次本地摘要一致、60 次更新强杀和 0 个残留受管理后代。 +- 正式隔离 Cargo target 在验收后通过 `cargo clean` 删除,共清理 7274 个文件、约 5.8 GiB;报告、JUnit、脱敏日志和配置保留。A-04 可记为通过,正式验收累计 **23/30**;A-01、C-01 与 E-01~E-05 尚未通过。 diff --git a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md index 593f9e5..9d14e52 100644 --- a/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md +++ b/docs/development/OpenNexus第三阶段生产验收清单-2026-09-10.md @@ -2,7 +2,7 @@ 更新时间:2026-09-10 -当前结果:22/30 通过,8/30 未通过。只有状态为“通过”的项目计入完成数;缺少环境、驱动或生产实现的项目均不计入。 +当前结果:23/30 通过,7/30 未通过。只有状态为“通过”的项目计入完成数;缺少环境、驱动或生产实现的项目均不计入。 验收定义以[第三阶段生产化工程规划与验收目标](../architecture/第三阶段生产化工程规划与验收目标.md)为准。逐轮实现、测试命令、提交和原始报告位置记录在[生产化实施进度](OpenNexus生产化实施进度-2026-09-08.md)。 @@ -11,7 +11,7 @@ | A-01 | 阻塞 | 缺少受控签名的 Windows RC、干净标准用户 VM 和离线安装环境;不能用开发机便携包替代。 | | A-02 | 通过 | Sidecar 会话认证、300 次拒绝矩阵、真实 CoreSupervisor 代际轮换和秘密扫描通过。 | | A-03 | 通过 | 协议不兼容、64 MiB 传输边界、取消/断流和 operation_id 恢复通过。 | -| A-04 | 未通过 | 提交 `bdba729` 已用真实 Core 连续强杀 6 次验证 1/2/4/8/16 秒退避、第 6 次熔断、本地保存摘要不变及进程树清理;尚缺每个 Core 更新切换点 20 次恢复的生产驱动,且更新场景依赖签名 RC。 | +| A-04 | 通过 | 真实 Core 连续强杀 6 次验证 1/2/4/8/16 秒退避、第 6 次熔断、本地保存摘要不变及进程树清理;Ed25519 签名兼容组合在三个更新边界各经 20 次真实强杀后恢复。正式报告绑定提交 `36c497f`,目录为 `H:\OpenNexus-acceptance\a04-36c497f\report`。 | | B-01 | 通过 | Provider、Plugin、MCP、Sync 分域与明文泄漏矩阵通过。 | | B-02 | 通过 | 100 条 Fernet 迁移、异常输入与三次幂等迁移通过。 | | B-03 | 通过 | Windows 会话锁定、手工锁定、改密和损坏库恢复通过。 | @@ -48,4 +48,4 @@ ## 下一验收条件 -要把 22/30 推进到 30/30,至少需要:受控签名 RC 与旧 RC、两台干净 Windows 设备或等价可销毁 VM、管理员网络抓包能力,以及 A-04 尚缺的 Core 更新事务与生产驱动。缺少这些条件时,runner 必须继续返回非通过状态。 +要把 23/30 推进到 30/30,至少需要:受控签名 RC 与旧 RC、两台干净 Windows 设备或等价可销毁 VM,以及管理员网络抓包能力。缺少这些条件时,runner 必须继续返回非通过状态。 -- 2.43.0 From dbab4cba50aa8c791773610a69c359eecb861b24 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:55:51 +0800 Subject: [PATCH 21/25] =?UTF-8?q?build:=20=E8=A1=A5=E5=BC=BA=E7=AD=BE?= =?UTF-8?q?=E5=90=8DCore=E5=8F=91=E5=B8=83=E9=A2=84=E6=A3=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OpenNexus生产化实施进度-2026-09-08.md | 7 +++ frontend/src-tauri/tests/core_bundle.rs | 53 +++++++++++++++- scripts/build-core.py | 62 ++++++++++++++++++- 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index 1c53e9f..a677ee9 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -1024,3 +1024,10 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 父测试在 `journal_recorded`、`pointer_recorded`、`switch_committed` 三个边界分别强制终止子进程 20 次。每个子进程先完成真实签名和包树校验,60 次恢复均得到完整旧 Host/Core 组合,无 pending 操作。 - A-04 已接入统一生产 Runner。正式结果为 `PASSED`,绑定提交 `36c497f879bcf1fe727e22f14751da25294fa55b`,报告位于 `H:\OpenNexus-acceptance\a04-36c497f\report`;报告确认 6 次 Core 崩溃、5 次自动重启、6 次本地摘要一致、60 次更新强杀和 0 个残留受管理后代。 - 正式隔离 Cargo target 在验收后通过 `cargo clean` 删除,共清理 7274 个文件、约 5.8 GiB;报告、JUnit、脱敏日志和配置保留。A-04 可记为通过,正式验收累计 **23/30**;A-01、C-01 与 E-01~E-05 尚未通过。 + +### 2026-09-12:A-01 发布 Core 本机预检补强 + +- `build-core.py` 生成的清单新增精确 `host_version` 和 `core_version`。显式 `--release` 模式要求从 `OPENNEXUS_CORE_SIGNING_KEY_FILE` 引用 Ed25519 PEM 私钥,输出 64 字节 `manifest.sig` 与 32 字节原始公钥;缺少密钥或密钥类型错误时拒绝生产构建,私钥不写入参数、仓库或产物。 +- 构建成功后默认删除可重建的 PyInstaller `work` 目录;只有显式 `--keep-work` 才保留。当前构建得到 1523 个文件、223.56 MiB,低于 300 MiB 基础包门槛。 +- 打包 Core 连续 20 次真实启动通过,每轮使用独立数据目录、不同会话代际、认证健康请求,并通过 Rust Workspace 写入及重读本地笔记;ready P95 为 2.676 秒,低于 10 秒。复制出的 Core 可执行文件修改一个字节后连续 20 次均在创建进程前返回 `CORE_INTEGRITY_FAILED`。 +- 临时 Ed25519 PEM 夹具验证了发布签名输出格式;本机没有代码签名证书,Rust 工具链为 GNU 而非 MSVC,也不是干净标准用户离线 VM。因此这些结果是 A-01 本机预检,A-01 仍不计为通过。 diff --git a/frontend/src-tauri/tests/core_bundle.rs b/frontend/src-tauri/tests/core_bundle.rs index 1cafade..119116f 100644 --- a/frontend/src-tauri/tests/core_bundle.rs +++ b/frontend/src-tauri/tests/core_bundle.rs @@ -24,11 +24,12 @@ fn bundle_rejects_modified_missing_and_extra_files() { #[ignore = "requires scripts/build-core.py; run explicitly after building the isolated Core"] fn packaged_core_twenty_cold_starts_use_isolated_data_and_rotate_sessions() { use notesagent_host::core::CoreSupervisor; + use notesagent_host::workspace::Workspace; use std::{ collections::HashSet, io::{Read, Write}, path::Path, - time::Duration, + time::{Duration, Instant}, }; let output = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../.build/sidecar"); let bundle = output.join("dist/opennexus-core").canonicalize().unwrap(); @@ -39,7 +40,11 @@ fn packaged_core_twenty_cold_starts_use_isolated_data_and_rotate_sessions() { "opennexus-core" }); let temp = tempfile::tempdir().unwrap(); + let vault = temp.path().join("vault"); + std::fs::create_dir(&vault).unwrap(); + let mut workspace = Workspace::open(&vault).unwrap(); let mut generations = HashSet::new(); + let mut ready_times = Vec::new(); for attempt in 0..20 { let mut core = CoreSupervisor::new( executable.clone(), @@ -48,7 +53,9 @@ fn packaged_core_twenty_cold_starts_use_isolated_data_and_rotate_sessions() { temp.path().join(format!("run-{attempt}")), ) .with_bundle_manifest(manifest.clone()); + let started = Instant::now(); let request = core.request_session("/health").unwrap(); + ready_times.push(started.elapsed()); assert!(generations.insert(request.generation.clone())); let endpoint = request .url @@ -66,8 +73,52 @@ fn packaged_core_twenty_cold_starts_use_isolated_data_and_rotate_sessions() { "packaged health failed on attempt {attempt}" ); assert!(!response.contains(request.authorization.as_str())); + let path = format!("cold-start-{attempt}.md"); + let saved = workspace + .write(&path, "", format!("本地编辑 {attempt}").as_bytes(), "local") + .unwrap(); + assert_eq!(workspace.read(&path).unwrap().entry.hash, saved.hash); drop(stream); drop(core); assert!(std::net::TcpStream::connect(endpoint).is_err()); } + ready_times.sort(); + let p95 = ready_times[18]; + eprintln!("20 次打包 Core 冷启动 ready P95: {p95:?}"); + assert!(p95 <= Duration::from_secs(10), "ready P95 超过 10 秒"); +} + +#[test] +#[ignore = "需要先通过 scripts/build-core.py 构建隔离 Core"] +fn one_byte_tampered_packaged_core_is_refused_twenty_times() { + use notesagent_host::core::CoreSupervisor; + use sha2::{Digest, Sha256}; + let output = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../.build/sidecar"); + let source = output.join("dist/opennexus-core/opennexus-core.exe"); + let temp = tempfile::tempdir().unwrap(); + let root = temp.path().join("core"); + std::fs::create_dir(&root).unwrap(); + let executable = root.join("opennexus-core.exe"); + std::fs::copy(&source, &executable).unwrap(); + let original = std::fs::read(&executable).unwrap(); + let manifest = serde_json::json!({ + "protocol": 1, + "product": "OpenNexus", + "files": {"opennexus-core.exe": format!("{:x}", Sha256::digest(&original))} + }) + .to_string(); + let mut tampered = original; + tampered[0] ^= 1; + std::fs::write(&executable, tampered).unwrap(); + for attempt in 0..20 { + let mut core = CoreSupervisor::new( + executable.clone(), + vec![], + root.clone(), + temp.path().join(format!("tampered-{attempt}")), + ) + .with_bundle_manifest(manifest.clone()); + assert_eq!(core.start().unwrap_err(), "CORE_INTEGRITY_FAILED"); + assert!(core.process_id().is_none()); + } } diff --git a/scripts/build-core.py b/scripts/build-core.py index a076073..540f885 100644 --- a/scripts/build-core.py +++ b/scripts/build-core.py @@ -4,16 +4,56 @@ 输出保留在当前工作树中已忽略的 .build 目录内。 """ from __future__ import annotations +import argparse import hashlib import json +import os from pathlib import Path +import shutil import subprocess import sys +import tomllib ROOT = Path(__file__).resolve().parents[1] +def arguments() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument( + "--release", + action="store_true", + help="生成必须带 Ed25519 签名的生产 Core 发布包", + ) + parser.add_argument( + "--keep-work", + action="store_true", + help="保留可重建的 PyInstaller 中间目录用于诊断", + ) + return parser.parse_args() + + +def sign_release(manifest: bytes, output: Path) -> None: + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + + key_file = os.environ.get("OPENNEXUS_CORE_SIGNING_KEY_FILE", "") + if not key_file: + raise RuntimeError("release build requires OPENNEXUS_CORE_SIGNING_KEY_FILE") + key_path = Path(key_file).resolve(strict=True) + private = serialization.load_pem_private_key(key_path.read_bytes(), password=None) + if not isinstance(private, Ed25519PrivateKey): + raise RuntimeError("Core signing key must be an Ed25519 PEM private key") + signature = private.sign(manifest) + public = private.public_key().public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.Raw, + ) + (output / "manifest.sig").write_bytes(signature) + (output / "public-key.hex").write_text(public.hex() + "\n", encoding="ascii") + + def main(): + options = arguments() output = ROOT / ".build" / "sidecar" output.mkdir(parents=True, exist_ok=True) subprocess.run([ @@ -36,9 +76,25 @@ def main(): if path.is_file(): with path.open("rb") as stream: files[path.relative_to(bundle).as_posix()] = hashlib.file_digest(stream, "sha256").hexdigest() - manifest = {"protocol": 1, "product": "OpenNexus", "files": files, - "lock_sha256": hashlib.sha256((ROOT / "backend" / "uv.lock").read_bytes()).hexdigest()} - (output / "manifest.json").write_text(json.dumps(manifest, sort_keys=True, separators=(",", ":")), encoding="utf-8") + cargo = tomllib.loads((ROOT / "frontend" / "src-tauri" / "Cargo.toml").read_text(encoding="utf-8")) + version = cargo["package"]["version"] + manifest = { + "protocol": 1, + "product": "OpenNexus", + "host_version": version, + "core_version": version, + "files": files, + "lock_sha256": hashlib.sha256((ROOT / "backend" / "uv.lock").read_bytes()).hexdigest(), + } + encoded = json.dumps(manifest, sort_keys=True, separators=(",", ":")).encode("utf-8") + (output / "manifest.json").write_bytes(encoded) + if options.release: + sign_release(encoded, output) + else: + for stale in (output / "manifest.sig", output / "public-key.hex"): + stale.unlink(missing_ok=True) + if not options.keep_work: + shutil.rmtree(output / "work", ignore_errors=True) print(f"Core built: {len(files)} files; manifest: {output / 'manifest.json'}") -- 2.43.0 From 46acc1824828b5ea364327017fed0bca5465e466 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 02:59:53 +0800 Subject: [PATCH 22/25] =?UTF-8?q?ci:=20=E5=A2=9E=E5=8A=A0Windows=E7=AD=BE?= =?UTF-8?q?=E5=90=8DRC=E5=8F=91=E5=B8=83=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/windows-rc.yml | 113 ++++++++++++++++++ .../OpenNexus生产化实施进度-2026-09-08.md | 3 + scripts/verify-windows-rc.ps1 | 42 +++++++ 3 files changed, 158 insertions(+) create mode 100644 .gitea/workflows/windows-rc.yml create mode 100644 scripts/verify-windows-rc.ps1 diff --git a/.gitea/workflows/windows-rc.yml b/.gitea/workflows/windows-rc.yml new file mode 100644 index 0000000..c5d808c --- /dev/null +++ b/.gitea/workflows/windows-rc.yml @@ -0,0 +1,113 @@ +name: Windows RC + +on: + workflow_dispatch: + +jobs: + signed-rc: + runs-on: windows-latest + permissions: + contents: read + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: pnpm + cache-dependency-path: frontend/pnpm-lock.yaml + - uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-pc-windows-msvc + components: rustfmt, clippy + + - name: 准备锁定依赖 + shell: pwsh + run: | + python -m pip install uv==0.9.24 + uv sync --frozen --group packaging --directory backend + corepack enable + corepack prepare pnpm@10.28.0 --activate + pnpm --dir frontend install --frozen-lockfile + + - name: 导入受控签名材料 + shell: pwsh + env: + WINDOWS_CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }} + WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + CORE_SIGNING_KEY_PEM_BASE64: ${{ secrets.CORE_SIGNING_KEY_PEM_BASE64 }} + run: | + if (-not $env:WINDOWS_CERTIFICATE_BASE64 -or -not $env:WINDOWS_CERTIFICATE_PASSWORD -or -not $env:CORE_SIGNING_KEY_PEM_BASE64) { + throw '缺少 Windows RC 签名秘密' + } + $secretRoot = Join-Path $env:RUNNER_TEMP 'opennexus-signing' + New-Item -ItemType Directory -Force -Path $secretRoot | Out-Null + $pfx = Join-Path $secretRoot 'codesign.pfx' + $coreKey = Join-Path $secretRoot 'core-ed25519.pem' + [IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_BASE64)) + [IO.File]::WriteAllBytes($coreKey, [Convert]::FromBase64String($env:CORE_SIGNING_KEY_PEM_BASE64)) + $password = ConvertTo-SecureString $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force + $certificate = Import-PfxCertificate -FilePath $pfx -CertStoreLocation Cert:\CurrentUser\My -Password $password + if (-not $certificate.HasPrivateKey) { throw '代码签名证书没有私钥' } + "OPENNEXUS_CORE_SIGNING_KEY_FILE=$coreKey" | Out-File $env:GITHUB_ENV -Append -Encoding utf8 + "OPENNEXUS_WINDOWS_CERTIFICATE_THUMBPRINT=$($certificate.Thumbprint)" | Out-File $env:GITHUB_ENV -Append -Encoding utf8 + Remove-Item -LiteralPath $pfx -Force + + - name: 构建签名 Core + shell: pwsh + run: uv run --directory backend --group packaging python ../scripts/build-core.py --release + + - name: 生成签名打包配置 + shell: pwsh + run: | + $config = @{ + bundle = @{ + active = $true + targets = @('nsis') + resources = @{ + '../../.build/sidecar/dist/opennexus-core/' = 'core/' + } + windows = @{ + certificateThumbprint = $env:OPENNEXUS_WINDOWS_CERTIFICATE_THUMBPRINT + digestAlgorithm = 'sha256' + timestampUrl = 'http://timestamp.digicert.com' + } + } + } | ConvertTo-Json -Depth 5 + $path = Join-Path $env:GITHUB_WORKSPACE 'frontend\src-tauri\tauri.rc.conf.json' + [IO.File]::WriteAllText($path, $config, [Text.UTF8Encoding]::new($false)) + "OPENNEXUS_RC_CONFIG=$path" | Out-File $env:GITHUB_ENV -Append -Encoding utf8 + + - name: 构建 MSVC NSIS 安装包 + shell: pwsh + run: pnpm --dir frontend exec tauri build --target x86_64-pc-windows-msvc --features desktop --config src-tauri/tauri.rc.conf.json + + - name: 验证 RC 签名与大小 + shell: pwsh + run: ./scripts/verify-windows-rc.ps1 + + - uses: actions/upload-artifact@v4 + with: + name: OpenNexus-windows-x64-rc + if-no-files-found: error + retention-days: 14 + path: | + frontend/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe + .build/sidecar/manifest.json + .build/sidecar/manifest.sig + .build/sidecar/public-key.hex + .build/windows-rc-sha256.json + + - name: 清理签名材料 + if: always() + shell: pwsh + run: | + if ($env:OPENNEXUS_WINDOWS_CERTIFICATE_THUMBPRINT) { + Remove-Item -LiteralPath "Cert:\CurrentUser\My\$env:OPENNEXUS_WINDOWS_CERTIFICATE_THUMBPRINT" -Force -ErrorAction SilentlyContinue + } + Remove-Item -LiteralPath (Join-Path $env:RUNNER_TEMP 'opennexus-signing') -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -LiteralPath (Join-Path $env:GITHUB_WORKSPACE 'frontend\src-tauri\tauri.rc.conf.json') -Force -ErrorAction SilentlyContinue diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index a677ee9..af2d7ca 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -1031,3 +1031,6 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 构建成功后默认删除可重建的 PyInstaller `work` 目录;只有显式 `--keep-work` 才保留。当前构建得到 1523 个文件、223.56 MiB,低于 300 MiB 基础包门槛。 - 打包 Core 连续 20 次真实启动通过,每轮使用独立数据目录、不同会话代际、认证健康请求,并通过 Rust Workspace 写入及重读本地笔记;ready P95 为 2.676 秒,低于 10 秒。复制出的 Core 可执行文件修改一个字节后连续 20 次均在创建进程前返回 `CORE_INTEGRITY_FAILED`。 - 临时 Ed25519 PEM 夹具验证了发布签名输出格式;本机没有代码签名证书,Rust 工具链为 GNU 而非 MSVC,也不是干净标准用户离线 VM。因此这些结果是 A-01 本机预检,A-01 仍不计为通过。 +- 新增手动触发的 Gitea Actions `Windows RC` 作业。作业固定 `x86_64-pc-windows-msvc`,从仓库 Secrets 解码并导入 PFX 与 Core Ed25519 PEM,生成单一动态 Tauri 配置并构建 NSIS;缺任一签名材料立即失败。 +- `verify-windows-rc.ps1` 要求恰好一个 NSIS 安装包、包体不超过 300 MiB、Host 与安装器 Authenticode 均为 `Valid` 且使用本次受控证书,同时检查 Core 签名/公钥格式并输出逐文件 SHA-256 清单。工作流只在全部门禁通过后上传 14 天保留的 RC,最后无条件移除证书、密钥目录和动态配置。 +- 本机已解析工作流 YAML 和 PowerShell 脚本语法;由于没有 Windows Runner Secrets、MSVC/SDK 和证书,不能在当前会话声称该发布作业已成功产出签名 RC。 diff --git a/scripts/verify-windows-rc.ps1 b/scripts/verify-windows-rc.ps1 new file mode 100644 index 0000000..7e4a10f --- /dev/null +++ b/scripts/verify-windows-rc.ps1 @@ -0,0 +1,42 @@ +$ErrorActionPreference = 'Stop' +$root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path +$bundle = Join-Path $root 'frontend\src-tauri\target\x86_64-pc-windows-msvc\release\bundle\nsis' +$installers = @(Get-ChildItem -LiteralPath $bundle -Filter '*.exe' -File) +if ($installers.Count -ne 1) { throw "应恰好生成一个 NSIS 安装包,实际为 $($installers.Count)" } +$installer = $installers[0] +if ($installer.Length -gt 300MB) { throw "基础安装包超过 300 MiB:$($installer.Length)" } + +$hostExecutable = Join-Path $root 'frontend\src-tauri\target\x86_64-pc-windows-msvc\release\notesagent-desktop.exe' +if (-not (Test-Path -LiteralPath $hostExecutable -PathType Leaf)) { throw '缺少 MSVC Host 可执行文件' } +foreach ($path in @($hostExecutable, $installer.FullName)) { + $signature = Get-AuthenticodeSignature -LiteralPath $path + if ($signature.Status -ne 'Valid') { throw "Authenticode 签名无效:$path ($($signature.Status))" } + if ($signature.SignerCertificate.Thumbprint -ne $env:OPENNEXUS_WINDOWS_CERTIFICATE_THUMBPRINT) { + throw "签名证书与受控证书不匹配:$path" + } +} + +$manifest = Join-Path $root '.build\sidecar\manifest.json' +$manifestSignature = Join-Path $root '.build\sidecar\manifest.sig' +$publicKey = Join-Path $root '.build\sidecar\public-key.hex' +foreach ($path in @($manifest, $manifestSignature, $publicKey)) { + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "缺少 Core 发布文件:$path" } +} +if ((Get-Item -LiteralPath $manifestSignature).Length -ne 64) { throw 'Core 清单签名长度必须为 64 字节' } +if ((Get-Content -LiteralPath $publicKey -Raw).Trim() -notmatch '^[0-9a-f]{64}$') { throw 'Core 发布公钥格式无效' } + +$result = [ordered]@{ + schema = 1 + product = 'OpenNexus' + target = 'x86_64-pc-windows-msvc' + installer_bytes = $installer.Length + certificate_thumbprint = $env:OPENNEXUS_WINDOWS_CERTIFICATE_THUMBPRINT + files = [ordered]@{} +} +foreach ($path in @($installer.FullName, $hostExecutable, $manifest, $manifestSignature, $publicKey)) { + $relative = [IO.Path]::GetRelativePath($root, $path).Replace('\', '/') + $result.files[$relative] = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash.ToLowerInvariant() +} +$output = Join-Path $root '.build\windows-rc-sha256.json' +[IO.File]::WriteAllText($output, ($result | ConvertTo-Json -Depth 5), [Text.UTF8Encoding]::new($false)) +Write-Host "Windows RC 校验通过:$($installer.Name),$([math]::Round($installer.Length / 1MB, 2)) MiB" -- 2.43.0 From c1a17236c024cb199c0a56339a6e8bd5b9a1cdf7 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 03:08:13 +0800 Subject: [PATCH 23/25] =?UTF-8?q?docs:=20=E8=AE=B0=E5=BD=95Windows=20RC?= =?UTF-8?q?=E4=B8=8E=E6=9C=AC=E5=9C=B0Demo=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development/OpenNexus生产化实施进度-2026-09-08.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index af2d7ca..792da1f 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -1034,3 +1034,6 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 新增手动触发的 Gitea Actions `Windows RC` 作业。作业固定 `x86_64-pc-windows-msvc`,从仓库 Secrets 解码并导入 PFX 与 Core Ed25519 PEM,生成单一动态 Tauri 配置并构建 NSIS;缺任一签名材料立即失败。 - `verify-windows-rc.ps1` 要求恰好一个 NSIS 安装包、包体不超过 300 MiB、Host 与安装器 Authenticode 均为 `Valid` 且使用本次受控证书,同时检查 Core 签名/公钥格式并输出逐文件 SHA-256 清单。工作流只在全部门禁通过后上传 14 天保留的 RC,最后无条件移除证书、密钥目录和动态配置。 - 本机已解析工作流 YAML 和 PowerShell 脚本语法;由于没有 Windows Runner Secrets、MSVC/SDK 和证书,不能在当前会话声称该发布作业已成功产出签名 RC。 +- 动态 RC 配置另经 Tauri CLI 实际解析,并完成 `--debug --no-bundle` 构建,证明 NSIS、资源和 Windows 签名字段可被当前 Tauri 版本接受。随后仓库定义的 GNU release `--no-bundle` 构建成功,本地 Demo 位于 `.build/OpenNexus-demo-46acc18`,总计 275.37 MiB,Host EXE 51.64 MiB。 +- Demo 使用独立 `LOCALAPPDATA`/`APPDATA` 启动 12 秒,Host、WebView2 与打包 Core 均保持运行;强制关闭后按可执行路径查询残留进程为 0,隔离冒烟数据已删除。该 Demo 未经 Authenticode 签名,仅供本机查看,不计入 A-01 正式证据。 +- 分支推送至 Gitea 后,远端 CI #6 明确停在“没有匹配 `ubuntu-latest` 标签的在线运行器”。仓库 Actions 页面尚未在默认分支列出仅存在功能分支的 `Windows RC` 工作流;即使合并,当前 Gitea 也没有可执行 `windows-latest` 的在线 Runner。 -- 2.43.0 From 5d3f54d7aacab7d5ed600d4b6141321681ae89c2 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 12:03:23 +0800 Subject: [PATCH 24/25] =?UTF-8?q?style:=20=E7=BB=9F=E4=B8=80Sync=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=8F=B0=E9=BB=98=E8=AE=A4=E6=B5=85=E8=89=B2=E4=B8=BB?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server sync/console/src/style.css | 125 ++++++++++-------- .../static/assets/index-B8qnzSCe.css | 1 + .../static/assets/index-BS5pfOkS.css | 1 - .../{index-CV7tuz7b.js => index-CsQwWg1J.js} | 0 server sync/sync_server/static/index.html | 4 +- 5 files changed, 70 insertions(+), 61 deletions(-) create mode 100644 server sync/sync_server/static/assets/index-B8qnzSCe.css delete mode 100644 server sync/sync_server/static/assets/index-BS5pfOkS.css rename server sync/sync_server/static/assets/{index-CV7tuz7b.js => index-CsQwWg1J.js} (100%) diff --git a/server sync/console/src/style.css b/server sync/console/src/style.css index 882495f..6b602b5 100644 --- a/server sync/console/src/style.css +++ b/server sync/console/src/style.css @@ -1,19 +1,26 @@ :root { - color-scheme: dark; - --bg: #06100d; - --surface: rgba(13, 29, 24, .84); - --surface-strong: #10241d; - --line: rgba(169, 222, 196, .15); - --line-strong: rgba(169, 222, 196, .28); - --text: #edf7f2; - --muted: #8fa99c; - --mint: #72e0ad; - --mint-bright: #a6f4cf; - --purple: #a991ff; - --danger: #ff8e8e; - --warning: #f5ca72; - --shadow: 0 26px 90px rgba(0, 0, 0, .34); - font-family: Inter, "Segoe UI", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif; + color-scheme: light; + --bg: #ffffff; + --bg-secondary: #f7f8fa; + --bg-tertiary: #eef0f3; + --surface: #ffffff; + --surface-strong: #fafbfc; + --line: #e4e7eb; + --line-strong: #d6dae0; + --text: #1f2328; + --muted: #656d76; + --tertiary: #9198a0; + --accent: #5b67f1; + --accent-hover: #4a55e0; + --accent-soft: #eef0ff; + --purple: #8b94f5; + --success: #2da44e; + --success-soft: #dafbe3; + --danger: #cf222e; + --danger-soft: #ffebe9; + --warning: #d4a72c; + --shadow: 0 16px 36px rgba(31, 35, 40, .11), 0 4px 12px rgba(31, 35, 40, .05); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", "PingFang SC", "Microsoft YaHei", sans-serif; } * { box-sizing: border-box; } @@ -23,19 +30,19 @@ body { min-height: 100vh; color: var(--text); background: - linear-gradient(rgba(255,255,255,.018) 1px, transparent 1px), - linear-gradient(90deg, rgba(255,255,255,.018) 1px, transparent 1px), - radial-gradient(circle at 48% 0%, #15372c 0, var(--bg) 46%); + linear-gradient(rgba(91,103,241,.025) 1px, transparent 1px), + linear-gradient(90deg, rgba(91,103,241,.025) 1px, transparent 1px), + radial-gradient(circle at 48% 0%, #f4f5ff 0, var(--bg) 48%); background-size: 52px 52px, 52px 52px, auto; overflow-x: hidden; } button, input { font: inherit; } button { color: inherit; } -button:focus-visible, input:focus-visible { outline: 2px solid var(--mint); outline-offset: 3px; } +button:focus-visible, input:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; } button:disabled { cursor: default; opacity: .5; } .ambient { position: fixed; border-radius: 50%; filter: blur(80px); pointer-events: none; opacity: .16; } -.ambient-one { width: 420px; height: 420px; background: var(--mint); top: -220px; right: 8%; } +.ambient-one { width: 420px; height: 420px; background: var(--accent); top: -220px; right: 8%; } .ambient-two { width: 360px; height: 360px; background: var(--purple); bottom: -220px; left: -100px; opacity: .1; } .topbar { width: min(1280px, calc(100% - 48px)); @@ -53,95 +60,97 @@ button:disabled { cursor: default; opacity: .5; } .brand strong { font-size: 17px; letter-spacing: -.02em; } .brand small { color: var(--muted); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; } .brand-mark { width: 34px; height: 34px; display: grid; place-items: center; position: relative; transform: rotate(30deg); } -.brand-mark i { position: absolute; display: block; border: 1.5px solid var(--mint); border-radius: 4px; } +.brand-mark i { position: absolute; display: block; border: 1.5px solid var(--accent); border-radius: 4px; } .brand-mark i:nth-child(1) { width: 25px; height: 25px; opacity: .45; } .brand-mark i:nth-child(2) { width: 17px; height: 17px; opacity: .72; } -.brand-mark i:nth-child(3) { width: 8px; height: 8px; background: var(--mint); box-shadow: 0 0 20px rgba(114,224,173,.8); } +.brand-mark i:nth-child(3) { width: 8px; height: 8px; background: var(--accent); box-shadow: 0 0 14px rgba(91,103,241,.38); } .service-strip { display: flex; align-items: center; gap: 9px; } -.status-chip { min-height: 34px; padding: 0 12px; border: 1px solid var(--line); border-radius: 999px; display: flex; align-items: center; gap: 7px; background: rgba(7,18,15,.7); } +.status-chip { min-height: 34px; padding: 0 12px; border: 1px solid var(--line); border-radius: 999px; display: flex; align-items: center; gap: 7px; background: var(--surface); box-shadow: 0 1px 2px rgba(31,35,40,.05); } .status-chip i { width: 7px; height: 7px; border-radius: 50%; background: var(--warning); box-shadow: 0 0 12px currentColor; } .status-chip b { color: var(--muted); font-size: 11px; font-weight: 500; } .status-chip em { font-size: 11px; font-style: normal; } -.status-chip.ok i { background: var(--mint); } +.status-chip.ok i { background: var(--success); } .status-chip.bad i { background: var(--danger); } .status-chip.bad em { color: var(--danger); } -.icon-button { width: 36px; height: 36px; border: 1px solid var(--line); border-radius: 10px; display: grid; place-items: center; background: rgba(14,31,25,.7); cursor: pointer; transition: .18s ease; } +.icon-button { width: 36px; height: 36px; border: 1px solid var(--line); border-radius: 9px; display: grid; place-items: center; background: var(--surface); cursor: pointer; transition: .18s ease; } .icon-button:hover { border-color: var(--line-strong); background: var(--surface-strong); transform: translateY(-1px); } .icon-button svg { width: 17px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } main { width: min(1180px, calc(100% - 48px)); margin: 0 auto; position: relative; z-index: 1; } .hero { min-height: calc(100vh - 150px); display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(360px, .75fr); gap: clamp(48px, 8vw, 116px); align-items: center; padding: 64px 0 82px; } .hero-copy { max-width: 680px; } -.eyebrow { color: var(--mint); font-size: 11px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; display: flex; align-items: center; gap: 10px; } -.eyebrow span { width: 24px; height: 1px; background: var(--mint); box-shadow: 0 0 10px var(--mint); } +.eyebrow { color: var(--accent); font-size: 11px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; display: flex; align-items: center; gap: 10px; } +.eyebrow span { width: 24px; height: 1px; background: var(--accent); } .hero h1, .console-heading h1 { margin: 20px 0; letter-spacing: -.06em; line-height: .99; font-size: clamp(50px, 6.4vw, 86px); font-weight: 620; } -.hero h1 em { font-style: normal; color: transparent; background: linear-gradient(95deg, var(--mint-bright), #82d8c1 47%, var(--purple)); background-clip: text; -webkit-background-clip: text; } +.hero h1 em { font-style: normal; color: transparent; background: linear-gradient(95deg, var(--accent), var(--purple)); background-clip: text; -webkit-background-clip: text; } .hero-copy > p { max-width: 620px; color: var(--muted); font-size: 16px; line-height: 1.85; } -.protocol-grid { display: grid; grid-template-columns: repeat(3, 1fr); margin-top: 42px; border: 1px solid var(--line); border-radius: 16px; overflow: hidden; background: rgba(8,22,17,.45); backdrop-filter: blur(15px); } +.protocol-grid { display: grid; grid-template-columns: repeat(3, 1fr); margin-top: 42px; border: 1px solid var(--line); border-radius: 13px; overflow: hidden; background: var(--surface); box-shadow: 0 1px 5px rgba(31,35,40,.03); } .protocol-grid article { padding: 19px 22px; display: grid; gap: 5px; border-right: 1px solid var(--line); } .protocol-grid article:last-child { border-right: 0; } .protocol-grid strong { font-size: 18px; font-weight: 600; } .protocol-grid span { color: var(--muted); font-size: 11px; } -.login-card { padding: 31px; border: 1px solid var(--line-strong); border-radius: 24px; background: linear-gradient(145deg, rgba(18,40,32,.94), rgba(8,21,17,.9)); box-shadow: var(--shadow); position: relative; overflow: hidden; backdrop-filter: blur(25px); } -.card-glow { position: absolute; width: 210px; height: 210px; right: -100px; top: -120px; border-radius: 50%; background: var(--mint); filter: blur(60px); opacity: .16; } +.login-card { padding: 31px; border: 1px solid var(--line); border-radius: 18px; background: var(--surface); box-shadow: var(--shadow); position: relative; overflow: hidden; } +.card-glow { position: absolute; width: 210px; height: 210px; right: -100px; top: -120px; border-radius: 50%; background: var(--accent); filter: blur(60px); opacity: .08; } .card-heading { display: flex; gap: 14px; align-items: center; margin-bottom: 28px; position: relative; } -.card-heading p, .surface-heading p { margin: 0 0 3px; color: var(--mint); font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; } +.card-heading p, .surface-heading p { margin: 0 0 3px; color: var(--accent); font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; } .card-heading h2, .surface-heading h2 { margin: 0; font-size: 21px; letter-spacing: -.025em; } -.lock-mark { width: 43px; height: 43px; border-radius: 13px; display: grid; place-items: center; background: rgba(114,224,173,.1); color: var(--mint); border: 1px solid rgba(114,224,173,.18); } +.lock-mark { width: 43px; height: 43px; border-radius: 13px; display: grid; place-items: center; background: var(--accent-soft); color: var(--accent); border: 1px solid #e2e5ff; } .lock-mark svg { width: 21px; fill: none; stroke: currentColor; stroke-width: 1.7; } form { position: relative; } .login-card form { display: grid; gap: 17px; } label { display: grid; gap: 7px; color: var(--muted); font-size: 11px; font-weight: 600; letter-spacing: .04em; } -input { width: 100%; color: var(--text); background: rgba(3,12,9,.58); border: 1px solid var(--line); border-radius: 11px; padding: 12px 13px; transition: border-color .18s, background .18s; } -input::placeholder { color: #5f786c; } -input:hover, input:focus { border-color: rgba(114,224,173,.46); background: rgba(3,12,9,.8); } -.primary-button { width: 100%; margin-top: 5px; padding: 13px 16px; border: 0; border-radius: 11px; display: flex; justify-content: center; align-items: center; gap: 8px; color: #062015; background: linear-gradient(100deg, var(--mint-bright), var(--mint)); font-weight: 750; cursor: pointer; box-shadow: 0 10px 35px rgba(114,224,173,.16); transition: .18s ease; } -.primary-button:hover { transform: translateY(-1px); filter: brightness(1.05); } +input { width: 100%; color: var(--text); background: var(--bg); border: 1px solid var(--line); border-radius: 9px; padding: 12px 13px; transition: border-color .18s, background .18s, box-shadow .18s; } +input::placeholder { color: var(--tertiary); } +input:hover { border-color: var(--line-strong); } +input:focus { border-color: var(--accent); background: var(--surface); box-shadow: 0 0 0 3px var(--accent-soft); } +.primary-button { width: 100%; margin-top: 5px; padding: 13px 16px; border: 0; border-radius: 9px; display: flex; justify-content: center; align-items: center; gap: 8px; color: #fff; background: var(--accent); font-weight: 750; cursor: pointer; box-shadow: 0 4px 12px rgba(91,103,241,.24); transition: .18s ease; } +.primary-button:hover { transform: translateY(-1px); background: var(--accent-hover); box-shadow: 0 7px 18px rgba(91,103,241,.28); } .primary-button svg { width: 18px; fill: none; stroke: currentColor; stroke-width: 2; } -.privacy-note { margin: 18px 0 0; color: #718b7e; font-size: 10px; line-height: 1.5; display: flex; gap: 8px; } -.privacy-note span { color: var(--mint); font-size: 7px; margin-top: 3px; } +.privacy-note { margin: 18px 0 0; color: var(--tertiary); font-size: 10px; line-height: 1.5; display: flex; gap: 8px; } +.privacy-note span { color: var(--success); font-size: 7px; margin-top: 3px; } .console-view { padding: 64px 0 90px; min-height: calc(100vh - 150px); } .console-heading { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 38px; } .console-heading h1 { font-size: clamp(42px, 5vw, 64px); margin: 13px 0 7px; } .console-heading p { margin: 0; color: var(--muted); } .secondary-button { padding: 10px 15px; border-radius: 10px; border: 1px solid var(--line-strong); background: transparent; cursor: pointer; } -.secondary-button:hover { border-color: var(--mint); color: var(--mint-bright); } -.metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); border: 1px solid var(--line); background: rgba(8,22,17,.52); border-radius: 18px; overflow: hidden; margin-bottom: 22px; } +.secondary-button:hover { border-color: var(--accent); background: var(--bg-secondary); color: var(--accent); } +.metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); border: 1px solid var(--line); background: var(--surface); border-radius: 13px; overflow: hidden; margin-bottom: 22px; box-shadow: 0 1px 5px rgba(31,35,40,.03); } .metric-grid article { min-height: 134px; padding: 23px; display: flex; flex-direction: column; border-right: 1px solid var(--line); } .metric-grid article:last-child { border-right: 0; } .metric-grid span { color: var(--muted); font-size: 11px; } .metric-grid strong { margin: 12px 0 8px; font-size: 27px; font-weight: 620; letter-spacing: -.04em; } -.metric-grid small { color: #668075; font-size: 10px; } +.metric-grid small { color: var(--tertiary); font-size: 10px; } .content-grid { display: grid; grid-template-columns: 1.25fr .75fr; gap: 22px; } -.surface { border: 1px solid var(--line); border-radius: 18px; background: var(--surface); padding: 25px; backdrop-filter: blur(18px); } +.surface { border: 1px solid var(--line); border-radius: 13px; background: var(--surface); padding: 25px; box-shadow: 0 1px 5px rgba(31,35,40,.03); } .surface-heading { display: flex; justify-content: space-between; align-items: center; margin-bottom: 22px; } -.secure-badge { color: var(--mint); background: rgba(114,224,173,.08); border: 1px solid rgba(114,224,173,.18); padding: 6px 9px; border-radius: 999px; font-size: 9px; letter-spacing: .08em; } +.secure-badge { color: var(--accent); background: var(--accent-soft); border: 1px solid #e2e5ff; padding: 6px 9px; border-radius: 999px; font-size: 9px; letter-spacing: .08em; } .surface-intro { margin: -9px 0 19px; color: var(--muted); font-size: 12px; line-height: 1.65; } -.create-form { margin-bottom: 20px; padding: 16px; border-radius: 13px; background: rgba(4,15,11,.42); border: 1px solid var(--line); } +.create-form { margin-bottom: 20px; padding: 16px; border-radius: 13px; background: var(--bg-secondary); border: 1px solid var(--line); } .create-form > div { display: flex; gap: 8px; margin-top: 8px; } .create-form input { padding: 10px 12px; } -.create-form button { border: 0; border-radius: 9px; padding: 0 17px; color: #082016; background: var(--mint); font-weight: 700; cursor: pointer; } +.create-form button { border: 0; border-radius: 9px; padding: 0 17px; color: #fff; background: var(--accent); font-weight: 700; cursor: pointer; } +.create-form button:hover { background: var(--accent-hover); } .vault-list, .device-list { display: grid; gap: 9px; } -.vault-item, .device-item { min-width: 0; border: 1px solid var(--line); background: rgba(6,19,14,.48); border-radius: 13px; padding: 15px 16px; display: flex; align-items: center; gap: 13px; } -.vault-symbol, .device-symbol { width: 38px; height: 38px; flex: 0 0 auto; display: grid; place-items: center; border-radius: 11px; background: rgba(114,224,173,.08); color: var(--mint); font-weight: 700; } -.device-symbol { color: var(--purple); background: rgba(169,145,255,.08); } +.vault-item, .device-item { min-width: 0; border: 1px solid var(--line); background: var(--surface-strong); border-radius: 9px; padding: 15px 16px; display: flex; align-items: center; gap: 13px; } +.vault-symbol, .device-symbol { width: 38px; height: 38px; flex: 0 0 auto; display: grid; place-items: center; border-radius: 9px; background: var(--accent-soft); color: var(--accent); font-weight: 700; } +.device-symbol { color: #7443ad; background: #f3eefe; } .item-copy { min-width: 0; flex: 1; } .item-copy strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; } .item-copy small { color: var(--muted); font-size: 10px; } -progress { display: block; width: 100%; height: 4px; margin-top: 9px; border: 0; border-radius: 99px; overflow: hidden; accent-color: var(--mint); } -progress::-webkit-progress-bar { background: rgba(255,255,255,.06); } -progress::-webkit-progress-value { background: linear-gradient(90deg, var(--mint), var(--purple)); } +progress { display: block; width: 100%; height: 4px; margin-top: 9px; border: 0; border-radius: 99px; overflow: hidden; accent-color: var(--accent); } +progress::-webkit-progress-bar { background: var(--bg-tertiary); } +progress::-webkit-progress-value { background: linear-gradient(90deg, var(--accent), var(--purple)); } .item-side { text-align: right; flex: 0 0 auto; } .item-side strong { display: block; font-size: 12px; } .item-side small { color: var(--muted); font-size: 9px; } -.danger-button { border: 1px solid rgba(255,142,142,.25); color: var(--danger); background: rgba(255,142,142,.05); border-radius: 8px; padding: 7px 9px; cursor: pointer; font-size: 10px; } -.danger-button:hover { background: rgba(255,142,142,.12); } +.danger-button { border: 1px solid #ffc1bd; color: var(--danger); background: var(--danger-soft); border-radius: 8px; padding: 7px 9px; cursor: pointer; font-size: 10px; } +.danger-button:hover { background: #ffd8d5; } .revoked { opacity: .48; } .empty-state { min-height: 100px; display: grid; place-items: center; color: var(--muted); border: 1px dashed var(--line); border-radius: 12px; font-size: 12px; } -footer { width: min(1280px, calc(100% - 48px)); min-height: 62px; margin: 0 auto; border-top: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; color: #597166; font-size: 9px; letter-spacing: .04em; position: relative; z-index: 1; } -.toast { position: fixed; z-index: 10; right: 24px; bottom: 24px; max-width: min(420px, calc(100% - 48px)); padding: 13px 16px; border: 1px solid var(--line-strong); border-radius: 11px; background: #142b23; box-shadow: var(--shadow); font-size: 12px; } -.toast.error { color: #ffd2d2; border-color: rgba(255,142,142,.35); background: #321a1a; } +footer { width: min(1280px, calc(100% - 48px)); min-height: 62px; margin: 0 auto; border-top: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; color: var(--tertiary); font-size: 9px; letter-spacing: .04em; position: relative; z-index: 1; } +.toast { position: fixed; z-index: 10; right: 24px; bottom: 24px; max-width: min(420px, calc(100% - 48px)); padding: 13px 16px; border: 1px solid var(--line-strong); border-radius: 9px; background: var(--surface); box-shadow: var(--shadow); font-size: 12px; } +.toast.error { color: var(--danger); border-color: #ffc1bd; background: var(--danger-soft); } @media (max-width: 920px) { .hero { grid-template-columns: 1fr; padding-top: 76px; } diff --git a/server sync/sync_server/static/assets/index-B8qnzSCe.css b/server sync/sync_server/static/assets/index-B8qnzSCe.css new file mode 100644 index 0000000..6f05935 --- /dev/null +++ b/server sync/sync_server/static/assets/index-B8qnzSCe.css @@ -0,0 +1 @@ +:root{color-scheme:light;--bg: #ffffff;--bg-secondary: #f7f8fa;--bg-tertiary: #eef0f3;--surface: #ffffff;--surface-strong: #fafbfc;--line: #e4e7eb;--line-strong: #d6dae0;--text: #1f2328;--muted: #656d76;--tertiary: #9198a0;--accent: #5b67f1;--accent-hover: #4a55e0;--accent-soft: #eef0ff;--purple: #8b94f5;--success: #2da44e;--success-soft: #dafbe3;--danger: #cf222e;--danger-soft: #ffebe9;--warning: #d4a72c;--shadow: 0 16px 36px rgba(31, 35, 40, .11), 0 4px 12px rgba(31, 35, 40, .05);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Noto Sans,PingFang SC,Microsoft YaHei,sans-serif}*{box-sizing:border-box}html{min-width:320px;min-height:100%;background:var(--bg)}body{margin:0;min-height:100vh;color:var(--text);background:linear-gradient(rgba(91,103,241,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(91,103,241,.025) 1px,transparent 1px),radial-gradient(circle at 48% 0%,#f4f5ff 0,var(--bg) 48%);background-size:52px 52px,52px 52px,auto;overflow-x:hidden}button,input{font:inherit}button{color:inherit}button:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:3px}button:disabled{cursor:default;opacity:.5}.ambient{position:fixed;border-radius:50%;filter:blur(80px);pointer-events:none;opacity:.16}.ambient-one{width:420px;height:420px;background:var(--accent);top:-220px;right:8%}.ambient-two{width:360px;height:360px;background:var(--purple);bottom:-220px;left:-100px;opacity:.1}.topbar{width:min(1280px,calc(100% - 48px));height:88px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);position:relative;z-index:2}.brand{color:inherit;text-decoration:none;display:inline-flex;align-items:center;gap:13px}.brand>span:last-child{display:grid;gap:1px}.brand strong{font-size:17px;letter-spacing:-.02em}.brand small{color:var(--muted);font-size:11px;letter-spacing:.12em;text-transform:uppercase}.brand-mark{width:34px;height:34px;display:grid;place-items:center;position:relative;transform:rotate(30deg)}.brand-mark i{position:absolute;display:block;border:1.5px solid var(--accent);border-radius:4px}.brand-mark i:nth-child(1){width:25px;height:25px;opacity:.45}.brand-mark i:nth-child(2){width:17px;height:17px;opacity:.72}.brand-mark i:nth-child(3){width:8px;height:8px;background:var(--accent);box-shadow:0 0 14px #5b67f161}.service-strip{display:flex;align-items:center;gap:9px}.status-chip{min-height:34px;padding:0 12px;border:1px solid var(--line);border-radius:999px;display:flex;align-items:center;gap:7px;background:var(--surface);box-shadow:0 1px 2px #1f23280d}.status-chip i{width:7px;height:7px;border-radius:50%;background:var(--warning);box-shadow:0 0 12px currentColor}.status-chip b{color:var(--muted);font-size:11px;font-weight:500}.status-chip em{font-size:11px;font-style:normal}.status-chip.ok i{background:var(--success)}.status-chip.bad i{background:var(--danger)}.status-chip.bad em{color:var(--danger)}.icon-button{width:36px;height:36px;border:1px solid var(--line);border-radius:9px;display:grid;place-items:center;background:var(--surface);cursor:pointer;transition:.18s ease}.icon-button:hover{border-color:var(--line-strong);background:var(--surface-strong);transform:translateY(-1px)}.icon-button svg{width:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}main{width:min(1180px,calc(100% - 48px));margin:0 auto;position:relative;z-index:1}.hero{min-height:calc(100vh - 150px);display:grid;grid-template-columns:minmax(0,1.25fr) minmax(360px,.75fr);gap:clamp(48px,8vw,116px);align-items:center;padding:64px 0 82px}.hero-copy{max-width:680px}.eyebrow{color:var(--accent);font-size:11px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;display:flex;align-items:center;gap:10px}.eyebrow span{width:24px;height:1px;background:var(--accent)}.hero h1,.console-heading h1{margin:20px 0;letter-spacing:-.06em;line-height:.99;font-size:clamp(50px,6.4vw,86px);font-weight:620}.hero h1 em{font-style:normal;color:transparent;background:linear-gradient(95deg,var(--accent),var(--purple));background-clip:text;-webkit-background-clip:text}.hero-copy>p{max-width:620px;color:var(--muted);font-size:16px;line-height:1.85}.protocol-grid{display:grid;grid-template-columns:repeat(3,1fr);margin-top:42px;border:1px solid var(--line);border-radius:13px;overflow:hidden;background:var(--surface);box-shadow:0 1px 5px #1f232808}.protocol-grid article{padding:19px 22px;display:grid;gap:5px;border-right:1px solid var(--line)}.protocol-grid article:last-child{border-right:0}.protocol-grid strong{font-size:18px;font-weight:600}.protocol-grid span{color:var(--muted);font-size:11px}.login-card{padding:31px;border:1px solid var(--line);border-radius:18px;background:var(--surface);box-shadow:var(--shadow);position:relative;overflow:hidden}.card-glow{position:absolute;width:210px;height:210px;right:-100px;top:-120px;border-radius:50%;background:var(--accent);filter:blur(60px);opacity:.08}.card-heading{display:flex;gap:14px;align-items:center;margin-bottom:28px;position:relative}.card-heading p,.surface-heading p{margin:0 0 3px;color:var(--accent);font-size:10px;font-weight:700;letter-spacing:.13em;text-transform:uppercase}.card-heading h2,.surface-heading h2{margin:0;font-size:21px;letter-spacing:-.025em}.lock-mark{width:43px;height:43px;border-radius:13px;display:grid;place-items:center;background:var(--accent-soft);color:var(--accent);border:1px solid #e2e5ff}.lock-mark svg{width:21px;fill:none;stroke:currentColor;stroke-width:1.7}form{position:relative}.login-card form{display:grid;gap:17px}label{display:grid;gap:7px;color:var(--muted);font-size:11px;font-weight:600;letter-spacing:.04em}input{width:100%;color:var(--text);background:var(--bg);border:1px solid var(--line);border-radius:9px;padding:12px 13px;transition:border-color .18s,background .18s,box-shadow .18s}input::placeholder{color:var(--tertiary)}input:hover{border-color:var(--line-strong)}input:focus{border-color:var(--accent);background:var(--surface);box-shadow:0 0 0 3px var(--accent-soft)}.primary-button{width:100%;margin-top:5px;padding:13px 16px;border:0;border-radius:9px;display:flex;justify-content:center;align-items:center;gap:8px;color:#fff;background:var(--accent);font-weight:750;cursor:pointer;box-shadow:0 4px 12px #5b67f13d;transition:.18s ease}.primary-button:hover{transform:translateY(-1px);background:var(--accent-hover);box-shadow:0 7px 18px #5b67f147}.primary-button svg{width:18px;fill:none;stroke:currentColor;stroke-width:2}.privacy-note{margin:18px 0 0;color:var(--tertiary);font-size:10px;line-height:1.5;display:flex;gap:8px}.privacy-note span{color:var(--success);font-size:7px;margin-top:3px}.console-view{padding:64px 0 90px;min-height:calc(100vh - 150px)}.console-heading{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:38px}.console-heading h1{font-size:clamp(42px,5vw,64px);margin:13px 0 7px}.console-heading p{margin:0;color:var(--muted)}.secondary-button{padding:10px 15px;border-radius:10px;border:1px solid var(--line-strong);background:transparent;cursor:pointer}.secondary-button:hover{border-color:var(--accent);background:var(--bg-secondary);color:var(--accent)}.metric-grid{display:grid;grid-template-columns:repeat(4,1fr);border:1px solid var(--line);background:var(--surface);border-radius:13px;overflow:hidden;margin-bottom:22px;box-shadow:0 1px 5px #1f232808}.metric-grid article{min-height:134px;padding:23px;display:flex;flex-direction:column;border-right:1px solid var(--line)}.metric-grid article:last-child{border-right:0}.metric-grid span{color:var(--muted);font-size:11px}.metric-grid strong{margin:12px 0 8px;font-size:27px;font-weight:620;letter-spacing:-.04em}.metric-grid small{color:var(--tertiary);font-size:10px}.content-grid{display:grid;grid-template-columns:1.25fr .75fr;gap:22px}.surface{border:1px solid var(--line);border-radius:13px;background:var(--surface);padding:25px;box-shadow:0 1px 5px #1f232808}.surface-heading{display:flex;justify-content:space-between;align-items:center;margin-bottom:22px}.secure-badge{color:var(--accent);background:var(--accent-soft);border:1px solid #e2e5ff;padding:6px 9px;border-radius:999px;font-size:9px;letter-spacing:.08em}.surface-intro{margin:-9px 0 19px;color:var(--muted);font-size:12px;line-height:1.65}.create-form{margin-bottom:20px;padding:16px;border-radius:13px;background:var(--bg-secondary);border:1px solid var(--line)}.create-form>div{display:flex;gap:8px;margin-top:8px}.create-form input{padding:10px 12px}.create-form button{border:0;border-radius:9px;padding:0 17px;color:#fff;background:var(--accent);font-weight:700;cursor:pointer}.create-form button:hover{background:var(--accent-hover)}.vault-list,.device-list{display:grid;gap:9px}.vault-item,.device-item{min-width:0;border:1px solid var(--line);background:var(--surface-strong);border-radius:9px;padding:15px 16px;display:flex;align-items:center;gap:13px}.vault-symbol,.device-symbol{width:38px;height:38px;flex:0 0 auto;display:grid;place-items:center;border-radius:9px;background:var(--accent-soft);color:var(--accent);font-weight:700}.device-symbol{color:#7443ad;background:#f3eefe}.item-copy{min-width:0;flex:1}.item-copy strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.item-copy small{color:var(--muted);font-size:10px}progress{display:block;width:100%;height:4px;margin-top:9px;border:0;border-radius:99px;overflow:hidden;accent-color:var(--accent)}progress::-webkit-progress-bar{background:var(--bg-tertiary)}progress::-webkit-progress-value{background:linear-gradient(90deg,var(--accent),var(--purple))}.item-side{text-align:right;flex:0 0 auto}.item-side strong{display:block;font-size:12px}.item-side small{color:var(--muted);font-size:9px}.danger-button{border:1px solid #ffc1bd;color:var(--danger);background:var(--danger-soft);border-radius:8px;padding:7px 9px;cursor:pointer;font-size:10px}.danger-button:hover{background:#ffd8d5}.revoked{opacity:.48}.empty-state{min-height:100px;display:grid;place-items:center;color:var(--muted);border:1px dashed var(--line);border-radius:12px;font-size:12px}footer{width:min(1280px,calc(100% - 48px));min-height:62px;margin:0 auto;border-top:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;color:var(--tertiary);font-size:9px;letter-spacing:.04em;position:relative;z-index:1}.toast{position:fixed;z-index:10;right:24px;bottom:24px;max-width:min(420px,calc(100% - 48px));padding:13px 16px;border:1px solid var(--line-strong);border-radius:9px;background:var(--surface);box-shadow:var(--shadow);font-size:12px}.toast.error{color:var(--danger);border-color:#ffc1bd;background:var(--danger-soft)}@media (max-width: 920px){.hero{grid-template-columns:1fr;padding-top:76px}.hero-copy{max-width:none}.login-card{max-width:560px;width:100%}.content-grid{grid-template-columns:1fr}.metric-grid{grid-template-columns:repeat(2,1fr)}.metric-grid article:nth-child(2){border-right:0}.metric-grid article:nth-child(-n+2){border-bottom:1px solid var(--line)}}@media (max-width: 620px){.topbar,main,footer{width:min(100% - 28px,1180px)}.topbar{height:74px}.brand small,.status-chip b,footer span:last-child{display:none}.status-chip{padding:0 9px}.hero{padding:52px 0 64px;gap:45px}.hero h1{font-size:43px}.hero-copy>p{font-size:14px}.protocol-grid article{padding:15px 11px}.protocol-grid strong{font-size:14px}.login-card,.surface{padding:21px}.console-heading{align-items:flex-start;gap:18px}.metric-grid{grid-template-columns:1fr}.metric-grid article{min-height:116px;border-right:0;border-bottom:1px solid var(--line)}.metric-grid article:last-child{border-bottom:0}.create-form>div{flex-direction:column}.create-form button{padding:10px}}@media (prefers-reduced-motion: reduce){*,*:before,*:after{scroll-behavior:auto!important;transition:none!important}} diff --git a/server sync/sync_server/static/assets/index-BS5pfOkS.css b/server sync/sync_server/static/assets/index-BS5pfOkS.css deleted file mode 100644 index bf1bd9f..0000000 --- a/server sync/sync_server/static/assets/index-BS5pfOkS.css +++ /dev/null @@ -1 +0,0 @@ -:root{color-scheme:dark;--bg: #06100d;--surface: rgba(13, 29, 24, .84);--surface-strong: #10241d;--line: rgba(169, 222, 196, .15);--line-strong: rgba(169, 222, 196, .28);--text: #edf7f2;--muted: #8fa99c;--mint: #72e0ad;--mint-bright: #a6f4cf;--purple: #a991ff;--danger: #ff8e8e;--warning: #f5ca72;--shadow: 0 26px 90px rgba(0, 0, 0, .34);font-family:Inter,Segoe UI,PingFang SC,Microsoft YaHei,system-ui,sans-serif}*{box-sizing:border-box}html{min-width:320px;min-height:100%;background:var(--bg)}body{margin:0;min-height:100vh;color:var(--text);background:linear-gradient(rgba(255,255,255,.018) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.018) 1px,transparent 1px),radial-gradient(circle at 48% 0%,#15372c 0,var(--bg) 46%);background-size:52px 52px,52px 52px,auto;overflow-x:hidden}button,input{font:inherit}button{color:inherit}button:focus-visible,input:focus-visible{outline:2px solid var(--mint);outline-offset:3px}button:disabled{cursor:default;opacity:.5}.ambient{position:fixed;border-radius:50%;filter:blur(80px);pointer-events:none;opacity:.16}.ambient-one{width:420px;height:420px;background:var(--mint);top:-220px;right:8%}.ambient-two{width:360px;height:360px;background:var(--purple);bottom:-220px;left:-100px;opacity:.1}.topbar{width:min(1280px,calc(100% - 48px));height:88px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);position:relative;z-index:2}.brand{color:inherit;text-decoration:none;display:inline-flex;align-items:center;gap:13px}.brand>span:last-child{display:grid;gap:1px}.brand strong{font-size:17px;letter-spacing:-.02em}.brand small{color:var(--muted);font-size:11px;letter-spacing:.12em;text-transform:uppercase}.brand-mark{width:34px;height:34px;display:grid;place-items:center;position:relative;transform:rotate(30deg)}.brand-mark i{position:absolute;display:block;border:1.5px solid var(--mint);border-radius:4px}.brand-mark i:nth-child(1){width:25px;height:25px;opacity:.45}.brand-mark i:nth-child(2){width:17px;height:17px;opacity:.72}.brand-mark i:nth-child(3){width:8px;height:8px;background:var(--mint);box-shadow:0 0 20px #72e0adcc}.service-strip{display:flex;align-items:center;gap:9px}.status-chip{min-height:34px;padding:0 12px;border:1px solid var(--line);border-radius:999px;display:flex;align-items:center;gap:7px;background:#07120fb3}.status-chip i{width:7px;height:7px;border-radius:50%;background:var(--warning);box-shadow:0 0 12px currentColor}.status-chip b{color:var(--muted);font-size:11px;font-weight:500}.status-chip em{font-size:11px;font-style:normal}.status-chip.ok i{background:var(--mint)}.status-chip.bad i{background:var(--danger)}.status-chip.bad em{color:var(--danger)}.icon-button{width:36px;height:36px;border:1px solid var(--line);border-radius:10px;display:grid;place-items:center;background:#0e1f19b3;cursor:pointer;transition:.18s ease}.icon-button:hover{border-color:var(--line-strong);background:var(--surface-strong);transform:translateY(-1px)}.icon-button svg{width:17px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}main{width:min(1180px,calc(100% - 48px));margin:0 auto;position:relative;z-index:1}.hero{min-height:calc(100vh - 150px);display:grid;grid-template-columns:minmax(0,1.25fr) minmax(360px,.75fr);gap:clamp(48px,8vw,116px);align-items:center;padding:64px 0 82px}.hero-copy{max-width:680px}.eyebrow{color:var(--mint);font-size:11px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;display:flex;align-items:center;gap:10px}.eyebrow span{width:24px;height:1px;background:var(--mint);box-shadow:0 0 10px var(--mint)}.hero h1,.console-heading h1{margin:20px 0;letter-spacing:-.06em;line-height:.99;font-size:clamp(50px,6.4vw,86px);font-weight:620}.hero h1 em{font-style:normal;color:transparent;background:linear-gradient(95deg,var(--mint-bright),#82d8c1 47%,var(--purple));background-clip:text;-webkit-background-clip:text}.hero-copy>p{max-width:620px;color:var(--muted);font-size:16px;line-height:1.85}.protocol-grid{display:grid;grid-template-columns:repeat(3,1fr);margin-top:42px;border:1px solid var(--line);border-radius:16px;overflow:hidden;background:#08161173;-webkit-backdrop-filter:blur(15px);backdrop-filter:blur(15px)}.protocol-grid article{padding:19px 22px;display:grid;gap:5px;border-right:1px solid var(--line)}.protocol-grid article:last-child{border-right:0}.protocol-grid strong{font-size:18px;font-weight:600}.protocol-grid span{color:var(--muted);font-size:11px}.login-card{padding:31px;border:1px solid var(--line-strong);border-radius:24px;background:linear-gradient(145deg,#122820f0,#081511e6);box-shadow:var(--shadow);position:relative;overflow:hidden;-webkit-backdrop-filter:blur(25px);backdrop-filter:blur(25px)}.card-glow{position:absolute;width:210px;height:210px;right:-100px;top:-120px;border-radius:50%;background:var(--mint);filter:blur(60px);opacity:.16}.card-heading{display:flex;gap:14px;align-items:center;margin-bottom:28px;position:relative}.card-heading p,.surface-heading p{margin:0 0 3px;color:var(--mint);font-size:10px;font-weight:700;letter-spacing:.13em;text-transform:uppercase}.card-heading h2,.surface-heading h2{margin:0;font-size:21px;letter-spacing:-.025em}.lock-mark{width:43px;height:43px;border-radius:13px;display:grid;place-items:center;background:#72e0ad1a;color:var(--mint);border:1px solid rgba(114,224,173,.18)}.lock-mark svg{width:21px;fill:none;stroke:currentColor;stroke-width:1.7}form{position:relative}.login-card form{display:grid;gap:17px}label{display:grid;gap:7px;color:var(--muted);font-size:11px;font-weight:600;letter-spacing:.04em}input{width:100%;color:var(--text);background:#030c0994;border:1px solid var(--line);border-radius:11px;padding:12px 13px;transition:border-color .18s,background .18s}input::placeholder{color:#5f786c}input:hover,input:focus{border-color:#72e0ad75;background:#030c09cc}.primary-button{width:100%;margin-top:5px;padding:13px 16px;border:0;border-radius:11px;display:flex;justify-content:center;align-items:center;gap:8px;color:#062015;background:linear-gradient(100deg,var(--mint-bright),var(--mint));font-weight:750;cursor:pointer;box-shadow:0 10px 35px #72e0ad29;transition:.18s ease}.primary-button:hover{transform:translateY(-1px);filter:brightness(1.05)}.primary-button svg{width:18px;fill:none;stroke:currentColor;stroke-width:2}.privacy-note{margin:18px 0 0;color:#718b7e;font-size:10px;line-height:1.5;display:flex;gap:8px}.privacy-note span{color:var(--mint);font-size:7px;margin-top:3px}.console-view{padding:64px 0 90px;min-height:calc(100vh - 150px)}.console-heading{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:38px}.console-heading h1{font-size:clamp(42px,5vw,64px);margin:13px 0 7px}.console-heading p{margin:0;color:var(--muted)}.secondary-button{padding:10px 15px;border-radius:10px;border:1px solid var(--line-strong);background:transparent;cursor:pointer}.secondary-button:hover{border-color:var(--mint);color:var(--mint-bright)}.metric-grid{display:grid;grid-template-columns:repeat(4,1fr);border:1px solid var(--line);background:#08161185;border-radius:18px;overflow:hidden;margin-bottom:22px}.metric-grid article{min-height:134px;padding:23px;display:flex;flex-direction:column;border-right:1px solid var(--line)}.metric-grid article:last-child{border-right:0}.metric-grid span{color:var(--muted);font-size:11px}.metric-grid strong{margin:12px 0 8px;font-size:27px;font-weight:620;letter-spacing:-.04em}.metric-grid small{color:#668075;font-size:10px}.content-grid{display:grid;grid-template-columns:1.25fr .75fr;gap:22px}.surface{border:1px solid var(--line);border-radius:18px;background:var(--surface);padding:25px;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.surface-heading{display:flex;justify-content:space-between;align-items:center;margin-bottom:22px}.secure-badge{color:var(--mint);background:#72e0ad14;border:1px solid rgba(114,224,173,.18);padding:6px 9px;border-radius:999px;font-size:9px;letter-spacing:.08em}.surface-intro{margin:-9px 0 19px;color:var(--muted);font-size:12px;line-height:1.65}.create-form{margin-bottom:20px;padding:16px;border-radius:13px;background:#040f0b6b;border:1px solid var(--line)}.create-form>div{display:flex;gap:8px;margin-top:8px}.create-form input{padding:10px 12px}.create-form button{border:0;border-radius:9px;padding:0 17px;color:#082016;background:var(--mint);font-weight:700;cursor:pointer}.vault-list,.device-list{display:grid;gap:9px}.vault-item,.device-item{min-width:0;border:1px solid var(--line);background:#06130e7a;border-radius:13px;padding:15px 16px;display:flex;align-items:center;gap:13px}.vault-symbol,.device-symbol{width:38px;height:38px;flex:0 0 auto;display:grid;place-items:center;border-radius:11px;background:#72e0ad14;color:var(--mint);font-weight:700}.device-symbol{color:var(--purple);background:#a991ff14}.item-copy{min-width:0;flex:1}.item-copy strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.item-copy small{color:var(--muted);font-size:10px}progress{display:block;width:100%;height:4px;margin-top:9px;border:0;border-radius:99px;overflow:hidden;accent-color:var(--mint)}progress::-webkit-progress-bar{background:#ffffff0f}progress::-webkit-progress-value{background:linear-gradient(90deg,var(--mint),var(--purple))}.item-side{text-align:right;flex:0 0 auto}.item-side strong{display:block;font-size:12px}.item-side small{color:var(--muted);font-size:9px}.danger-button{border:1px solid rgba(255,142,142,.25);color:var(--danger);background:#ff8e8e0d;border-radius:8px;padding:7px 9px;cursor:pointer;font-size:10px}.danger-button:hover{background:#ff8e8e1f}.revoked{opacity:.48}.empty-state{min-height:100px;display:grid;place-items:center;color:var(--muted);border:1px dashed var(--line);border-radius:12px;font-size:12px}footer{width:min(1280px,calc(100% - 48px));min-height:62px;margin:0 auto;border-top:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;color:#597166;font-size:9px;letter-spacing:.04em;position:relative;z-index:1}.toast{position:fixed;z-index:10;right:24px;bottom:24px;max-width:min(420px,calc(100% - 48px));padding:13px 16px;border:1px solid var(--line-strong);border-radius:11px;background:#142b23;box-shadow:var(--shadow);font-size:12px}.toast.error{color:#ffd2d2;border-color:#ff8e8e59;background:#321a1a}@media (max-width: 920px){.hero{grid-template-columns:1fr;padding-top:76px}.hero-copy{max-width:none}.login-card{max-width:560px;width:100%}.content-grid{grid-template-columns:1fr}.metric-grid{grid-template-columns:repeat(2,1fr)}.metric-grid article:nth-child(2){border-right:0}.metric-grid article:nth-child(-n+2){border-bottom:1px solid var(--line)}}@media (max-width: 620px){.topbar,main,footer{width:min(100% - 28px,1180px)}.topbar{height:74px}.brand small,.status-chip b,footer span:last-child{display:none}.status-chip{padding:0 9px}.hero{padding:52px 0 64px;gap:45px}.hero h1{font-size:43px}.hero-copy>p{font-size:14px}.protocol-grid article{padding:15px 11px}.protocol-grid strong{font-size:14px}.login-card,.surface{padding:21px}.console-heading{align-items:flex-start;gap:18px}.metric-grid{grid-template-columns:1fr}.metric-grid article{min-height:116px;border-right:0;border-bottom:1px solid var(--line)}.metric-grid article:last-child{border-bottom:0}.create-form>div{flex-direction:column}.create-form button{padding:10px}}@media (prefers-reduced-motion: reduce){*,*:before,*:after{scroll-behavior:auto!important;transition:none!important}} diff --git a/server sync/sync_server/static/assets/index-CV7tuz7b.js b/server sync/sync_server/static/assets/index-CsQwWg1J.js similarity index 100% rename from server sync/sync_server/static/assets/index-CV7tuz7b.js rename to server sync/sync_server/static/assets/index-CsQwWg1J.js diff --git a/server sync/sync_server/static/index.html b/server sync/sync_server/static/index.html index 9e737fc..71990d1 100644 --- a/server sync/sync_server/static/index.html +++ b/server sync/sync_server/static/index.html @@ -6,8 +6,8 @@ OpenNexus Sync Console - - + +
-- 2.43.0 From 4feeee4d1a38828a26d62c9e03fbf11083732ece Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Sat, 12 Sep 2026 15:13:10 +0800 Subject: [PATCH 25/25] =?UTF-8?q?fix(release):=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E4=B8=8E=E6=A1=8C=E9=9D=A2=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 2 +- frontend/src/services/themeImport.spec.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 3e53816..23dcc79 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "notes-agent-frontend", "private": true, - "version": "0.2.0", + "version": "0.3.0-alpha.1", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/services/themeImport.spec.ts b/frontend/src/services/themeImport.spec.ts index d1a819b..7aaba83 100644 --- a/frontend/src/services/themeImport.spec.ts +++ b/frontend/src/services/themeImport.spec.ts @@ -5,6 +5,9 @@ import { decodeThemePackage, fetchThemePackage, inspectThemePackage, installThem import paper from '@/assets/themes/paper-moments.theme?raw' afterEach(() => vi.unstubAllGlobals()) +it('uses the desktop release version for compatibility checks', () => { + expect(THEME_APP_VERSION).toBe('0.3.0-alpha.1') +}) it.each(['999.0.0', 'bad', '0.2'])('rejects unsupported minimum app version %s at inspection and install', async version => { const source = paper.replace(/min_app_version:.*\r?\n/, `min_app_version: ${version}\n`) expect((await inspectThemePackage(source)).compatible).toBe(false) -- 2.43.0