docs: 将仓库代码注释统一为中文
CI / docs-check (push) Canceled after 0s
CI / backend-test (push) Canceled after 0s
CI / service-test (push) Canceled after 0s
CI / frontend-test (push) Canceled after 0s
CI / rust-core (push) Canceled after 0s
CI / docs-check (pull_request) Canceled after 0s
CI / backend-test (pull_request) Canceled after 0s
CI / service-test (pull_request) Canceled after 0s
CI / frontend-test (pull_request) Canceled after 0s
CI / rust-core (pull_request) Canceled after 0s

This commit is contained in:
2026-09-10 00:40:56 +08:00
parent 51c592841d
commit d703ab64e3
249 changed files with 707 additions and 900 deletions
+1 -2
View File
@@ -53,7 +53,6 @@ windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Securit
[build-dependencies]
tauri-build = { version = "2", optional = true , features = [] }
# Cryptographic KDFs retain their production work factors in debug/test runs.
# Optimize dependencies rather than weakening those factors for local execution.
# 加密 KDF 在调试/测试运行中保留其生产工作因素。优化依赖关系,而不是削弱本地执行的这些因素。
[profile.dev.package."*"]
opt-level = 2
+7 -8
View File
@@ -1,4 +1,4 @@
//! Trusted Core process supervisor. The WebView never receives session material.
//! 可信的 Core 进程监管器;WebView 永远不会接触会话材料。
use command_group::{CommandGroup, GroupChild};
use hmac::{Hmac, Mac};
use serde::Deserialize;
@@ -14,7 +14,7 @@ use zeroize::Zeroizing;
type Result<T> = std::result::Result<T, String>;
pub type Broker = Arc<dyn Fn(&serde_json::Value) -> Result<serde_json::Value> + Send + Sync>;
/// The manifest is embedded in the Host at build time, never loaded from the installation.
/// 清单在构建时嵌入到 Host 中,从未从安装中加载。
pub fn verify_bundle(root: &Path, manifest: &str) -> Result<()> {
use sha2::Digest;
use std::collections::BTreeMap;
@@ -182,7 +182,7 @@ impl Drop for Session {
}
std::thread::sleep(Duration::from_millis(25));
}
// Kill the entire group even if its leader has exited.
// 即使主进程已经退出,也要终止整个进程组。
let _ = self.child.kill();
let _ = self.child.wait();
}
@@ -200,7 +200,7 @@ pub struct CoreSupervisor {
bundle_manifest: Option<String>,
}
/// Host-only request context; deliberately neither Serialize nor Debug.
/// Host 请求上下文;特意既不序列化也不调试。
pub struct RequestSession {
pub url: String,
pub authorization: Zeroizing<String>,
@@ -304,7 +304,7 @@ impl CoreSupervisor {
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null());
// Runtime requirements only; never copy Provider tokens or general PATH.
// 这里只复制运行时必需项,绝不复制提供商令牌或通用 PATH
for key in [
"SystemRoot",
"WINDIR",
@@ -323,7 +323,7 @@ impl CoreSupervisor {
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
command.creation_flags(0x08000000); // CREATE_NO_WINDOW
command.creation_flags(0x08000000); // 使用 CREATE_NO_WINDOW
}
let child = {
#[cfg(windows)]
@@ -397,8 +397,7 @@ impl CoreSupervisor {
}
continue;
}
// A child has no broker authority until its ready frame has
// passed the protocol, identity, generation, and HMAC checks.
// 子级在其就绪帧通过协议、身份、生成和 HMAC 检查之前没有代理权限。
if !activated {
break;
}
+18 -26
View File
@@ -1,7 +1,7 @@
//! Device-local Stronghold broker. No public IPC returns secret bytes.
//! 设备本地 Stronghold 代理;任何公开 IPC 都不会返回机密字节。
//!
//! Stronghold Store contains AEAD ciphertext, including while unlocked. Snapshot
//! and salt are one atomic envelope, so password changes cannot tear two files.
//! Stronghold 存储即使在解锁期间也只包含 AEAD 密文。快照与盐构成一个原子整体,
//! 避免密码变更使两个文件处于不一致状态。
use argon2::{Algorithm, Argon2, Params, Version};
use chacha20poly1305::{
aead::{Aead, Payload},
@@ -157,8 +157,7 @@ pub struct CredentialId {
}
impl CredentialId {
/// Preserve opaque legacy references. Hashed Plugin/MCP IDs remain isolated
/// from Provider IDs; only the trusted Core adapter can use these aliases.
/// 保留不透明的遗留引用。散列 Plugin/MCP ID 与提供商 ID 保持隔离;只有受信任的 Core 适配器才能使用这些别名。
pub fn legacy(id: &str) -> Self {
let scope = if let Some(owner) = id.strip_prefix("plugin.") {
Scope::Plugin(owner.into())
@@ -348,16 +347,14 @@ fn verify_migration_backup(
pub struct CredentialBroker {
path: PathBuf,
unlocked: Option<Unlocked>,
// Separate stable inode: snapshots are atomically replaced, so locking the
// snapshot itself would not protect the next writer after replacement.
// 单独的稳定索引节点:快照被原子替换,因此锁定快照本身不会保护替换后的下一个写入者。
ownership: Option<fs::File>,
lock_epoch: Arc<AtomicU64>,
unlocked_epoch: u64,
}
impl CredentialBroker {
/// Source comes from the native file picker, never a raw WebView path.
/// Import is idempotent; conflicting IDs stop the entire transaction.
/// 源来自本机文件选择器,而不是原始 WebView 路径。导入是幂等的;冲突的 ID 会停止整个事务。
pub fn import_fernet(
&mut self,
directory: &Path,
@@ -459,8 +456,7 @@ impl CredentialBroker {
}
}
fs::create_dir_all(&backup).map_err(|_| "MIGRATION_BACKUP_FAILED")?;
// Backups contain ciphertext; the legacy key is sealed under the already
// unlocked device key, rather than adding another plaintext master.key.
// 备份包含密文;旧密钥被密封在已解锁的设备密钥下,而不是添加另一个明文 master.key。
let mut nonce = [0u8; 12];
rand::rngs::OsRng
.try_fill_bytes(&mut nonce)
@@ -506,7 +502,7 @@ impl CredentialBroker {
state.state = "copied".into();
persist_state(&journal, &state)?;
checkpoint("copied")?;
// Re-open the committed Stronghold snapshot, not the in-memory cache.
// 重新打开提交的 Stronghold 快照,而不是内存缓存。
let envelope = fs::read(&self.path).map_err(|_| "MIGRATION_VERIFY_FAILED")?;
let mut temporary =
tempfile::NamedTempFile::new_in(&backup).map_err(|_| "MIGRATION_VERIFY_FAILED")?;
@@ -552,8 +548,7 @@ impl CredentialBroker {
result
}
/// Deletes only the verified legacy files and this migration's encrypted backup.
/// The native Host must obtain explicit user confirmation for source_sha256 first.
/// 仅删除已验证的旧文件和此迁移的加密备份。本机 Host 必须首先获得 source_sha256 的明确用户确认。
pub fn cleanup_fernet(
&mut self,
directory: &Path,
@@ -733,7 +728,7 @@ impl CredentialBroker {
let source = CredentialId::legacy(old);
let target =
CredentialId::legacy(new.as_str().ok_or("HOST_REQUEST_INVALID")?);
// ID migrations cannot change Provider/Plugin/MCP families.
// ID 迁移无法更改提供商/Plugin/MCP 系列。
if std::mem::discriminant(&source.scope)
!= std::mem::discriminant(&target.scope)
{
@@ -750,8 +745,7 @@ impl CredentialBroker {
moves.push((source_key, target_key, value));
}
}
// Reject cycles/overlapping source+destination rather than deleting
// a newly written value midway through a multi-ID migration.
// 拒绝循环/重叠源+目标,而不是在多 ID 迁移中途删除新写入的值。
if moves.iter().any(|(old, new, _)| {
old != new && moves.iter().any(|(source, _, _)| source == new)
}) {
@@ -879,7 +873,7 @@ impl CredentialBroker {
#[cfg(windows)]
{
use std::os::windows::fs::OpenOptionsExt;
options.share_mode(0x1 | 0x2); // Do not allow replacing the held lock file.
options.share_mode(0x1 | 0x2); // 不允许替换保留的锁定文件。
}
#[cfg(unix)]
{
@@ -906,7 +900,7 @@ impl CredentialBroker {
let mut salt = [0u8; 32];
salt.copy_from_slice(&data[8..40]);
let session = Unlocked::derive(password, salt)?;
// Backups can be on read-only media. This temporary file contains ciphertext only.
// 备份可以位于只读介质上。该临时文件仅包含密文。
let mut temp = tempfile::NamedTempFile::new().map_err(|_| "CREDENTIAL_IO_FAILED")?;
temp.write_all(&data[40..])
.map_err(|_| "CREDENTIAL_IO_FAILED")?;
@@ -921,7 +915,7 @@ impl CredentialBroker {
Ok(session)
}
/// Native picker selected destination; backup is encrypted and never overwrites.
/// 本机选择器选择的目的地;备份已加密并且永远不会覆盖。
pub fn backup(&self, destination: &Path) -> Result<()> {
self.session()?;
let parent = destination.parent().ok_or("CREDENTIAL_PATH_INVALID")?;
@@ -938,8 +932,7 @@ impl CredentialBroker {
Ok(())
}
/// Validate every record before atomic replacement; preserve the previous encrypted file.
/// Caller must obtain explicit confirmation through the native dialog.
/// 在原子替换之前验证每条记录;保留之前的加密文件。调用者必须通过本机对话框获得明确的确认。
pub fn restore(&mut self, source: &Path, password: Zeroizing<Vec<u8>>) -> Result<usize> {
if !self.is_locked() {
return Err("CREDENTIALS_MUST_LOCK".into());
@@ -976,7 +969,7 @@ impl CredentialBroker {
previous.keep().map_err(|_| "CREDENTIAL_IO_FAILED")?;
}
session.persist(&self.path)?;
// Restoration deliberately leaves the vault locked; no implicit permission grant.
// 恢复特意将金库锁定;没有隐式许可授予。
Ok(keys.len())
}
@@ -996,7 +989,7 @@ impl CredentialBroker {
.and_then(|_| session.persist(&self.path));
if result.is_err() {
self.lock();
} // Never serve uncommitted memory after disk failure.
} // 磁盘故障后切勿服务未提交的内存。
result
}
pub fn delete(&mut self, id: &CredentialId) -> Result<()> {
@@ -1011,8 +1004,7 @@ impl CredentialBroker {
}
result
}
/// Internal consumers must supply the scope established by the Host dispatcher.
/// This method must never be registered as a Tauri command.
/// 内部消费者必须提供 Host 调度程序建立的范围。此方法绝不能注册为 Tauri 命令。
pub fn resolve(&self, caller: &Scope, id: &CredentialId) -> Result<Option<Zeroizing<Vec<u8>>>> {
if caller != &id.scope {
return Err("CREDENTIAL_SCOPE_DENIED".into());
@@ -1,5 +1,5 @@
//! Host-memory call reviews. The UI/registry must establish actual user consent
//! before confirm; no renderer command or automatic-consent policy is added here.
//! Host 内存中的调用审核。UI 或注册表必须先确认用户确已授权,再执行确认;
//! 此处不提供渲染进程命令,也不设置自动授权策略。
use crate::{
extension_mcp_tools::{Description, Tool},
extension_permit::{Claims, ExecutionKind},
@@ -26,7 +26,7 @@ pub struct Identity {
execution_digest: String,
}
impl Identity {
/// Only called after launch permit/entry/context validation.
/// 仅在启动许可/条目/上下文验证后调用。
pub(crate) fn from_claims(claims: &Claims) -> Result<Self> {
let bytes = Zeroizing::new(
serde_json::to_vec(claims)
@@ -64,8 +64,7 @@ struct Pending {
expires: Instant,
bytes: usize,
}
/// An in-process, non-cloneable, non-serializable, single-consumption capability.
/// Tool name and arguments cannot be replaced after review confirmation.
/// 进程内、不可克隆、不可序列化、单次消耗功能。审核确认后,工具名称和参数无法更换。
pub struct ApprovedCall {
instance: String,
epoch: String,
@@ -127,7 +126,7 @@ impl Gate {
valid_for_seconds: 120,
})
}
/// The authenticated Host approval route must verify user consent first.
/// 经过身份验证的 Host 批准路线必​​须首先验证用户同意。
pub(crate) fn confirm(&mut self, review_id: &str) -> Result<ApprovedCall> {
let call = self
.pending
+1 -1
View File
@@ -1,4 +1,4 @@
//! Only the local main window may review Host trust or prepared installations.
//! 只有本地主窗口可以检查 Host 信任或准备的安装。
use super::Host;
use notesagent_host::extension_store::{ExtensionStore, InstallRequest, TrustSetting};
use serde::Deserialize;
+4 -5
View File
@@ -1,4 +1,4 @@
//! Signed, offline configuration schemas. Credentials belong to the vault broker.
//! 已签名的离线配置结构;凭据由 Vault 代理管理。
use crate::workspace::{HostError, Result};
use serde_json::{json, Value};
@@ -11,7 +11,7 @@ fn walk(value: &Value, depth: usize, nodes: &mut usize, schema: bool) -> Result<
Value::Object(map) => {
for (key, value) in map {
if schema && matches!(key.as_str(), "$ref" | "$dynamicRef" | "$recursiveRef") {
// Recursive/unbounded schema execution is not allowed in the Host.
// Host 中不允许递归/无界模式执行。
return Err(HostError::new("EXTENSION_CONFIG_REFERENCE"));
}
if !schema
@@ -44,8 +44,7 @@ fn walk(value: &Value, depth: usize, nodes: &mut usize, schema: bool) -> Result<
Ok(())
}
// Examine every applicable schema branch; an alternative branch must not
// turn a secret declaration back into persistable plaintext.
// 检查每个适用的模式分支;替代分支不得将秘密声明转回可持久的明文。
fn reject_secrets(schema: &Value, instance: &Value) -> Result<()> {
let Some(map) = schema.as_object() else {
return Ok(());
@@ -124,7 +123,7 @@ fn reject_secrets(schema: &Value, instance: &Value) -> Result<()> {
Ok(())
}
/// Schema is read from the verified manifest, never from the proposed configuration.
/// 架构是从已验证的清单中读取的,而不是从建议的配置中读取的。
pub fn validate(manifest: &Value, configuration: &Value) -> Result<()> {
if !configuration.is_object() {
return Err(HostError::new("EXTENSION_CONFIG_INVALID"));
+23 -33
View File
@@ -1,4 +1,4 @@
//! Per-instance AppContainer profile ownership. No existing profile is adopted.
//! 每个实例独占其 AppContainer 配置,不接管任何已有配置。
use crate::workspace::{HostError, Result};
use windows_sys::Win32::Security::{
FreeSid, IsValidSid,
@@ -34,7 +34,7 @@ impl Profile {
)
};
if status < 0 {
// In particular, ERROR_ALREADY_EXISTS must not transfer ownership.
// 特别是,ERROR_ALREADY_EXISTS 不得转让所有权。
if !sid.is_null() {
unsafe {
FreeSid(sid);
@@ -52,25 +52,22 @@ impl Profile {
}
Ok(profile)
}
/// Borrowed SID for SECURITY_CAPABILITIES. Valid only while this owner lives.
/// SECURITY_CAPABILITIES 借用的 SID,仅在此所有者对象存续期间有效。
pub fn sid(&self) -> PSID {
self.sid
}
/// Grant this instance read/execute access to one Host-owned package object.
/// The caller must open it without following reparse points and retain the
/// verified package handles for the entire launch. No recursive inheritance
/// is used: every directory and file must be checked and granted separately.
/// This adds an ACE; it does not sanitize pre-existing permissions.
/// 授予当前实例读取和执行一个 Host 所有包对象的权限。调用方打开对象时不得跟随重解析点,
/// 并须在整个启动期间持有已验证的包句柄。这里不使用递归继承,每个目录和文件都要分别检查、授权。
/// 此操作只会添加一条 ACE,不会清理已有权限。
pub fn grant_package_read_execute(&self, object: &std::fs::File) -> Result<()> {
self.update_package_access(object, false)
}
/// Remove only this freshly-created instance's allowed ACEs, using the
/// original held object handle. Other principals keep their current ACLs.
/// 使用最初持有的对象句柄,仅移除这个新实例对应的允许 ACE;其他安全主体的 ACL 保持不变。
pub fn revoke_package_access(&self, object: &std::fs::File) -> Result<()> {
self.update_package_access(object, true)
}
fn update_package_access(&self, object: &std::fs::File, revoke: bool) -> Result<()> {
// Serialize Host read/merge/write operations across concurrent instances.
// 跨并发实例序列化 Host 读/合并/写操作。
let _lock = PACKAGE_ACL_LOCK
.lock()
.map_err(|_| HostError::new("EXTENSION_CONTAINER_ACL_FAILED"))?;
@@ -125,8 +122,7 @@ impl Profile {
)
};
let _descriptor = LocalAllocation(descriptor);
// A null DACL grants everyone full access, so fail closed rather than
// silently treating it as a suitably isolated package object.
// 空 DACL 会向所有人授予完全访问权限,因此这里必须拒绝处理,不能误判为已妥善隔离的包对象。
if status != 0 || old_acl.is_null() {
return Err(HostError::new("EXTENSION_CONTAINER_ACL_FAILED"));
}
@@ -205,7 +201,7 @@ impl Profile {
}
result
}
/// Stop all container processes and close their handles before removal.
/// 在删除之前停止所有容器进程并关闭其句柄。
pub fn remove(mut self) -> Result<()> {
self.remove_inner()
}
@@ -221,7 +217,7 @@ impl Profile {
}
impl Drop for Profile {
fn drop(&mut self) {
// Explicit remove reports failures; drop is a final best-effort retry.
// 显式删除报告失败; drop 是最后的尽力重试。
let _ = self.remove_inner();
if !self.sid.is_null() {
unsafe {
@@ -302,7 +298,7 @@ mod tests {
assert_eq!(unsafe { EqualSid(one.sid(), two.sid()) }, 0);
let name = String::from_utf16(&one.name[..one.name.len() - 1]).unwrap();
assert!(Profile::create_named(name.clone()).is_err());
// Repeated collision must still fail: the failing owner did not delete it.
// 重复发生名称冲突时仍须失败:创建失败的一方并不拥有该配置,也无权删除它。
assert!(Profile::create_named(name.clone()).is_err());
one.remove().unwrap();
Profile::create_named(name).unwrap().remove().unwrap();
@@ -362,8 +358,7 @@ mod tests {
profile.remove().unwrap();
}
// Only tests use cmd.exe, with fixed commands and controlled temporary paths.
// A production extension launcher must use a verified entry, never a shell.
// 只有测试会通过 cmd.exe 运行固定命令和受控临时路径;生产扩展启动器必须使用已验证入口,不能调用 shell。
fn checked_process(profile: &Profile, command: Option<&str>) -> Option<u32> {
let executable = std::path::PathBuf::from(std::env::var_os("SystemRoot").unwrap())
.join("System32/cmd.exe");
@@ -391,7 +386,7 @@ mod tests {
if let Some(data) = data.take() {
let suspended =
crate::extension_process::Suspended::create(profile, executable, data).unwrap();
// Controlled test fixture only; no user extension is authorized here.
// 仅用于受控测试夹具;此处没有授权任何用户扩展。
let running = unsafe { suspended.resume().unwrap() };
let result = running.wait(std::time::Duration::from_secs(10)).unwrap();
assert!(result.is_some());
@@ -612,7 +607,7 @@ mod tests {
let read = format!("set /p value=<\"{}\"", payload.display());
assert_ne!(checked_process(&profile, Some(&read)), Some(0));
profile.grant_package_read_execute(&root_handle).unwrap();
// The directory ACE does not propagate to existing children.
// 目录 ACE 不会传播到现有子级。
assert_ne!(checked_process(&profile, Some(&read)), Some(0));
profile.grant_package_read_execute(&file_handle).unwrap();
assert_eq!(checked_process(&profile, Some(&read)), Some(0));
@@ -759,8 +754,7 @@ mod tests {
profile.grant_package_read_execute(&root).unwrap();
profile.grant_package_read_execute(&entry).unwrap();
}
// Exercise the actual builder, not a test-side quote decoder. The child
// compares argv and its entire environment without logging values.
// 测试真实构建器,而不是在测试侧另写引号解码器;子进程会比较 argv 和完整环境,但不会记录具体值。
let args = [
"launch",
"",
@@ -822,9 +816,8 @@ mod tests {
vault_id: uuid::Uuid::new_v4().to_string(),
platform: "windows".into(),
policy_version: "1".into(),
// This probe checks argv/environment, not expiry. Keep its permit
// longer than the bounded process observation under parallel load.
// The dedicated expiry probe below still uses a two-second lease.
// 此探测器检查 argv 与环境,不验证过期行为。许可有效期须覆盖并行负载下的有界进程观测;
// 下方专用的过期探测仍使用两秒租期。
expires_at_ms: 120_000,
};
broker
@@ -858,8 +851,7 @@ mod tests {
running.active_test_processes().ok(),
);
drop(running);
// Actual native RPC: the child cannot name an identity or connect to
// a shared endpoint; only its own stdio pipe reaches this broker.
// 实际本机 RPC:子进程无法命名身份或连接到共享端点;只有它自己的 stdio 管道才能到达该代理。
{
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Storage::FileSystem::{
@@ -1335,7 +1327,7 @@ mod tests {
"expiry" => {}
_ => drop(issuer),
}
// The monitor must act without check_authorization or tool polling.
// 监视器必须在没有 check_authorization 或工具轮询的情况下运行。
assert!(running
.wait(std::time::Duration::from_secs(5))
.unwrap()
@@ -1397,7 +1389,7 @@ mod tests {
let deadline = running
.start_test_tool_call(std::time::Duration::from_millis(100))
.unwrap();
// No check() or finish() drives expiration; wait only on the OS process.
// 过期处理不依赖 check() finish() 驱动;这里只等待 OS 进程退出。
assert!(running
.wait(std::time::Duration::from_secs(5))
.unwrap()
@@ -1444,7 +1436,7 @@ mod tests {
("udp", udp.local_addr().unwrap()),
] {
let address = address.to_string();
// The exact executable and target work outside containment.
// 验证同一个可执行文件与目标在容器隔离之外能够正常工作。
assert!(std::process::Command::new(&executable)
.args([mode, &address])
.status()
@@ -1470,9 +1462,7 @@ mod tests {
&std::collections::BTreeMap::new(),
)
.unwrap();
// Loopback isolation can silently drop packets. TCP must
// explicitly report denial or timeout; UDP send may succeed,
// but no datagram may reach the controlled listener below.
// 环回隔离可以静默丢弃数据包。 TCP必须明确报告拒绝或超时; UDP 发送可能会成功,但没有数据报可能到达下面的受控侦听器。
let exit = checked_executable_data(&profile, &executable, None, Some(data));
eprintln!("container network probe {mode} {address}: {exit:?}");
if mode == "tcp" {
+4 -8
View File
@@ -1,4 +1,4 @@
//! Per-tool-call deadline. A persistent server does not have a 60-second lifetime.
//! 每个工具调用的截止时间。持久服务器的生命周期不是 60 秒。
use crate::{
extension_job::Job,
workspace::{HostError, Result},
@@ -18,10 +18,7 @@ struct State {
wake: Condvar,
outcome: AtomicU8,
}
/// Host-owned guard, created before dispatching a tool call. Finish it only when
/// the call completes. Expiration kills the entire instance group even if the
/// caller never polls. Dropping without finish aborts the instance; only explicit
/// successful completion cancels the timer while preserving the server.
/// Host 拥有的防护,在调度工具调用之前创建。仅当呼叫完成时才完成。即使调用者从不轮询,过期也会杀死整个实例组。未完成就丢弃会中止实例;只有显式成功完成才能取消计时器,同时保留服务器。
pub struct ToolDeadline {
state: Arc<State>,
job: Arc<Job>,
@@ -91,8 +88,7 @@ impl ToolDeadline {
_ => Ok(()),
}
}
/// Completion cannot cancel an already elapsed budget, even if the timer
/// thread has not yet been scheduled to observe expiration.
/// 完成无法取消已用完的预算,即使尚未安排计时器线程来观察到期情况。
pub fn finish(mut self) -> Result<()> {
self.stop();
match self.state.outcome.load(Ordering::Acquire) {
@@ -102,7 +98,7 @@ impl ToolDeadline {
_ => Err(HostError::new("EXTENSION_TOOL_DEADLINE_EXCEEDED")),
}
}
/// Explicit abandonment terminates the instance and reports kill failures.
/// 显式放弃终止实例并报告终止失败。
pub fn cancel(mut self) -> Result<()> {
let result = self.job.terminate();
self.stop();
@@ -1,4 +1,4 @@
//! Deterministic, bounded dependency planning; no mutation, activation or automatic downloads.
//! 确定性、有界依赖规划;没有突变、激活或自动下载。
use crate::{
extension_package::Release,
workspace::{hash, HostError, Result},
@@ -410,7 +410,7 @@ mod tests {
resolve(&candidates).unwrap_err().code,
"EXTENSION_DEPENDENCY_COMPLEXITY"
);
// A valid-size graph with oversized permission metadata must not create an unbounded IPC result.
// 节点数合法但权限元数据过大的依赖图,也不能产生无界的 IPC 结果。
let mut wide = vec![candidate("root", "1.0.0", &[])];
for i in 0..100 {
let id = format!("leaf-{i:03}");
@@ -421,7 +421,7 @@ mod tests {
.collect();
wide.push(leaf);
}
// Split the direct dependencies to respect the per-release 64-entry limit.
// 拆分直接依赖项以遵守每个版本 64 个条目的限制。
let second = wide[0].release.dependencies.split_off("leaf-050");
wide[0]
.release
@@ -1,6 +1,4 @@
//! Instance-bound file RPC policy. Transport must bind one Broker to one
//! authenticated instance. Write commits currently use Workspace's transaction;
//! full handle-relative write hardening is required before untrusted activation.
//! 实例绑定文件 RPC 策略。 Transport 必须将一个 Broker 绑定到一个经过身份验证的实例。写入提交当前使用 Workspace 的事务;在不受信任的激活之前,需要完全的句柄相关的写强化。
use crate::{
credentials::CredentialBroker,
extension_permit::{Authority, Claims, Lease, Permit},
@@ -45,7 +43,7 @@ pub struct Broker {
requests: u32,
}
impl Broker {
/// Called by the Host after instance identity binding; never an IPC command.
/// 实例身份绑定后由Host调用;绝不是 IPC 命令。
pub fn bind(
authority: &Authority,
permit: &Permit,
@@ -108,8 +106,7 @@ impl Broker {
}
self.lease.check()
}
/// The transport must apply MAX_FRAME_BYTES while reading, before allocation.
/// Hold the Host workspace lock throughout dispatch and commit.
/// 传输在分配之前读取时必须应用 MAX_FRAME_BYTES。在整个调度和提交过程中保持 Host 工作区锁。
pub fn dispatch(&mut self, workspace: &mut Workspace, bytes: &[u8]) -> Result<Value> {
self.lease.check()?;
if workspace.vault_id != self.vault {
@@ -295,7 +292,7 @@ mod tests {
call(&mut broker, &mut ws, altered).unwrap_err().code,
"OPERATION_PAYLOAD_CONFLICT"
);
// Build a stale CAS with a genuinely new operation ID.
// 使用真正的新操作 ID 构建过期的 CAS。
let mut stale = write.clone();
stale["operation_id"] = json!(uuid::Uuid::new_v4().to_string());
assert_eq!(
+3 -4
View File
@@ -1,5 +1,5 @@
//! Native instance workers. Construct/drop this manager off the UI thread.
//! Production callers must still satisfy the complete launch policy contract.
//! 原生实例工作线程。必须在 UI 线程之外创建和销毁此管理器;
//! 生产调用方仍须满足完整的启动策略约定。
use crate::{
credentials::CredentialBroker,
extension_call_authorization::{Identity, Review},
@@ -46,8 +46,7 @@ pub struct LaunchSpec {
pub vault_id: String,
pub policy_version: String,
pub system_root: PathBuf,
/// Revalidate active install/current trust and all external policy immediately
/// before resume, after expensive package checks. Errors prohibit execution.
/// 在昂贵的软件包检查之后,在恢复之前立即重新验证活动安装/当前信任和所有外部策略。错误禁止执行。
pub before_resume: ResumeCheck,
}
#[derive(Clone, Copy, Serialize, PartialEq, Eq, Debug)]
+1 -2
View File
@@ -1,5 +1,4 @@
//! Bounded IO for Host-created anonymous pipes. Never run shutdown on the UI
//! thread: cancellation waits for the native pipe operations to acknowledge it.
//! 用于 Host 创建的匿名管道的有界 IO。切勿在 UI 线程上运行关闭:取消等待本机管道操作确认它。
use crate::{
extension_job::Job,
extension_stdio::{write_frame, Frames, HostIo, MAX_FRAME_BYTES},
+5 -5
View File
@@ -1,4 +1,4 @@
//! Windows resource containment only. This is NOT a filesystem/network sandbox.
//! 仅负责 Windows 资源隔离,不构成文件系统或网络沙箱。
use crate::workspace::{HostError, Result};
use std::{
mem::size_of,
@@ -91,11 +91,11 @@ impl Job {
}
Ok(())
}
/// Attach before any extension instruction executes. No breakaway flags are enabled.
/// 在任何扩展指令执行之前附加。没有启用任何分离标志。
///
/// # Safety
/// Caller must own an unresumed CREATE_SUSPENDED process and terminate it on
/// any error. Resume only after all AppContainer/handle/permission checks pass.
/// # 安全性
/// 调用方必须拥有尚未恢复执行的 CREATE_SUSPENDED 进程,并在出现任何错误时终止该进程。
/// 只有 AppContainer、句柄与权限检查全部通过后,才能恢复执行。
pub unsafe fn assign_suspended(&self, process: BorrowedHandle<'_>) -> Result<()> {
self.check_resources()?;
if unsafe { AssignProcessToJobObject(self.handle.as_raw_handle(), process.as_raw_handle()) }
@@ -1,5 +1,5 @@
//! Derive launch bytes from a verified permit and Host-bound entry/context.
//! This preparation step does not authorize resume or establish sandbox readiness.
//! 根据已验证许可及 Host 绑定的入口和上下文生成启动数据;
//! 此准备步骤既不授权恢复执行,也不表示沙箱已经就绪。
use crate::{
credentials::{CredentialBroker, CredentialId, Scope},
extension_launch_data::LaunchData,
@@ -11,7 +11,7 @@ use sha2::{Digest, Sha256};
use std::{collections::BTreeMap, path::Path, sync::atomic::Ordering};
use zeroize::Zeroize;
/// Only the Host's selected workspace, policy and container supply these values.
/// 只有 Host 选定的工作区、策略和容器提供这些值。
pub struct Context<'a> {
pub vault_id: &'a str,
pub policy_version: &'a str,
@@ -80,9 +80,7 @@ impl PreparedLaunch {
}
}
impl<'a> LeasedSuspended<'a> {
/// # Safety
/// Live trust, active installation, broker and all sandbox resource policy
/// requirements must also hold. A lease does not establish those conditions.
/// # Safety Live 信任、主动安装、代理和所有沙箱资源策略要求也必须满足。租约不规定这些条件。
pub unsafe fn resume(self) -> Result<crate::extension_process::Running<'a>> {
unsafe { self.process.resume_with_lease(self.lease, self.identity) }
}
@@ -146,8 +144,7 @@ impl Context<'_> {
now_ms: u64,
) -> Result<PreparedLaunch> {
let mut lease = authority.lease(permit, claims, now_ms)?;
// Locking the Host session gates all third-party execution, including
// packages that do not request environment secrets.
// 锁定 Host 会话会限制所有第三方执行,包括不请求环境机密的包。
lease.bind_credential(broker.lock_signal());
if broker.is_locked() {
return Err(HostError::new("CREDENTIALS_LOCKED"));
@@ -163,8 +160,7 @@ impl Context<'_> {
tree: entry.tree_sha256().to_owned(),
})
}
/// Caller must still recheck live trust/permit/session state immediately
/// before resume; returning encoded data is not an execution lease.
/// 调用者仍必须在恢复之前立即重新检查实时信任/许可/会话状态;返回编码数据不是执行租约。
fn build(
&self,
authority: &Authority,
@@ -1,4 +1,4 @@
//! Native argv/environment encoding. This does not authorize or launch a process.
//! 本机 ​​argv/环境编码。这不会授权或启动进程。
use crate::workspace::{HostError, Result};
use std::{collections::BTreeMap, os::windows::ffi::OsStrExt, path::Path};
use zeroize::Zeroize;
@@ -14,9 +14,7 @@ impl Drop for LaunchData {
}
}
impl LaunchData {
/// The Host supplies verified absolute paths and explicitly declared/resolved
/// environment values. Never reads the parent environment. CRT argv rules
/// apply to native executables, not cmd.exe, batch files or shell interpreters.
/// Host 提供经过验证的绝对路径和显式声明/解析的环境值。从不读取父环境。 CRT argv 规则适用于本机可执行文件,而不是 cmd.exe、批处理文件或 shell 解释器。
pub fn new(
executable: &Path,
arguments: &[String],
@@ -50,8 +48,7 @@ impl LaunchData {
if argument.len() > 8192 || argument.contains('\0') {
return Err(bad());
}
// Reserve the full bounded buffers once: do not leave earlier
// copies of resolved values behind through Vec reallocations.
// 保留一次完整的有界缓冲区:不要通过 Vec 重新分配留下解析值的早期副本。
let mut encoded_len = 0;
let mut trailing = 0;
for unit in argument.encode_utf16() {
@@ -94,8 +91,7 @@ impl LaunchData {
if result.command.len() > 32767 {
return Err(bad());
}
// ASCII names give a deterministic Windows case-insensitive order.
// Values remain borrowed until encoded so there are no secret clones.
// ASCII 名称给出确定性的 Windows 不区分大小写的顺序。值在编码之前一直是借用的,因此不存在秘密克隆。
let mut fields: BTreeMap<String, &std::ffi::OsStr> = BTreeMap::new();
for (name, path) in [
("SYSTEMROOT", system_root),
@@ -146,7 +142,7 @@ impl LaunchData {
pub(crate) fn command_mut(&mut self) -> &mut [u16] {
&mut self.command
}
/// Pass with CREATE_UNICODE_ENVIRONMENT; never substitute a null pointer.
/// 通过CREATE_UNICODE_ENVIRONMENT;切勿替换空指针。
pub fn environment(&self) -> &[u16] {
&self.environment
}
+1 -1
View File
@@ -1,4 +1,4 @@
//! Bounded declarative manifest inspection. No includes, environment interpolation or code execution.
//! 有界声明性清单检查。无包含、环境插值或代码执行。
use crate::{
extension_package::{Inventory, Release},
workspace::{HostError, Result},
+1 -2
View File
@@ -1,5 +1,4 @@
//! Serial MCP session over an already-authorized native instance. The Host
//! approval route still must establish user consent and current installation/trust.
//! 通过已授权的本机实例进行串行 MCP 会话。 Host 批准途径仍必须建立用户同意和当前安装/信任。
use crate::{
extension_io::{Event, Pump},
extension_process::Running,
@@ -1,5 +1,4 @@
//! Bounded, offline MCP tool contracts. Descriptions/annotations are untrusted
//! data and never confer permissions. URI content is validated, never fetched.
//! 有界、离线 MCP 工具约定。描述/注释是不受信任的数据,永远不会授予权限。 URI 内容经过验证,从未获取。
use crate::workspace::{HostError, Result};
use base64::Engine;
use serde::Serialize;
+7 -8
View File
@@ -1,4 +1,4 @@
//! Offline verification primitives. Passing these checks does not authorize installation or execution.
//! 离线验证原语。通过这些检查并不意味着授权安装或执行。
use crate::workspace::{hash, HostError, Result};
use base64::{engine::general_purpose::STANDARD, Engine};
use ed25519_dalek::{Signature, VerifyingKey};
@@ -66,7 +66,7 @@ fn version(s: &str) -> bool {
})
}
impl Release {
/// Full offline package check. Online revocation freshness and runtime permissions remain Host responsibilities.
/// 完整执行离线包检查;在线撤销信息的时效性与运行时权限仍由 Host 负责。
pub fn verify_package(
&self,
pinned: &[u8; 32],
@@ -165,7 +165,7 @@ impl Release {
}
Ok(canonical(&value).into_bytes())
}
/// `pinned` must come from the Host trust store, never from the archive or a WebView assertion.
/// `pinned` 必须来自 Host 信任存储,不能取自归档内容或 WebView 声明。
pub fn verify(
&self,
pinned: &[u8; 32],
@@ -204,8 +204,8 @@ pub struct Inventory {
pub expanded_size: u64,
pub manifest: String,
}
// ZipArchive stores names in a map and can hide duplicate central entries. Inspect the
// bounded central directory before handing the archive to its decompressor.
// ZipArchive 使用映射保存名称,可能掩盖重复的中央目录条目。因此在将归档交给解压器前,
// 必须先检查有界的中央目录。
fn directory(bytes: &[u8], max_entries: usize) -> Result<()> {
let bad = || HostError::new("EXTENSION_ZIP_INVALID");
let u16at = |p: usize| -> Result<usize> {
@@ -311,8 +311,7 @@ fn path(name: &str) -> Result<String> {
}
Ok(name.to_owned())
}
/// Checks all bytes (including CRC), without creating any package files.
/// Type-specific manifest schema/identity validation must follow before staging.
/// 检查全部字节(包括 CRC),且不创建任何包文件。暂存前还必须执行对应类型的清单结构与身份校验。
pub fn inspect(release: &Release, bytes: &[u8]) -> Result<Inventory> {
release.validate()?;
if bytes.len() as u64 != release.size || hash(bytes) != release.sha256 {
@@ -340,7 +339,7 @@ pub fn inspect(release: &Release, bytes: &[u8]) -> Result<Inventory> {
.by_index(index)
.map_err(|_| HostError::new("EXTENSION_ZIP_INVALID"))?;
let name = path(file.name())?;
// Include current Rust Unicode case mappings as well as full multi-character folds.
// 包括当前的 Rust Unicode 大小写映射以及完整的多字符折叠。
let folded: String = name
.case_fold()
.flat_map(char::to_uppercase)
+7 -10
View File
@@ -1,5 +1,5 @@
//! Host-only execution permit binding. No IPC caller can mint these permits.
//! The installer must complete user consent and current trust checks before issue.
//! Host 可以绑定执行许可,IPC 调用方无法伪造许可;
//! 安装器签发许可前必须完成用户授权与当前信任检查。
use crate::workspace::{HostError, Result};
use hmac::{Hmac, Mac};
use rand::RngCore;
@@ -19,7 +19,7 @@ use zeroize::Zeroize;
#[serde(tag = "kind", content = "value", deny_unknown_fields)]
pub enum Environment {
Literal(String),
// An opaque credential reference in the Host-derived package scope, never plaintext.
// Host 派生包范围中的不透明凭证引用,绝不是明文。
CredentialScope(String),
}
@@ -51,8 +51,7 @@ pub struct Claims {
pub expires_at_ms: u64,
}
/// Opaque authenticator; the Host retains claims separately. No paths, arguments
/// or credential declarations need to be passed to a renderer with the token.
/// 不透明验证器; Host 保留单独的权利要求。不需要使用令牌将路径、参数或凭据声明传递给渲染器。
pub struct Permit {
mac: [u8; 32],
generation: u64,
@@ -198,15 +197,14 @@ impl Claims {
}
}
impl Authority {
/// Host event wiring only; never expose this signal through IPC.
/// Host 事件接线;切勿通过 IPC 暴露此信号。
pub fn revocation_signal(&self) -> Arc<AtomicU64> {
Arc::clone(&self.generation)
}
pub fn revoke(&self) {
self.generation.fetch_add(1, Ordering::SeqCst);
}
/// Call only after consent and live trust validation. This authenticates the
/// decision; it does not establish sandbox availability or grant broker access.
/// 仅在同意和实时信任验证后才能调用。这证实了该决定;它不会建立沙箱可用性或授予代理访问权限。
pub fn issue(&self, claims: &Claims, now_ms: u64) -> Result<Permit> {
let generation = self.generation.load(Ordering::SeqCst);
let encoded = claims.encoded(now_ms)?;
@@ -250,8 +248,7 @@ impl Authority {
lease.check()?;
Ok(lease)
}
/// Lock/logout/policy invalidation may discard all permits. Restart creates a
/// fresh key, so an old process token cannot silently revive authorization.
/// 锁定/注销/策略失效可能会丢弃所有许可。重新启动会创建一个新密钥,因此旧进程令牌无法静默恢复授权。
pub fn invalidate_all(&mut self) {
self.revoke();
self.key.zeroize();
+6 -12
View File
@@ -1,5 +1,4 @@
//! Windows package handles retained across verification and launch. This pins
//! existing objects; it is not a read-only filesystem mount.
//! Windows 包句柄在验证和启动过程中保留。这会固定现有对象;它不是只读文件系统挂载。
use crate::{
extension_container::Profile,
extension_package::Inventory,
@@ -15,8 +14,8 @@ pub struct PinnedPackage {
files: BTreeMap<String, File>,
tree_sha256: String,
}
/// Scoped ACL ownership, created before any mutation. Release only after all
/// instance processes/handles have closed; drop retries cleanup on error/unwind.
/// 在任何变更前建立限定作用域的 ACL 所有权。只有所有实例进程与句柄均已关闭后才能释放;
/// 若发生错误或栈展开,Drop 会再次尝试清理。
pub struct PackageAccess<'a> {
package: &'a PinnedPackage,
profile: &'a Profile,
@@ -119,9 +118,7 @@ fn directory(parent: &Dir, name: &str) -> Result<Dir> {
Ok(Dir::from_std_file(handle))
}
impl PinnedPackage {
/// `root` and inventory originate from the verified Host store. Keep this
/// owner until the instance stops; no renderer-supplied filesystem path is
/// accepted here. Callers must also constrain ancestors used by native launch.
/// `root` 与清单来自已验证的 Host 存储。实例停止前必须保留此所有者;此处不接受渲染进程提供的文件系统路径。调用方还必须限制原生启动所使用的祖先目录。
pub fn open(root: &Dir, inventory: &Inventory, expected_tree: &str) -> Result<Self> {
let bad = || HostError::new("EXTENSION_STORE_CORRUPT");
if inventory.files.is_empty()
@@ -179,8 +176,7 @@ impl PinnedPackage {
}
pinned.files.insert(path.clone(), handle);
}
// All existing objects are already pinned when verification reopens
// them. Sharing violations or hash mismatches release the entire set.
// 当验证重新打开它们时,所有现有对象都已被固定。共享违规或哈希不匹配会释放整个集合。
pinned.tree_sha256 =
crate::extension_unpack::verify_tree(&pinned.directories[""], inventory)?;
if pinned.tree_sha256 != expected_tree {
@@ -188,9 +184,7 @@ impl PinnedPackage {
}
Ok(pinned)
}
/// Resolve through the owned file handle, then pin the volume-rooted path
/// component by component and compare native file identity. No drive-letter
/// or UNC fallback is permitted if volume GUID lookup is unavailable.
/// 通过拥有的文件句柄进行解析,然后逐个组件固定卷根路径并比较本机文件标识。如果卷 GUID 查找不可用,则不允许驱动器号或 UNC 回退。
pub fn bind_entry(&self, name: &str) -> Result<BoundEntry<'_>> {
use std::{
os::windows::fs::OpenOptionsExt as _,
+9 -14
View File
@@ -1,5 +1,4 @@
//! Windows process ownership primitive. A suspended process is not execution
//! authorization; the extension runtime must complete its checks before resume.
//! Windows 进程所有权原语。暂停的进程不是执行授权;扩展运行时必须在恢复之前完成其检查。
use crate::{
extension_container::Profile,
extension_job::Job,
@@ -51,8 +50,7 @@ impl Attributes {
return Err(bad());
}
value.initialized = true;
// The attribute stores a pointer to SECURITY_CAPABILITIES. The caller
// updates it with storage that remains alive until CreateProcessW.
// 该属性存储指向SECURITY_CAPABILITIES的指针。调用者使用在 CreateProcessW 之前保持活动状态的存储来更新它。
Ok(value)
}
}
@@ -223,8 +221,7 @@ impl<'a> Suspended<'a> {
value.0._bound_entry = Some(entry);
Ok(value)
}
/// Create instance-specific stdio without exposing an address or trusting a
/// self-reported process/package identity. Host endpoints are never inherited.
/// 创建特定于实例的 stdio,而不暴露地址或信任自我报告的进程/包身份。 Host 端点永远不会被继承。
#[cfg(feature = "desktop")]
pub fn create_bound_with_stdio(
profile: &'a Profile,
@@ -254,9 +251,8 @@ impl<'a> Suspended<'a> {
identity: None,
})
}
/// # Safety
/// The same complete resource/broker/trust preconditions as resume apply.
/// This additionally arms revocation monitoring before any instruction resumes.
/// # 安全性
/// 必须满足与 resume 相同的完整资源、代理与信任前提;此外还要在恢复执行任何指令前启用撤销监控。
#[cfg(feature = "desktop")]
pub(crate) unsafe fn resume_with_lease(
self,
@@ -311,8 +307,7 @@ impl Running<'_> {
pub(crate) fn active_test_processes(&self) -> Result<u32> {
self.process.job.active_processes()
}
/// Arm before dispatching a tool request; finish after receiving its result.
/// Failure to arm must prevent dispatch. This does not time server lifetime.
/// 发送工具请求前启动计时,收到结果后结束。若启动计时失败,必须阻止调度;该计时不限制服务器生命周期。
pub fn start_tool_call(&self) -> Result<crate::extension_deadline::ToolDeadline> {
self.check_authorization()?;
crate::extension_deadline::ToolDeadline::arm(&self.process.job)
@@ -324,7 +319,7 @@ impl Running<'_> {
) -> Result<crate::extension_deadline::ToolDeadline> {
crate::extension_deadline::ToolDeadline::arm_test(&self.process.job, budget)
}
/// A bounded observation only. The runtime must enforce the tool deadline.
/// 此处只进行有界观测;工具截止时间必须由运行时强制执行。
pub fn wait(&self, timeout: Duration) -> Result<Option<u32>> {
let milliseconds = u32::try_from(timeout.as_millis())
.ok()
@@ -347,7 +342,7 @@ impl Running<'_> {
_ => Err(HostError::new("EXTENSION_PROCESS_WAIT_FAILED")),
}
}
/// Terminates the entire managed group, including descendants.
/// 终止整个托管组,包括后代。
pub fn terminate(&self) -> Result<()> {
self.process.job.terminate()
}
@@ -458,7 +453,7 @@ mod tests {
unsafe { WaitForSingleObject(observer.as_raw_handle(), 0) },
WAIT_TIMEOUT
);
drop(suspended); // Never resumed any command interpreter instruction.
drop(suspended); // 从未恢复任何命令解释器指令。
assert_eq!(
unsafe { WaitForSingleObject(observer.as_raw_handle(), 5000) },
WAIT_OBJECT_0
@@ -1,4 +1,4 @@
//! Native instance monitor; revocation does not depend on the caller polling.
//! 本机实例监视器;撤销不依赖于调用者轮询。
use crate::{
extension_job::Job,
extension_permit::Lease,
+3 -4
View File
@@ -1,5 +1,4 @@
//! Per-launch anonymous pipes. Only child ends enter the explicit inheritance
//! list. The runtime owns Host ends and must bound frames and cancel blocked IO.
//! 每次启动的匿名管道。只有子端进入显式继承列表。运行时拥有 Host 端,必须绑定帧并取消阻塞的 IO。
use crate::workspace::{HostError, Result};
#[cfg(any(feature = "desktop", test))]
use std::os::windows::io::FromRawHandle;
@@ -55,8 +54,8 @@ impl ChildIo {
},
))
}
/// Own both the pipe ends and the launch lock. Field drop order closes all
/// inheritable ends before allowing a competing Host launch to proceed.
/// 同时持有管道端点与启动锁。字段的销毁顺序会先关闭所有可继承端点,
/// 再允许其他并发 Host 启动继续执行。
pub(crate) fn inherit(self) -> Result<InheritedIo> {
let lock = crate::process_creation::lock().map_err(HostError::new)?;
let guarded = InheritedIo {
+12 -17
View File
@@ -1,4 +1,4 @@
//! Durable verified-package staging. Staging never enables a package or grants permissions.
//! 持久的验证包暂存。暂存从不启用包或授予权限。
use crate::{
extension_package::Release,
workspace::{hash, HostError, Result},
@@ -161,7 +161,7 @@ fn source(value: &str) -> Result<String> {
Ok(url.to_string())
}
impl ExtensionStore {
/// Creates a lock preview from local staged packages. This does not replace online revocation checks.
/// 从本地暂存包创建锁定预览。这不会取代在线撤销检查。
pub fn dependency_plan(
&self,
root_key: &str,
@@ -250,7 +250,7 @@ impl ExtensionStore {
})?;
Ok(rows.collect::<std::result::Result<_, _>>()?)
}
/// Host supplies an existing application-owned directory, never a package-supplied path.
/// Host 提供现有应用程序拥有的目录,而不是包提供的路径。
pub fn open(root: &Path) -> Result<Self> {
ordinary(root)?;
if !root.is_dir() {
@@ -338,8 +338,7 @@ impl ExtensionStore {
})
.transpose()
}
/// Called after the user confirms the displayed fingerprint. A concurrent
/// setting change requires a fresh review; refresh never calls this method.
/// 用户确认显示的指纹后调用。并发设置更改需要重新审核;刷新从不调用此方法。
pub fn confirm_trust(
&mut self,
setting: &TrustSetting,
@@ -358,7 +357,7 @@ impl ExtensionStore {
if old_revision.as_deref() != expected_revision {
return Err(HostError::new("EXTENSION_TRUST_CONFLICT"));
}
// Prevent one canonical URL from silently acquiring a second source identity.
// 防止同一个规范化 URL 在无提示的情况下获得第二个来源标识。
let mut statement = self
.db
.prepare("SELECT setting FROM extension_trust WHERE source=?1")?;
@@ -433,7 +432,7 @@ impl ExtensionStore {
};
Ok(hash(&serde_json::to_vec(&identity).unwrap()))
}
/// A persisted denial is independent of rollback and renewed source consent.
/// 持续拒绝与回滚和更新源同意无关。
pub fn check_not_revoked(
&self,
source_url: &str,
@@ -481,8 +480,7 @@ impl ExtensionStore {
)?;
Ok(())
}
/// Builds the complete consent payload; staging work is allowed, but active
/// pointers, running instances and permissions are untouched.
/// 构建完整的同意有效负载;允许暂存工作,但活动指针、运行实例和权限不受影响。
pub fn installation_preview(&mut self, request: &InstallRequest) -> Result<InstallPreview> {
use crate::extension_transaction::{Change, Target};
let vault = Uuid::parse_str(&request.vault_id)
@@ -586,8 +584,7 @@ impl ExtensionStore {
changes,
})
}
/// Recompute the exact reviewed payload before online checks. The main-window
/// confirmation UI must supply this digest; this method alone is not consent.
/// 在线检查之前重新计算准确的已审核有效负载。主窗口确认 UI 必须提供此摘要;仅此方法并不表示同意。
pub async fn install_confirmed(
&mut self,
operation: &str,
@@ -698,7 +695,7 @@ impl ExtensionStore {
|_| checkpoint(),
)
}
/// Online installation gate, using confirmed Host trust settings only.
/// 在线安装检查点,只使用已确认的 Host 信任设置。
pub async fn switch_online(
&mut self,
operation: &str,
@@ -760,8 +757,7 @@ impl ExtensionStore {
Ok(())
})
}
/// Atomically selects a prepared group after installer policy checks. This
/// method does not stop processes, validate configuration schemas or issue permits.
/// 安装器策略检查通过后,以原子方式选定已准备的分组。此方法不会停止进程、验证配置结构或颁发许可。
pub fn switch_prepared(
&mut self,
operation: &str,
@@ -844,8 +840,7 @@ impl ExtensionStore {
) -> Result<Option<crate::extension_transaction::Active>> {
crate::extension_transaction::active(&self.db, slot)
}
/// Prepare a verified staged package. The caller supplies current signer/revocation
/// policy; persisted preparation does not bypass that policy on replay.
/// 准备经过验证的暂存包。调用者提供当前的签名者/撤销策略;持久准备不会在重放时绕过该策略。
pub fn prepare(
&mut self,
package_key: &str,
@@ -899,7 +894,7 @@ impl ExtensionStore {
)
.optional()?;
if let Some((directory, tree_sha256)) = existing {
// Database data never supplies an arbitrary relative path.
// 数据库数据从不提供任意相对路径。
if Uuid::parse_str(&directory)
.map(|id| id.to_string())
.ok()
@@ -1,4 +1,4 @@
//! Atomic package/configuration pointers. A pointer is never a runtime permission.
//! 原子包/配置指针。指针从来都不是运行时权限。
use crate::workspace::{hash, HostError, Result};
use rusqlite::{params, Connection, OptionalExtension};
use serde::{Deserialize, Serialize};
@@ -143,8 +143,7 @@ fn switch_inner(
})
}
/// `healthy` must come from the Host's matching package/config health probe.
/// Recovery calls this with false; it never reissues any execution permits.
/// healthy”必须来自 Host 的匹配包/配置运行状况探测。恢复称其为 false;它从不重新签发任何执行许可证。
pub fn finish(db: &mut Connection, operation: &str, healthy: bool) -> Result<Receipt> {
let tx = db.transaction()?;
let (before, after, state): (String, String, String) = tx.query_row(
+2 -2
View File
@@ -1,4 +1,4 @@
//! Fresh Community state checked against Host-pinned keys; never TOFU on refresh.
//! 根据 Host 固定密钥检查新的社区状态;刷新时绝不会 TOFU。
use crate::{
extension_package::Release,
workspace::{hash, HostError, Result},
@@ -413,7 +413,7 @@ mod tests {
} else {
"200 OK"
};
// No content length: exercise the streaming cap independently.
// 没有 Content-Length 时,单独验证流式传输上限。
write!(stream, "HTTP/1.1 {status}\r\nConnection: close\r\n\r\n").unwrap();
stream.write_all(&body).unwrap();
});
+4 -5
View File
@@ -1,4 +1,4 @@
//! Private, capability-relative extraction. Prepared trees are not executable installs.
//! 私有的、与能力相关的提取。准备好的树不是可执行安装。
use crate::{
extension_package::{Inventory, Release},
workspace::{HostError, Result},
@@ -95,7 +95,7 @@ fn collect(
Ok(())
}
/// Re-read the exact file set and all content through directory capabilities.
/// 通过目录功能重新读取确切的文件集和所有内容。
pub fn verify_tree(root: &Dir, inventory: &Inventory) -> Result<String> {
let mut found = BTreeSet::new();
collect(root, "", &mut found, &mut 10000, inventory)?;
@@ -162,8 +162,7 @@ pub fn verify_tree(root: &Dir, inventory: &Inventory) -> Result<String> {
Ok(format!("{:x}", tree.finalize()))
}
/// `root` must be an application-owned private staging directory. Trust freshness
/// and permission grants remain installation-layer responsibilities.
/// root”必须是应用程序拥有的私有暂存目录。信任新鲜度和权限授予仍然是安装层的责任。
pub fn prepare(
root: &Dir,
release: &Release,
@@ -207,7 +206,7 @@ pub fn prepare(
}
}
let tree_sha256 = verify_tree(&target, &inventory)?;
// Failed preparations remain isolated UUID directories; never expose them as current.
// 失败的准备工作仍然隔离UUID目录;切勿将它们暴露为当前状态。
Ok(Prepared {
directory,
tree_sha256,
+2 -2
View File
@@ -49,7 +49,7 @@ impl Host {
*active = next;
}
fn lock_credentials(&self) -> Result<(), String> {
// These do not wait for an in-flight unlock/KDF or credential operation.
// 这些不等待进行中解锁/KDF 或凭证操作。
self.extension_authority.revoke();
if let Some(signal) = self.credential_signal.get() {
signal.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
@@ -1159,7 +1159,7 @@ mod lifecycle_tests {
}
let observed = credentials.load(Ordering::SeqCst);
let revoked = extension.load(Ordering::SeqCst);
// Release before asserting, so an assertion cannot deadlock scope join.
// 在断言之前释放,因此断言不能死锁作用域连接。
drop(held);
worker.join().unwrap().unwrap();
assert!(observed > 0);
+5 -6
View File
@@ -1,4 +1,4 @@
//! Immutable payloads are fsynced before any SQLite reference becomes visible.
//! 在任何 SQLite 引用变得可见之前,不可变的有效负载会被 fsync。
use crate::workspace::{hash, HostError, Result, Workspace};
use rusqlite::{params, OptionalExtension};
use sha2::{Digest, Sha256};
@@ -104,7 +104,7 @@ impl Workspace {
.optional()?)
}
}
/// A new write either supplies bytes or references an existing immutable spool.
/// 新写入要么直接提供字节,要么引用现有的不可变暂存文件。
pub(crate) enum WritePayload<'a> {
Inline(&'a [u8]),
Stored { digest: &'a str, size: u64 },
@@ -200,8 +200,8 @@ fn hash_file_info(path: &Path) -> Result<(String, u64)> {
pub(crate) fn verify(path: &Path, digest: &str, size: u64) -> Result<()> {
open_verified(path, digest, size).map(drop)
}
/// Return the verified handle, rewound for use by a streaming caller.
/// Path containment is the caller's responsibility; this is not a sandbox opener.
/// 返回已经验证并回绕到起始位置的句柄,供流式调用方使用。
/// 路径约束由调用方负责;此函数并不负责建立沙箱。
pub(crate) fn open_verified(path: &Path, digest: &str, size: u64) -> Result<fs::File> {
let meta = fs::symlink_metadata(path)?;
if meta.file_type().is_symlink() || !meta.is_file() || meta.len() != size {
@@ -236,8 +236,7 @@ pub(crate) fn copy_verified(
let mut buffer = vec![0; VERIFY_BUFFER_BYTES];
let mut length = 0u64;
loop {
// Even if a file grows after metadata inspection, consume at most the
// declared payload plus one byte, never an unbounded changing stream.
// 即使文件在元数据检查后增长,最多消耗声明的有效负载加上一个字节,而不是无限变化的流。
let limit = size
.saturating_sub(length)
.saturating_add(1)
+1 -1
View File
@@ -1,4 +1,4 @@
//! Portable settings may declare required permissions, but never carry device grants, paths or secrets.
//! 可移植设置可以声明所需的权限,但绝不携带设备授权、路径或秘密。
use crate::workspace::{HostError, Result};
use serde::Deserialize;
use serde_json::Value;
+1 -2
View File
@@ -1,5 +1,4 @@
//! Coordinate Host-controlled Windows launches while inheritable handles exist.
//! This does not serialize foreign libraries that bypass this Host boundary.
//! 在存在可继承手柄的情况下协调 Host 控制的 Windows 启动。这不会序列化绕过此 Host 边界的外部库。
use std::sync::{Mutex, MutexGuard};
static CREATION: Mutex<()> = Mutex::new(());
+1 -1
View File
@@ -1,4 +1,4 @@
//! Main-window preference records; no generic credential or application-state accessor.
//! 主窗口偏好记录;没有通用凭证或应用程序状态访问器。
use super::{with_workspace, Host};
use notesagent_host::{
records,
+1 -1
View File
@@ -1,4 +1,4 @@
//! Versioned logical records: explicit fields only, never raw application databases/config.
//! 版本化逻辑记录:仅显式字段,从不原始应用程序数据库/配置。
use crate::workspace::{HostError, Result, Workspace};
use rusqlite::OptionalExtension;
use serde::{Deserialize, Serialize};
+2 -2
View File
@@ -1,4 +1,4 @@
//! Reserve before dispatch so cancellation cannot race a delayed IPC invocation.
//! 调度前保留,因此取消不能与延迟的 IPC 调用竞争。
use std::{
collections::HashMap,
future::Future,
@@ -71,7 +71,7 @@ impl Requests {
}
}
impl Lease {
/// Recheck after synchronous encoding/validation, immediately before network IO.
/// 同步编码/验证后、紧接网络 IO 之前重新检查。
pub fn checkpoint(&self) -> impl Fn() -> Result<(), String> + Send + 'static {
let cancel = self.cancel.clone();
let deadline = self.deadline;
+3 -7
View File
@@ -1,7 +1,4 @@
//! C23 compatibility for the MSVCRT-based Windows GNU development target.
//! Recent libsodium archives reference memset_explicit, absent in MSVCRT.
//! Volatile stores preserve its non-elidable wipe semantics; MSVC/UCRT release
//! builds use their native runtime and do not compile this compatibility symbol.
//! C23 与基于 MSVCRT Windows GNU 开发目标的兼容性。最近的 libsodium 档案参考 memset_explicitMSVCRT 中不存在。易失性存储保留其不可消除的擦除语义; MSVC/UCRT 发行版本使用其本机运行时,并且不编译此兼容性符号。
#[cfg(all(windows, target_env = "gnu"))]
#[no_mangle]
@@ -11,8 +8,7 @@ unsafe extern "C" fn memset_explicit(
count: usize,
) -> *mut std::ffi::c_void {
for offset in 0..count {
// SAFETY: the C ABI caller must supply a writable region of count bytes,
// exactly as for memset. Volatile stores cannot be removed as dead writes.
// SAFETYC ABI 调用者必须提供 count 字节的可写区域,与 memset 完全相同。易失性存储无法作为死写删除。
unsafe {
destination
.cast::<u8>()
@@ -30,7 +26,7 @@ mod tests {
fn explicit_memset_preserves_surrounding_bytes_and_return_pointer() {
let mut data = [0x55u8; 34];
let pointer = data[1..33].as_mut_ptr().cast();
// SAFETY: the subslice contains exactly 32 writable bytes.
// SAFETY: 子片恰好包含 32 个可写字节。
assert_eq!(unsafe { super::memset_explicit(pointer, 0, 32) }, pointer);
assert_eq!(data[0], 0x55);
assert_eq!(data[33], 0x55);
+2 -3
View File
@@ -1,5 +1,4 @@
//! Windows session notifications. Revocation is atomic and never waits for a KDF.
//! https://learn.microsoft.com/en-us/windows/win32/termserv/wm-wtssession-change
//! Windows 会话通知。撤销是原子性的,永远不会等待 KDF。 https://learn.microsoft.com/en-us/windows/win32/termserv/wm-wtssession-change
use std::cell::RefCell;
use std::sync::{
atomic::{AtomicU64, Ordering},
@@ -152,7 +151,7 @@ mod tests {
fn native_message_revokes_without_unlocking_on_session_return() {
let signal = Arc::new(AtomicU64::new(0));
let monitor = SessionMonitor::start(signal.clone()).unwrap();
// Inject only into our hidden test window; never lock the user's desktop.
// 仅注入到我们的隐藏测试窗口中;永远不要锁定用户的桌面。
unsafe {
SendMessageW(
monitor.window as HWND,
+5 -6
View File
@@ -1,4 +1,4 @@
//! Device-local Sync sessions. The serialized record never crosses IPC.
//! 设备本地同步会话。序列化记录从未跨越IPC
use crate::{
credentials::{CredentialBroker, CredentialId, Scope},
sync_client::{Session, SyncClient, SyncError},
@@ -64,7 +64,7 @@ pub fn available(credentials: &Credentials, endpoint: &str, account: &str) -> Re
.map(|v| v.is_some())
})
}
/// Dropping a guarded HTTP future closes the in-flight operation on any lock epoch change.
/// 删除受保护的 HTTP 未来会关闭任何锁定纪元更改的正在进行的操作。
pub async fn guarded<T>(
credentials: &Credentials,
future: impl Future<Output = Result<T>>,
@@ -123,7 +123,7 @@ pub async fn login(
})
.await
}
/// The caller serializes refreshes with the coordinator gate.
/// 调用者使用协调器门来串行刷新。
pub async fn client(
credentials: &Credentials,
endpoint: &str,
@@ -158,8 +158,7 @@ pub async fn client(
saved.allow_test_http,
)
}
/// The coordinator must serialize calls. Only use for read-only or durably idempotent work:
/// a 401 repeats the operation once with the same device after rotating its session.
/// 协调器必须序列化调用。仅用于只读或持久幂等工作:401 在轮换其会话后使用同一设备重复该操作一次。
pub async fn authenticated<T, F, Fut>(
credentials: &Credentials,
endpoint: &str,
@@ -181,7 +180,7 @@ where
}
pub async fn logout(credentials: &Credentials, endpoint: &str, account: &str) -> Result<()> {
let client = client(credentials, endpoint, account, false).await?;
// A failed server revocation is reported; the encrypted record remains available for retry.
// 报告服务器吊销失败;加密记录仍可供重试。
guarded(
credentials,
client.json(reqwest::Method::DELETE, "sync/v1/auth/sessions", None),
+2 -2
View File
@@ -1,4 +1,4 @@
//! Bounded Sync v1 transport. No redirects, no token-bearing URLs, no implicit retries.
//! 有界同步 v1 传输。没有重定向,没有带有令牌的 URL,没有隐式重试。
use crate::{
sync_state::{Binding, Job},
workspace::Workspace,
@@ -83,7 +83,7 @@ impl From<std::io::Error> for SyncError {
}
}
/// Persist only via the Stronghold Sync scope, never as an IPC response.
/// 仅通过 Stronghold Sync 范围持续,绝不作为 IPC 响应。
#[derive(Serialize, Deserialize)]
pub struct Session {
pub access_token: String,
+2 -2
View File
@@ -1,4 +1,4 @@
//! Main-window commands. Every ongoing run is bound to one Workspace and one account.
//! 主窗口命令。每次正在进行的运行都绑定到一个工作区和一个帐户。
use super::{with_workspace, Host};
use notesagent_host::{
sync_auth,
@@ -420,7 +420,7 @@ async fn cycle(host: &Host, binding: &Binding) -> Result<(), SyncError> {
break;
}
}
// Finish the fixed incoming window before freezing any new remote base.
// 在冻结任何新的远程基地之前完成固定的传入窗口。
if host
.workspace
.access(|ws| ws.sync_boundary(&binding.id))?
+3 -3
View File
@@ -1,9 +1,9 @@
//! Reconcile externally edited files against committed snapshots, never against UI read caches.
//! 根据已提交的快照协调外部编辑的文件,而不是根据 UI 读取缓存。
use crate::workspace::{HostError, Result, Workspace};
use rusqlite::{params, OptionalExtension};
use std::{collections::HashSet, fs, path::Path};
use uuid::Uuid;
/// File transport only. Logical records receive their own versioned whitelist separately.
/// 仅文件传输。逻辑记录单独接收自己的版本白名单。
pub fn allowed(path: &str) -> bool {
if crate::records::is_record(path) {
return crate::records::allowed(path);
@@ -115,7 +115,7 @@ impl Workspace {
{
continue;
}
// Confirm the snapshot without writing back over an external editor.
// 确认快照而不通过外部编辑器回写。
let operation = Uuid::new_v4().to_string();
self.store_payload_file(&operation, &target, &digest)?;
let file_id = previous.map_or_else(|| Uuid::new_v4().to_string(), |e| e.file_id);
+1 -1
View File
@@ -1,4 +1,4 @@
//! Persist received revisions before Workspace writes; cursor advancement follows application.
//! 在Workspace写入之前保留收到的修订;光标前进跟随应用程序。
use crate::{
sync_state::{Binding, Job},
workspace::{hash, HostError, Result, Workspace},
+1 -1
View File
@@ -1,4 +1,4 @@
//! Initial merge uses a confirmed fixed remote snapshot and never replays obsolete paths.
//! 初始合并使用已确认的固定远程快照,并且从不重播过时的路径。
use crate::{
sync_inbox::RemoteRevision,
sync_state::Binding,
+7 -11
View File
@@ -1,4 +1,4 @@
//! User decisions are durable before changing files; journal IDs make restart replay safe.
//! 用户决策在更改文件之前是持久的;日志 ID 使重新启动重放变得安全。
use crate::workspace::hash;
use crate::{
sync_inbox::RemoteRevision,
@@ -79,8 +79,7 @@ impl Workspace {
if !crate::sync_discovery::allowed(destination) {
return Err(HostError::new("SYNC_PATH_DENIED"));
}
// Validate the intended record path before freezing the decision.
// A typo must not leave an unchangeable, unappliable resolution.
// 在冻结决定之前验证预期的记录路径。拼写错误不得留下不可更改、不适用的解决方案。
if crate::records::is_record(destination) {
let spool = self.sync_spool(&current)?;
let size = fs::metadata(&spool)?.len();
@@ -161,8 +160,7 @@ impl Workspace {
if head != sequence {
return Err(HostError::new("SYNC_CONFLICT_CHANGED"));
}
// local_path is frozen when the conflict is recorded. Recomputing it from
// file identity after a partial resolution can select a different file.
// 记录冲突时local_path被冻结。部分解析后根据文件标识重新计算可以选择不同的文件。
let path = stored_path;
if !self
.operation(&operation)?
@@ -216,10 +214,8 @@ impl Workspace {
&& !source_hash.is_empty()
&& !source_renamed
{
// The deleted target still owns its unique database path until the
// final identity-aware write retires that tombstone. Retire the
// incoming identity at its old path, then resurrect it at the
// target with the frozen remote or chosen-local bytes.
// 被删除的目标仍占用其唯一数据库路径,直到最后一次感知身份的写入撤销该逻辑删除记录。
// 先在旧路径停用传入标识,再用冻结的远程内容或用户选择的本地内容在目标路径恢复它。
self.mutate_with_origin(
"delete",
&source_path,
@@ -640,7 +636,7 @@ mod tests {
],
)
.unwrap();
// Simulate a crash after the filesystem journal commits but before the resolution transaction.
// 在文件系统日志提交之后但在解析事务之前模拟崩溃。
if choice == "copy" {
ws.write_operation("copy.md", "", b"local", "local", &copy)
.unwrap();
@@ -732,7 +728,7 @@ mod tests {
remote.operation_id = Uuid::new_v4().to_string();
receive(&mut ws, &binding.id, &remote);
assert_eq!(ws.read("a.md").unwrap().entry.file_id, remote.file_id);
// A tombstoned identity can reappear at another free path.
// 已标记为删除的标识可以在另一个空闲路径重新出现。
remote.sequence = 4;
remote.base_revision = 2;
remote.file_id = original.clone();
+2 -2
View File
@@ -1,4 +1,4 @@
//! Binding-scoped retry decisions survive restart; wall time is bounded after clock changes.
//! 绑定范围内的重试决定在重启后仍然有效;系统时钟变化后也会约束实际经过时间。
use crate::workspace::{Result, Workspace};
use rusqlite::{params, OptionalExtension};
use serde::Serialize;
@@ -105,7 +105,7 @@ impl Workspace {
if code == "SYNC_CANCELLED" {
return Ok(());
}
// Only retain a bounded machine code, never an arbitrary remote response string.
// 仅保留有界机器代码,从不保留任意远程响应字符串。
let code = machine_code(code);
let failures = if code == "CREDENTIALS_LOCKED" {
0
+2 -2
View File
@@ -1,4 +1,4 @@
//! Device-local choices for optional logical data; never exported as sync records.
//! 可选逻辑数据的设备本地选择;从未导出为同步记录。
use crate::workspace::{HostError, Result, Workspace};
use rusqlite::OptionalExtension;
use serde::{Deserialize, Serialize};
@@ -91,7 +91,7 @@ mod tests {
.sync_bind_empty("https://sync.example", "remote", "account")
.unwrap();
let job = ws.sync_next(&binding.id).unwrap().unwrap();
// Models a pre-scope database's pending job after schema migration.
// 对模式迁移后范围内数据库的待处理作业进行建模。
ws.db
.execute("DELETE FROM sync_optional_scope", [])
.unwrap();
+4 -4
View File
@@ -1,4 +1,4 @@
//! Durable queue state. Network code never invents a remote base from a local revision.
//! 持久队列状态。网络代码永远不会从本地版本创建远程基础。
use crate::workspace::{HostError, Result, Workspace};
use rusqlite::{params, OptionalExtension};
use serde::{Deserialize, Serialize};
@@ -56,7 +56,7 @@ impl Workspace {
}
Ok(())
}
/// Caller verifies an empty remote and obtains a reconciliation confirmation first.
/// 调用者验证空远程并首先获得协调确认。
pub fn sync_bind_empty(
&mut self,
endpoint: &str,
@@ -73,7 +73,7 @@ impl Workspace {
})?;
let paths = self.sync_paths()?;
let id = Uuid::new_v4().to_string();
// Rebinding explicitly starts from the current snapshot, never an old account's queue.
// 重新绑定显式从当前快照开始,而不是旧帐户的队列。
if had_binding {
self.db.execute(
"UPDATE outbox SET state='archived' WHERE state IN ('pending','queued')",
@@ -246,7 +246,7 @@ impl Workspace {
}
pub fn sync_commit_payload(&self, job: &Job) -> Result<serde_json::Value> {
self.check_job(job)?;
// The base is frozen exactly once. A response loss reuses the byte-equivalent payload.
// 基准快照只冻结一次;响应丢失后复用字节完全相同的负载。
self.db.execute("UPDATE sync_jobs SET state='committing',base_revision=COALESCE((SELECT revision FROM sync_heads WHERE binding=?1 AND file_id=?3),0) WHERE binding=?1 AND operation_id=?2 AND base_revision IS NULL",
params![job.binding,job.operation_id,job.file_id])?;
let base: i64 = self.db.query_row(
+2 -5
View File
@@ -152,7 +152,7 @@ impl Workspace {
return Err(HostError::new("SCHEMA_INCOMPATIBLE"));
}
if (1..13).contains(&version) {
// Independent, complete SQLite backup before the schema ownership change.
// 模式所有权更改之前独立、完整的 SQLite 备份。
let backup = managed.join(format!("host-schema{version}-{}.sqlite3", Uuid::new_v4()));
db.execute("VACUUM INTO ?1", [backup.to_string_lossy().as_ref()])?;
}
@@ -476,10 +476,7 @@ impl Workspace {
)
}
/// Revalidate the caller before work and immediately before committing the
/// durable write intent. Once accepted, recovery must finish that intent.
/// The caller must serialize the authorization boundary if strict atomic
/// ordering with concurrent revocation is required.
/// 在工作之前和提交持久写入意图之前重新验证调用者。一旦接受,恢复必须完成该意图。如果需要并发撤销的严格原子排序,调用者必须序列化授权边界。
#[cfg(any(test, all(windows, feature = "desktop")))]
pub(crate) fn write_operation_guarded(
&mut self,
+1 -1
View File
@@ -1,4 +1,4 @@
//! Narrow Core RPC. Every request is bound to the Vault captured by the Host transport.
//! 受限的 Core RPC;每个请求都绑定到 Host 传输捕获的 Vault。
use crate::workspace::Workspace;
use serde::Deserialize;
use serde_json::{json, Value};
+1 -1
View File
@@ -1,4 +1,4 @@
//! Executes the real worktree Core, with no personal data or external Provider.
//! 执行真实的工作树Core,没有个人数据或外部提供者。
use notesagent_host::core::CoreSupervisor;
use notesagent_host::credentials::{CredentialBroker, CredentialId, Scope};
use std::path::Path;
+3 -5
View File
@@ -1,5 +1,5 @@
#![cfg(feature = "desktop")]
//! Real Python Core + Host pipes + isolated Workspace; no personal data or Provider.
//! 真正的Python Core + Host管道+隔离工作区;没有个人数据或提供商。
use notesagent_host::{core::CoreSupervisor, workspace::Workspace, workspace_broker};
use serde_json::{json, Value};
use std::{
@@ -289,8 +289,7 @@ async fn real_core_notes_roundtrip_only_through_bound_host_and_confirm_commits()
.path()
.join("core/unbound-vault/Core fixture.md")
.exists());
// The actual Python HTTP handler must round-trip revision through Host pipes,
// including repeat requests after a successful commit.
// 实际的 Python HTTP 处理程序必须通过 Host 管道进行往返修订,包括成功提交后的重复请求。
let path = "/api/settings/persona";
let (status, empty) = request(
&mut core,
@@ -369,8 +368,7 @@ async fn real_core_notes_roundtrip_only_through_bound_host_and_confirm_commits()
.unwrap();
assert_eq!(stored["hash"], first["revision"]);
// User-created Skills are Vault records, use Host CAS/idempotency, and are
// resolved by the actual Agent route without copying package paths or grants.
// 用户创建的Skills是Vault记录,使用Host CAS/幂等性,并通过实际的Agent路由解析,无需复制包路径或授权。
let create_skill_operation = uuid::Uuid::new_v4();
let skill_body = json!({
"revision":"", "name":"Vault reviewer", "description":"portable",
@@ -25,7 +25,7 @@ fn stale_writer_is_rejected_then_handoff_preserves_both_commits() {
assert!(two.resolve(&Scope::Provider, &a).unwrap().is_some());
two.put(&b, Zeroizing::new(b"fixture-b".to_vec())).unwrap();
two.lock();
// A failed password attempt must release its ownership too.
// 失败的密码尝试也必须释放其所有权。
assert!(one
.unlock(Zeroizing::new(b"wrong-fixture-password".to_vec()))
.is_err());
+3 -4
View File
@@ -1,4 +1,4 @@
//! Standalone native test probe; never shipped or used to launch extensions.
//! 独立本机测试探针;从未发货或用于启动扩展。
use std::net::{SocketAddr, TcpStream, UdpSocket};
use std::time::Duration;
fn main() {
@@ -84,7 +84,7 @@ fn main() {
if !stale.is_empty() { reply(id(&stale), r#"{"content":[],"structuredContent":{"ok":true}}"#); }
return;
}
// MCP server remains alive between calls until Host closes stdin.
// MCP 服务器在调用之间保持活动状态,直到 Host 关闭标准输入。
while !read(&mut input).is_empty() {}
return;
}
@@ -107,8 +107,7 @@ fn main() {
}
let sentinel: usize = args[2].parse().unwrap();
let mut identity = FileIdentity { volume: 0, id: [0; 16] };
// Numeric handles may alias unrelated child objects. Compare the actual
// file identity without reading from a possibly aliased pipe handle.
// 数字句柄可能会为不相关的子对象起别名。比较实际文件标识,而不读取可能有别名的管道句柄。
if unsafe { GetFileInformationByHandleEx(sentinel as *mut _, 18,
(&mut identity as *mut FileIdentity).cast(),
std::mem::size_of::<FileIdentity>() as u32) } != 0 {
+14 -16
View File
@@ -108,8 +108,7 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
.sync_next(&binding.id)
.unwrap()
.unwrap();
// Commit the first revision, then kill the client before the response can
// acknowledge the local journal. Reopen must keep all pending operations.
// 提交第一个修订,然后在响应确认本地日志之前终止客户端。重新打开必须保留所有挂起的操作。
std::fs::write(
root.path().join("interrupt-revision"),
b"controlled-fixture",
@@ -255,7 +254,7 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
.file_id,
first.file_id
);
// Receiving one's historical commits never rolls back newer local edits.
// 接收历史提交永远不会回滚较新的本地编辑。
{
let mut ws = workspace.lock().unwrap();
let current = ws.read("note.md").unwrap();
@@ -310,7 +309,7 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
.cursor,
21
);
// All three explicit choices converge; the local copy gets an independent file ID.
// 所有三个显式选择都收敛;本地副本获得独立文件 ID
for (iteration, choice) in ["local", "remote", "copy"].into_iter().enumerate() {
if iteration > 0 {
for (ws, content) in [(&workspace, "next-a"), (&workspace_b, "next-b")] {
@@ -351,7 +350,7 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
)
.unwrap();
assert!(ws.sync_conflicts(&binding_b.id).unwrap().is_empty());
// Repeating a persisted decision is harmless.
// 重复提交同一个已持久化决定不会产生副作用。
ws.sync_resolve(
&binding_b.id,
sequence,
@@ -447,7 +446,7 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
.file_id
);
// Initial merge reviews a fixed snapshot and preserves conflicting local content.
// 初始合并会检查固定快照并保留冲突的本地内容。
let merge_root = tempfile::tempdir().unwrap();
let merge_ws = Arc::new(Mutex::new(Workspace::open(merge_root.path()).unwrap()));
let snapshot = client.snapshot(remote).await.unwrap();
@@ -625,8 +624,8 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
.unwrap()["record"]["data"]["fontEditorSize"],
24
);
// Portable records traverse real HTTP, including equal display versions with
// different contents. A numeric persona version cannot replace content CAS.
// 可移植记录通过真实 HTTP 传输,包括显示版本相同但内容不同的情况;
// 数值型角色版本不能取代内容 CAS
for (kind, id, data, field) in [
(
"persona",
@@ -760,8 +759,8 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
);
}
// A default-off device consumes history metadata without downloading either
// optional record. Rebinding after opt-in must fetch the already-seen heads.
// 默认关闭该功能的设备只读取历史元数据,不下载任何可选记录;启用后重新绑定时,
// 必须获取先前已经见过的最新版本。
let excluded_root = tempfile::tempdir().unwrap();
let excluded_ws = Arc::new(Mutex::new(Workspace::open(excluded_root.path()).unwrap()));
let excluded_binding = excluded_ws
@@ -853,8 +852,7 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
.await
.unwrap());
// Kill the actual client process after each durable 10 MiB server offset,
// before its response reaches the client. The next process must query offset.
// 在每个持久的 10 MiB 服务器偏移之后,在其响应到达客户端之前,终止实际的客户端进程。下一个进程必须查询偏移量。
use sha2::{Digest, Sha256};
let large_remote = client
.json(
@@ -981,7 +979,7 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
.unwrap(),
0
);
// SQLite stores metadata, never the 100 MiB body.
// SQLite 存储元数据,而不是 100 MiB 主体。
for directory in [large_root.path(), download_root.path()] {
let managed = directory.join(".ainote");
for item in std::fs::read_dir(managed).unwrap().flatten() {
@@ -993,7 +991,7 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
}
}
}
// Host sessions survive encrypted storage reopen and refresh on the actual service.
// Host 会话可以在实际服务上重新打开加密存储并刷新。
use notesagent_host::{credentials::CredentialBroker, sync_auth};
let credential_root = tempfile::tempdir().unwrap();
let credential_path = credential_root.path().join("credentials.onxcred");
@@ -1032,7 +1030,7 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
.unwrap()
.unlock(Zeroizing::new(b"fixture-stronghold-password".to_vec()))
.unwrap();
// Expire the access token early in this isolated fixture; the refresh token stays valid.
// 在此隔离装置中尽早使访问令牌过期;刷新令牌保持有效。
let database = rusqlite::Connection::open(root.path().join("sync.sqlite3")).unwrap();
database
.execute("UPDATE sessions SET expires=0", [])
@@ -1054,7 +1052,7 @@ async fn s01_actual_service_preserves_offline_chains_and_response_loss_idempoten
.unwrap()
.iter()
.any(|v| v["id"] == remote));
// Even a repeated 401 must stop after one rotation, rather than refresh indefinitely.
// 即使再次收到 401,也只能轮换一次令牌,不能无限刷新。
attempts.store(0, std::sync::atomic::Ordering::SeqCst);
let denied = sync_auth::authenticated(&credentials, &canonical, "rust-fixture", |_client| {
attempts.fetch_add(1, std::sync::atomic::Ordering::SeqCst);