#![cfg(test)] // 裁决方授权:认证文件允许豁免纯风格类 clippy lint(不含任何安全/正确性 // lint)。type_complexity 属阈值型风格检查,对测试专用契约文件无意义。 #![allow(clippy::type_complexity)] //! 前置工程 B:Canonical Restore 认证测试套件 v2(测试先行) //! //! 规则与前置 A 完全一致:实现方不得 //! //! ## 与前置 A 的衔接(解冻声明) //! 前置 A 套件以 SHA-256 冻结了 schema.rs/migration.rs/backup.rs。本前置 //! 工程的实现必然修改它们,因此:**pre-B 实现期间(已于 2026-08-02 //! 终止关账,该窗口已闭合),pre-A 的 //! `certify_infra_files_stay_frozen_after_restore_closeout` 预期为红**,这是裁决内 //! 的解冻,不是违规;pre-B 认证通过后由裁决方以终态哈希重冻基线。除此之外 //! pre-A 的其余 34 项必须全程保持绿——restore 改动破坏写面契约同样是失败。 //! //! ## 范围界定 //! 既有 backup.rs 测试模块已实现修正案 2 E2 的大部分义务(恶意 schema 双 //! 入口、user_version 哨兵矩阵、VM/页预算、符号链接/FIFO/大小边界、NULL 与 //! 显式 ID 保真、canonical 预发布契约)。本套件不重复它们,而是: //! 1. 绑定冻结这些既有测试(删除/改名/空壳化即红); //! 2. 固化 R4 盲审确认、至今未修的缺口为可执行红灯; //! 3. 以结构断言钉死类型屏障与搬运禁令。 //! //! ## 义务清单(实现方交付,非本文件可执行部分) //! O1【safety 窗口】`restore_from_backup` 必须自 safety backup 创建前起持有 //! live 写边界直至 publish 完成——期间任何并发写都不得既错过 safety //! backup 又被 publish 覆盖。实现后补 seam 级确定性测试并上报并入。 //! **盲审重点核查项**:逐行核查 restore_from_backup 的锁范围。 //! O2【binary TOCTOU】文件解析链(backups 目录文件名解析 → 预检 //! symlink_metadata → O_NOFOLLOW 打开 → 打开后 fstat 身份复核)必须闭合, //! 路径任何组件在检查后不得再经 symlink 重解析。**盲审重点核查项**。 //! O3 pre-B 认证通过后,裁决方重冻 infra 基线并将本套件红灯清单归零存档。 //! //! ## 首轮认证失败后的契约裁决(v2,2026-08-01) //! 首轮组件盲审(止于 89961dff)确认两 High 两 Medium,均属契约真空, //! 现裁决如下: //! 裁决5【写入所有权边界,High】进程内对 live 数据库文件的**可写连接必须 //! 唯一**(AppState Database 主连接);对 live 路径的任何其他打开必须 //! 携带 SQLITE_OPEN_READ_ONLY(database/mod.rs 的版本预检连接是现存违 //! 例)。restore 在 Database mutex 内自 safety backup 前持锁至 publish //! 完成——唯一可写连接使该 mutex 成为真实的全局写边界,堵死"第二连接 //! 在 safety backup 后提交、被 publish 覆盖"的窗口。结构红灯 R5; //! **盲审重点**:全树审计 Connection::open* 调用点与其目标路径。 //! 裁决6【迁移语义分离,High】迁移链必须携带 //! `MigrationRunContext { LocalUpgrade, UntrustedRestore }`;一切修复/ //! 合并/去重步骤(含 v16→v17 的 endpoint 去重)在 UntrustedRestore 下 //! 必须整体 abort 并给结构化错误,LocalUpgrade 保留既有容错。结构红灯 //! R6(调用点级绑定:enum 形状/迁移入口签名/不可信入口传 //! UntrustedRestore/本地入口传 LocalUpgrade);行为义务 O4:用既有 //! user_version 哨兵机器构造 v16 重复 endpoint 输入,断言 import 整体 //! Err、零合并——**O4 交付并经裁决并入是首轮重认证的前置条件**, //! 不得以结构绿替代语义覆盖。语义注记:该枚举表达"修复策略", //! fresh/canonical bootstrap 映射 LocalUpgrade。 //! 裁决7【REAL 值域,Medium】浮点域列必须声明有限区间; //! `circuit_error_rate_threshold` ∈ [0,1] 且有限。行为红灯 R7。 //! O2 扩展【Windows TOCTOU,Medium】Windows 二进制路径必须基于已验证目录 //! 句柄相对打开(参考 NtCreateFile RootDirectory 语义)或在打开后按句柄 //! 身份复核,且补文件级预检;**盲审重点**,Linux 宿主无法执行,逐行核查。 //! 裁决5 边界补记:重复 Database::init()、其他连接 ATTACH live 库均属 //! 进程内旁路,纳入全树审计(盲审);**进程外写者**(第二进程、外部 //! SQLite 客户端、直接替换文件)不受进程内边界约束,single-instance //! 插件不是数据库锁——显式列为残余风险,由发布说明与盲审知悉。 //! 重认证预算:修复后 2 轮 fresh 双审;写入所有权或迁移语义同 invariant //! 复发 → 停止并上报用户。 //! //! ## 残余风险与收口 //! 沿用前置 A 的收口边界与残余清单(动态 SQL、trigger/view、Backup API、 //! `#[path]` 等);本套件新增接受项:导出路径(`dump_sql`)的 `SELECT *` //! 属导出语义,不在搬运禁令内;转移函数命名若脱离 //! restore/stage/scratch/publish/transfer/canonical 词根,搬运禁令扫描不到, //! 由盲审兜底。清单外新绕过按盲审 finding 处理,不再扩充扫描器。 use crate::database::Database; use std::collections::BTreeSet; use std::fs; use std::path::{Path, PathBuf}; use syn::visit::{self, Visit}; use syn::{Attribute, ImplItem, Item, Lit, Meta}; // --------------------------------------------------------------------------- // 基建(与前置 A 同构;各套件自持,保持契约文件独立) // --------------------------------------------------------------------------- fn source_root() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("src") } fn backup_source() -> String { fs::read_to_string(source_root().join("database/backup.rs")).expect("read backup.rs") } fn schema_source() -> String { fs::read_to_string(source_root().join("database/schema.rs")).expect("read schema.rs") } fn cfg_expr_requires_test(expr: &str) -> bool { let expr = expr.trim(); if expr == "test" { return true; } let strip = |name: &str| -> Option<&str> { expr.strip_prefix(name) .and_then(|rest| rest.trim_start().strip_prefix('(')) .and_then(|rest| rest.strip_suffix(')')) }; if let Some(args) = strip("all") { return args.split(',').any(cfg_expr_requires_test); } if let Some(args) = strip("any") { let parts: Vec<&str> = args.split(',').collect(); return !parts.is_empty() && parts.iter().all(|part| cfg_expr_requires_test(part)); } false } fn attrs_mark_test_only(attrs: &[Attribute]) -> bool { attrs.iter().any(|attribute| { attribute.path().is_ident("cfg") && matches!( &attribute.meta, Meta::List(list) if cfg_expr_requires_test(&list.tokens.to_string()) ) }) } /// 按函数收集生产字符串字面量(cfg-aware),供搬运禁令做函数级作用域判定。 #[derive(Default)] struct FnLiteralCollector { current: Vec, per_fn: Vec<(String, Vec)>, } impl FnLiteralCollector { fn enter_fn(&mut self, name: String, block: &syn::Block) { let saved = std::mem::take(&mut self.current); self.visit_block(block); let literals = std::mem::replace(&mut self.current, saved); self.per_fn.push((name, literals)); } } impl<'ast> Visit<'ast> for FnLiteralCollector { fn visit_item(&mut self, item: &'ast Item) { match item { Item::Fn(function) if !attrs_mark_test_only(&function.attrs) => { self.enter_fn(function.sig.ident.to_string(), &function.block); } Item::Impl(item_impl) if !attrs_mark_test_only(&item_impl.attrs) => { for impl_item in &item_impl.items { let ImplItem::Fn(function) = impl_item else { continue; }; if !attrs_mark_test_only(&function.attrs) { self.enter_fn(function.sig.ident.to_string(), &function.block); } } } Item::Mod(item_mod) if !attrs_mark_test_only(&item_mod.attrs) => { if let Some((_, nested)) = &item_mod.content { for nested_item in nested { self.visit_item(nested_item); } } } _ => {} } } fn visit_expr_lit(&mut self, expression: &'ast syn::ExprLit) { if let Lit::Str(literal) = &expression.lit { self.current.push(literal.value()); } visit::visit_expr_lit(self, expression); } } fn find_fn<'a>(items: &'a [Item], name: &str) -> Option<&'a syn::ItemFn> { for item in items { match item { Item::Fn(function) if function.sig.ident == name => return Some(function), Item::Mod(item_mod) => { let nested = item_mod.content.as_ref().map(|(_, items)| items.as_slice()); if let Some(found) = nested.and_then(|items| find_fn(items, name)) { return Some(found); } } _ => {} } } None } fn find_impl_fn_signature(source: &syn::File, name: &str) -> Option { for item in &source.items { let Item::Impl(item_impl) = item else { continue; }; for impl_item in &item_impl.items { let ImplItem::Fn(function) = impl_item else { continue; }; if function.sig.ident == name { let mut signature = String::new(); for input in &function.sig.inputs { if let syn::FnArg::Typed(typed) = input { let mut probe = IdentProbe::default(); probe.visit_type(&typed.ty); signature.push_str(&probe.found.into_iter().collect::>().join(",")); signature.push(';'); } } return Some(signature); } } } None } #[derive(Default)] struct IdentProbe { found: BTreeSet, } impl<'ast> Visit<'ast> for IdentProbe { fn visit_ident(&mut self, identifier: &'ast syn::Ident) { self.found.insert(identifier.to_string()); } // syn 的默认遍历不将方法名作为 ident 访问;绑定探针必须能看见 // `database.restore_from_backup(...)` 这类方法调用(裁决修正)。 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) { self.found.insert(node.method.to_string()); visit::visit_expr_method_call(self, node); } // syn 同样不遍历宏 token,而测试体大量使用 assert!(...) 包裹调用 // (backup.rs:2828 的 restore_from_backup 即在其中)。与前置 A 扫描器 // 的宏提取同源:把宏 token 按标识符切词纳入探针(裁决修正,第三层)。 fn visit_macro(&mut self, mac: &'ast syn::Macro) { for word in mac .tokens .to_string() .split(|c: char| !c.is_ascii_alphanumeric() && c != '_') { if !word.is_empty() { self.found.insert(word.to_string()); } } visit::visit_macro(self, mac); } } struct TestHomeGuard(Option); impl TestHomeGuard { fn set(path: &Path) -> Self { let previous = std::env::var_os("CC_SWITCH_TEST_HOME"); std::env::set_var("CC_SWITCH_TEST_HOME", path); Self(previous) } } impl Drop for TestHomeGuard { fn drop(&mut self) { match self.0.take() { Some(previous) => std::env::set_var("CC_SWITCH_TEST_HOME", previous), None => std::env::remove_var("CC_SWITCH_TEST_HOME"), } } } // --------------------------------------------------------------------------- // S1:类型屏障——publish 只接受 CanonicalStage,工厂外不可构造 // --------------------------------------------------------------------------- #[test] fn certify_publish_consumes_only_canonical_stage() { let syntax = syn::parse_file(&backup_source()).expect("parse backup.rs"); let signature = find_impl_fn_signature(&syntax, "publish_canonical_stage") .expect("publish_canonical_stage exists"); assert!( signature.contains("CanonicalStage"), "publish must consume CanonicalStage, got param types: {signature}" ); assert!( !signature.contains("UntrustedScratch") && !signature.contains("Connection"), "publish must not accept raw connections or untrusted scratch: {signature}" ); // CanonicalStage 字段必须全私有:只有 schema.rs 的 current-schema 工厂 // 能构造,输入派生的 schema 在类型上无路可发布。 let schema = syn::parse_file(&schema_source()).expect("parse schema.rs"); fn find_struct<'a>(items: &'a [Item], name: &str) -> Option<&'a syn::ItemStruct> { for item in items { match item { Item::Struct(item_struct) if item_struct.ident == name => return Some(item_struct), Item::Mod(item_mod) => { let nested = item_mod.content.as_ref().map(|(_, items)| items.as_slice()); if let Some(found) = nested.and_then(|items| find_struct(items, name)) { return Some(found); } } _ => {} } } None } let stage = find_struct(&schema.items, "CanonicalStage").expect("CanonicalStage in schema.rs"); for field in &stage.fields { assert!( matches!(field.vis, syn::Visibility::Inherited), "CanonicalStage fields must stay private to the schema factory" ); } // 禁止旁路转换:backup.rs/schema.rs 不得存在 UntrustedScratch → // CanonicalStage 的 From/Into 实现。 for source in [backup_source(), schema_source()] { let parsed = syn::parse_file(&source).expect("parse restore sources"); for item in &parsed.items { let Item::Impl(item_impl) = item else { continue; }; let Some((_, trait_path, _)) = &item_impl.trait_ else { continue; }; let is_conversion = trait_path .segments .last() .is_some_and(|segment| segment.ident == "From" || segment.ident == "Into"); if !is_conversion { continue; } let mut probe = IdentProbe::default(); probe.visit_item_impl(item_impl); assert!( !(probe.found.contains("UntrustedScratch") && probe.found.contains("CanonicalStage")), "no conversion between UntrustedScratch and CanonicalStage may exist" ); } } } // --------------------------------------------------------------------------- // S2:搬运禁令——restore 侧函数不得使用通配/冲突改写动词 // --------------------------------------------------------------------------- #[test] fn certify_restore_transfer_forbids_wildcard_and_conflict_verbs() { let syntax = syn::parse_file(&backup_source()).expect("parse backup.rs"); let mut collector = FnLiteralCollector::default(); for item in &syntax.items { collector.visit_item(item); } let restore_scoped = |name: &str| { [ "restore", "stage", "scratch", "publish", "transfer", "canonical", "copy_", ] .iter() .any(|root| name.contains(root)) }; // 核心搬运函数必须在扫描视野内(改名脱离词根即红,防止禁令被绕空)。 assert!( collector .per_fn .iter() .any(|(name, _)| name == "copy_fixed_table"), "the fixed-column transfer function 'copy_fixed_table' must exist and be scanned" ); let mut violations = Vec::new(); for (name, literals) in &collector.per_fn { if !restore_scoped(name) { continue; } for literal in literals { let upper = literal.to_ascii_uppercase(); for banned in [ "SELECT *", "INSERT OR IGNORE", "INSERT OR REPLACE", "ON CONFLICT DO UPDATE", "REPLACE INTO", ] { if upper.contains(banned) { violations.push(format!("{name}: {banned}")); } } } } assert!( violations.is_empty(), "restore-side transfer must use fixed column lists and plain INSERT:\n{}", violations.join("\n") ); } #[test] fn certify_no_wal_sidecar_manipulation() { let syntax = syn::parse_file(&backup_source()).expect("parse backup.rs"); let mut collector = FnLiteralCollector::default(); for item in &syntax.items { collector.visit_item(item); } for (name, literals) in &collector.per_fn { for literal in literals { assert!( !literal.contains("-wal") && !literal.contains("-shm"), "production fn '{name}' must not touch WAL/SHM sidecar files; \ all publication goes through the SQLite Backup API" ); } } } #[test] fn certify_untrusted_scratch_hardening_idents_present() { // 存在性绑定:NOFOLLOW 打开与打开后身份复核的关键符号不得被移除。 let syntax = syn::parse_file(&backup_source()).expect("parse backup.rs"); let mut probe = IdentProbe::default(); for item in &syntax.items { if let Item::Fn(function) = item { probe.visit_item_fn(function); } if let Item::Impl(item_impl) = item { probe.visit_item_impl(item_impl); } if let Item::Struct(item_struct) = item { probe.visit_item_struct(item_struct); } } for required in ["O_NOFOLLOW", "SQLITE_OPEN_NOFOLLOW", "symlink_metadata"] { assert!( probe.found.contains(required), "scratch hardening symbol '{required}' disappeared from backup.rs" ); } } // --------------------------------------------------------------------------- // S3:绑定冻结既有认证级测试(删除/改名/空壳化即红) // --------------------------------------------------------------------------- #[test] fn certify_bound_restore_tests_present() { let syntax = syn::parse_file(&backup_source()).expect("parse backup.rs"); for (required, must_reference) in [ ( "restore_policy_snapshot_is_exhaustive_and_detects_missing_table_fixture", "RESTORE_TABLE_SPECS", ), ( "sql_and_binary_restore_share_canonical_prepublication_contracts", "assert_weak_ledgers_are_rebuilt", ), ( "public_restore_entries_discard_hostile_schema_and_publish_only_canonical_objects", "run_restore_entry", ), ( "public_restore_entries_abort_invalid_rows_without_live_or_ledger_mutation", "run_restore_entry", ), ( "public_restore_entries_preserve_nulls_unknown_json_and_explicit_ids", "run_restore_entry", ), ( "every_supported_user_version_has_a_public_migration_sentinel", "SCHEMA_VERSION", ), ( "import_rejects_cross_file_statements_and_leaves_no_file_behind", "import_sql_string", ), ( "public_sql_restore_discards_input_trigger_before_local_copy", "import_sql_string", ), ( "public_file_restore_entries_reject_symlink_directory_and_fifo", "restore_from_backup", ), ( "restore_file_size_limits_accept_n_and_publicly_reject_n_plus_one", "MAX_BINARY_RESTORE_BYTES", ), ( "public_restore_entries_enforce_vm_and_page_budgets", "RestoreLimitGuard", ), ("import_still_accepts_a_genuine_export", "export_sql_string"), ( "publish_copies_device_local_ledgers_at_the_commit_boundary", "UntrustedScratch", ), ] { let function = find_fn(&syntax.items, required) .unwrap_or_else(|| panic!("bound restore test '{required}' is missing")); assert!( function .attrs .iter() .any(|attribute| attribute.path().is_ident("test")), "'{required}' must be a #[test] function" ); let mut probe = IdentProbe::default(); probe.visit_block(&function.block); assert!( probe.found.contains(must_reference), "'{required}' must exercise '{must_reference}' (empty stubs cannot pass)" ); } } // --------------------------------------------------------------------------- // R1:导入值域——负 sort_index 不得发布 // --------------------------------------------------------------------------- #[test] fn certify_imported_sort_index_domain_is_enforced() { use crate::database::NewProviderAggregate; use crate::provider::ProviderMutationInput; use serde_json::json; let database = Database::memory().expect("memory db"); let input = ProviderMutationInput { id: "domain-probe".to_string(), name: "值域探针".to_string(), settings_config: json!({"env": {}}), website_url: None, category: None, created_at: Some(1_700_000_000), sort_index: Some(7777), notes: None, meta: None, icon: None, icon_color: None, in_failover_queue: false, }; database .create_provider(NewProviderAggregate::from_input("claude", input).expect("build")) .expect("create"); let exported = database.export_sql_string().expect("export"); assert!( exported.contains("7777"), "export must contain the sort_index sentinel" ); let hostile = exported.replace("7777", "-1"); let target = Database::memory().expect("target db"); let err = target .import_sql_string(&hostile) .expect_err("negative sort_index must abort the import: production reads Option"); let message = err.to_string(); assert!( !message.is_empty(), "domain violation must surface a real error" ); // 值域违规必须整体中止:目标库不得出现该 provider 的任何残留。 let conn = target.conn.lock().expect("lock target"); let count: i64 = conn .query_row( "SELECT COUNT(*) FROM providers WHERE id = 'domain-probe'", [], |row| row.get(0), ) .expect("count probe"); assert_eq!( count, 0, "aborted import must leave no partial provider row" ); } // --------------------------------------------------------------------------- // R2:publish 后 live 文件必须保持 INCREMENTAL auto-vacuum // --------------------------------------------------------------------------- #[test] #[serial_test::serial] fn certify_incremental_auto_vacuum_survives_restore() { let home = tempfile::tempdir().expect("temp home"); let _guard = TestHomeGuard::set(home.path()); let database = Database::init().expect("file-backed db"); // 空库导出会被 import 的基本状态校验拒绝("未包含有效的供应商或 MCP // 数据"),导致本测试在错误的位置变红;必须先播种最小数据,使红灯 // 精确落在 auto_vacuum 断言上、并在实现修复后能够转绿(裁决修正)。 { use crate::database::NewProviderAggregate; use crate::provider::ProviderMutationInput; use serde_json::json; let input = ProviderMutationInput { id: "vacuum-probe".to_string(), name: "auto-vacuum 探针".to_string(), settings_config: json!({"env": {}}), website_url: None, category: None, created_at: Some(1_700_000_000), sort_index: None, notes: None, meta: None, icon: None, icon_color: None, in_failover_queue: false, }; database .create_provider(NewProviderAggregate::from_input("claude", input).expect("build")) .expect("seed provider"); } let auto_vacuum_of = |db: &Database| -> i64 { let conn = db.conn.lock().expect("lock"); conn.query_row("PRAGMA auto_vacuum", [], |row| row.get(0)) .expect("read auto_vacuum") }; assert_eq!( auto_vacuum_of(&database), 2, "baseline: production file DB is created with INCREMENTAL auto-vacuum" ); let exported = database.export_sql_string().expect("export"); database .import_sql_string(&exported) .expect("roundtrip import of a genuine export"); assert_eq!( auto_vacuum_of(&database), 2, "publish must not silently downgrade the live file to auto_vacuum=NONE \ (canonical stage must inherit INCREMENTAL before its first page is written)" ); } // --------------------------------------------------------------------------- // R5:live 库的次级连接必须只读——调用级检查 // --------------------------------------------------------------------------- /// 每个函数收集连接打开调用:(调用名, 该调用实参中的 ident 集)。 #[derive(Default)] struct OpenCallCollector { calls: Vec<(String, BTreeSet)>, } impl<'ast> Visit<'ast> for OpenCallCollector { fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) { if let syn::Expr::Path(path) = node.func.as_ref() { if let Some(segment) = path.path.segments.last() { let name = segment.ident.to_string(); if matches!(name.as_str(), "open" | "open_with_flags" | "open_in_memory") { let mut args = IdentProbe::default(); for arg in &node.args { args.visit_expr(arg); } self.calls.push((name, args.found)); } } } visit::visit_expr_call(self, node); } fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) { let name = node.method.to_string(); if matches!(name.as_str(), "open" | "open_with_flags" | "open_in_memory") { let mut args = IdentProbe::default(); for arg in &node.args { args.visit_expr(arg); } self.calls.push((name, args.found)); } visit::visit_expr_method_call(self, node); } } /// 裁决5 的调用级规则。返回违规描述;空即合规。 fn live_open_violations(fn_name: &str, calls: &[(String, BTreeSet)]) -> Vec { let mut violations = Vec::new(); let mut primary_opens = 0usize; for (call, args) in calls { match call.as_str() { "open_in_memory" => {} "open" => { if fn_name == "init" { primary_opens += 1; } else { violations.push(format!( "{fn_name}: bare Connection::open is read-write; secondary \ connections must use open_with_flags + SQLITE_OPEN_READ_ONLY" )); } } "open_with_flags" => { if fn_name == "init" { primary_opens += 1; } else if !args.contains("SQLITE_OPEN_READ_ONLY") { violations.push(format!( "{fn_name}: open_with_flags without SQLITE_OPEN_READ_ONLY" )); } } _ => {} } } if fn_name == "init" && primary_opens > 1 { violations.push( "init: more than one primary open; the writable live connection must be unique" .to_string(), ); } violations } fn collect_open_calls(items: &[Item], out: &mut Vec<(String, Vec<(String, BTreeSet)>)>) { for item in items { match item { Item::Fn(function) if !attrs_mark_test_only(&function.attrs) => { let mut collector = OpenCallCollector::default(); collector.visit_block(&function.block); out.push((function.sig.ident.to_string(), collector.calls)); } Item::Impl(item_impl) if !attrs_mark_test_only(&item_impl.attrs) => { for impl_item in &item_impl.items { let ImplItem::Fn(function) = impl_item else { continue; }; if !attrs_mark_test_only(&function.attrs) { let mut collector = OpenCallCollector::default(); collector.visit_block(&function.block); out.push((function.sig.ident.to_string(), collector.calls)); } } } Item::Mod(item_mod) if !attrs_mark_test_only(&item_mod.attrs) => { if let Some((_, nested)) = &item_mod.content { collect_open_calls(nested, out); } } _ => {} } } } #[test] fn certify_live_database_secondary_opens_are_read_only() { // 负向 fixture 先证明检查器会响(修复后假绿是 R9 终审确认的 P0)。 let fixture = r#" impl Database { fn bad_bare(path: &Path) { let _ = Connection::open(path); } fn bad_flags(path: &Path) { let _ = Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_WRITE); } fn good_flags(path: &Path) { let _ = Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_ONLY); } fn good_memory() { let _ = Connection::open_in_memory(); } fn init(path: &Path) { let _ = Connection::open(path); let _ = Connection::open(path); } } "#; let parsed = syn::parse_file(fixture).expect("parse fixture"); let mut fixture_fns = Vec::new(); collect_open_calls(&parsed.items, &mut fixture_fns); let violations_of = |name: &str, fns: &[(String, Vec<(String, BTreeSet)>)]| { fns.iter() .filter(|(fn_name, _)| fn_name == name) .flat_map(|(fn_name, calls)| live_open_violations(fn_name, calls)) .collect::>() }; assert!(!violations_of("bad_bare", &fixture_fns).is_empty()); assert!(!violations_of("bad_flags", &fixture_fns).is_empty()); assert!(violations_of("good_flags", &fixture_fns).is_empty()); assert!(violations_of("good_memory", &fixture_fns).is_empty()); assert!( !violations_of("init", &fixture_fns).is_empty(), "a second primary open inside init must be flagged" ); // 真实检查:database/mod.rs 全部生产函数。 let source = fs::read_to_string(source_root().join("database/mod.rs")).expect("read database/mod.rs"); let syntax = syn::parse_file(&source).expect("parse database/mod.rs"); let mut fns = Vec::new(); collect_open_calls(&syntax.items, &mut fns); let mut violations = Vec::new(); for (name, calls) in &fns { if name == "memory" { continue; // 测试内存库工厂 } violations.extend(live_open_violations(name, calls)); } assert!( violations.is_empty(), "writable live-database connections must be unique to init: {violations:?}" ); } // --------------------------------------------------------------------------- // R6:迁移语义分离——迁移调用实参级绑定 // --------------------------------------------------------------------------- /// 收集对 `apply_schema_migrations_on_conn` 的每次调用及其实参 ident 集。 #[derive(Default)] struct MigrationCallCollector { calls: Vec>, } impl<'ast> Visit<'ast> for MigrationCallCollector { fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) { if let syn::Expr::Path(path) = node.func.as_ref() { if path .path .segments .last() .is_some_and(|segment| segment.ident == "apply_schema_migrations_on_conn") { let mut args = IdentProbe::default(); for arg in &node.args { args.visit_expr(arg); } self.calls.push(args.found); } } visit::visit_expr_call(self, node); } fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) { if node.method == "apply_schema_migrations_on_conn" { let mut args = IdentProbe::default(); for arg in &node.args { args.visit_expr(arg); } self.calls.push(args.found); } visit::visit_expr_method_call(self, node); } } #[test] fn certify_untrusted_migration_context_is_threaded() { // 函数体 ident 聚合会被迁移函数内部的 `match context` 分支假绿 // (R10 终审 P0),因此绑定到调用实参: // 1) enum 精确形状;2) 确切入口 apply_schema_migrations_on_conn 的签名 // 携带 context;3) 不可信区域的迁移调用实参含 UntrustedRestore 且绝无 // LocalUpgrade;4) 本地区域的迁移调用实参含 LocalUpgrade 且绝无 // UntrustedRestore。v16 去重 fail-closed 的行为覆盖由义务 O4 交付 // (首轮重认证前置条件)。语义注记:枚举表达"修复策略", // fresh/canonical bootstrap 映射 LocalUpgrade。 let schema = syn::parse_file(&schema_source()).expect("parse schema.rs"); let migration_source = fs::read_to_string(source_root().join("database/migration.rs")).expect("read migration.rs"); let migration = syn::parse_file(&migration_source).expect("parse migration.rs"); // 1) enum 精确形状 fn find_enum<'a>(items: &'a [Item], name: &str) -> Option<&'a syn::ItemEnum> { for item in items { match item { Item::Enum(item_enum) if item_enum.ident == name => return Some(item_enum), Item::Mod(item_mod) => { let nested = item_mod.content.as_ref().map(|(_, items)| items.as_slice()); if let Some(found) = nested.and_then(|items| find_enum(items, name)) { return Some(found); } } _ => {} } } None } let context_enum = find_enum(&schema.items, "MigrationRunContext") .or_else(|| find_enum(&migration.items, "MigrationRunContext")) .expect("MigrationRunContext enum must exist in the migration chain"); let variants: Vec = context_enum .variants .iter() .map(|variant| variant.ident.to_string()) .collect(); assert_eq!( variants, vec!["LocalUpgrade".to_string(), "UntrustedRestore".to_string()], "MigrationRunContext variants drifted from the adjudicated contract" ); // 2) 确切入口签名携带 context fn entry_takes_context(items: &[Item]) -> bool { for item in items { match item { Item::Fn(function) if function.sig.ident == "apply_schema_migrations_on_conn" => { let mut probe = IdentProbe::default(); for input in &function.sig.inputs { if let syn::FnArg::Typed(typed) = input { probe.visit_type(&typed.ty); } } return probe.found.contains("MigrationRunContext"); } Item::Impl(item_impl) => { for impl_item in &item_impl.items { let ImplItem::Fn(function) = impl_item else { continue; }; if function.sig.ident != "apply_schema_migrations_on_conn" { continue; } let mut probe = IdentProbe::default(); for input in &function.sig.inputs { if let syn::FnArg::Typed(typed) = input { probe.visit_type(&typed.ty); } } return probe.found.contains("MigrationRunContext"); } } Item::Mod(item_mod) => { if let Some((_, nested)) = &item_mod.content { if entry_takes_context(nested) { return true; } } } _ => {} } } false } assert!( entry_takes_context(&schema.items) || entry_takes_context(&migration.items), "apply_schema_migrations_on_conn must take MigrationRunContext in its signature" ); // 3) 不可信区域:UntrustedScratch impl 内的迁移调用 let backup = syn::parse_file(&backup_source()).expect("parse backup.rs"); let mut untrusted_calls = MigrationCallCollector::default(); for item in &backup.items { let Item::Impl(item_impl) = item else { continue; }; let matches_type = matches!( item_impl.self_ty.as_ref(), syn::Type::Path(type_path) if type_path .path .segments .last() .is_some_and(|segment| segment.ident == "UntrustedScratch") ); if !matches_type { continue; } for impl_item in &item_impl.items { if let ImplItem::Fn(function) = impl_item { untrusted_calls.visit_block(&function.block); } } } assert!( untrusted_calls .calls .iter() .any(|args| args.contains("UntrustedRestore")), "the untrusted scratch pipeline must call migrations with UntrustedRestore" ); assert!( untrusted_calls .calls .iter() .all(|args| !args.contains("LocalUpgrade")), "the untrusted pipeline must never request tolerant LocalUpgrade repairs" ); // 4) 本地区域:database/mod.rs ∪ schema.rs ∪ migration.rs 生产函数内的 // 迁移调用(排除 backup.rs;调用实参而非函数体,match 分支无法假绿) let mod_source = fs::read_to_string(source_root().join("database/mod.rs")).expect("read database/mod.rs"); let mod_parsed = syn::parse_file(&mod_source).expect("parse database/mod.rs"); let mut local_calls = MigrationCallCollector::default(); fn visit_production_fns(items: &[Item], collector: &mut MigrationCallCollector) { for item in items { match item { Item::Fn(function) if !attrs_mark_test_only(&function.attrs) => { collector.visit_block(&function.block); } Item::Impl(item_impl) if !attrs_mark_test_only(&item_impl.attrs) => { for impl_item in &item_impl.items { if let ImplItem::Fn(function) = impl_item { if !attrs_mark_test_only(&function.attrs) { collector.visit_block(&function.block); } } } } Item::Mod(item_mod) if !attrs_mark_test_only(&item_mod.attrs) => { if let Some((_, nested)) = &item_mod.content { visit_production_fns(nested, collector); } } _ => {} } } } visit_production_fns(&mod_parsed.items, &mut local_calls); visit_production_fns(&schema.items, &mut local_calls); visit_production_fns(&migration.items, &mut local_calls); assert!( local_calls .calls .iter() .any(|args| args.contains("LocalUpgrade")), "a production local-upgrade call site must pass LocalUpgrade" ); assert!( local_calls .calls .iter() .all(|args| !args.contains("UntrustedRestore")), "local upgrade paths must never run under UntrustedRestore semantics" ); } // --------------------------------------------------------------------------- // R7:REAL 值域——熔断阈值必须限于有限 [0,1](定向篡改) // --------------------------------------------------------------------------- #[test] fn certify_imported_circuit_threshold_domain_is_enforced() { let database = Database::memory().expect("memory db"); let exported = database.export_sql_string().expect("export"); // 定向替换:只动 proxy_config 表插入行里的阈值,不碰 DDL 默认值与 // model_pricing 的 0.60/0.65 子串(R9 终审确认全局替换会误伤)。 for hostile_value in ["6.5", "-0.5", "1e999"] { let mut replaced = 0usize; let hostile: String = exported .lines() .map(|line| { if replaced == 0 && line.contains("proxy_config") && !line.to_ascii_uppercase().contains("CREATE TABLE") && line.contains(", 0.6,") { replaced += 1; let mut tampered = line.replacen(", 0.6,", &format!(", {hostile_value},"), 1); tampered.push('\n'); tampered } else { let mut kept = line.to_string(); kept.push('\n'); kept } }) .collect(); assert_eq!( replaced, 1, "exactly one proxy_config insert row must carry the 0.6 sentinel \ (export format drifted; escalate for adjudication)" ); let target = Database::memory().expect("target db"); target .import_sql_string(&hostile) .expect_err("circuit_error_rate_threshold outside finite [0,1] must abort the import"); let conn = target.conn.lock().expect("lock target"); let out_of_domain: i64 = conn .query_row( "SELECT COUNT(*) FROM proxy_config WHERE circuit_error_rate_threshold < 0.0 OR circuit_error_rate_threshold > 1.0", [], |row| row.get(0), ) .expect("count out-of-domain thresholds"); assert_eq!( out_of_domain, 0, "aborted import must leave no out-of-domain breaker thresholds behind" ); } }