docs(database): close restore hardening prerequisite

This commit is contained in:
SaladDay
2026-08-02 10:10:45 +00:00
parent 3fa6b1f158
commit 2533db035a
4 changed files with 72 additions and 9 deletions
+13
View File
@@ -50,6 +50,19 @@
> 文档考据说明:修正案 1/2(`pi-support-contracts-amendment-*.md`)的条款已按其自身要求**合并**进 `pi-support-contracts-zh.md` 与 `pi-support-review-contract-zh.md`,独立文件已随合并删除,这是预期状态而非丢失;本文引用的"修正案 2 §F/E1"以合并后的规范文档对应章节为准。 > 文档考据说明:修正案 1/2(`pi-support-contracts-amendment-*.md`)的条款已按其自身要求**合并**进 `pi-support-contracts-zh.md` 与 `pi-support-review-contract-zh.md`,独立文件已随合并删除,这是预期状态而非丢失;本文引用的"修正案 2 §F/E1"以合并后的规范文档对应章节为准。
## 4.6 前置 B 终止关账(用户裁决,2026-08-02,优先于 §4.5)
§4.5 的缩围执行后,重认证 R1 仍在**存量产品面**出现 3 项 High(配置目录可变
性竞态、restore 未复用写面校验链、safety backup 可预测文件名)。用户裁决:
**PR 聚焦 Pi 业务,restore 老面放一放,不乱扩张**。据此前置 B **终止关账**:
- Pi 耦合面已达成且在两套认证套件中持续全绿,Pi 工程期间完成的加固随 PR 保留;
- 5 项未决 finding 与 v1v15 历史导入移交 `docs/restore-hardening-debt-zh.md`
(独立"Restore 加固工程"债务登记),不在本 PR 范围;
- infra 基线(schema.rs/migration.rs/backup.rs)已在前置 A 套件中按 `3fa6b1f1`
状态**重冻**:本 PR 内任何再触碰即认证红,机械防止范围回潮;
- 前置 B 不再有重认证轮次;§4.5 的 N/N−1 缩围作为已落地事实保留。
## 4.5 范围修订(用户批准,2026-08-02) ## 4.5 范围修订(用户批准,2026-08-02)
前置 B 的不可信恢复(SQL/binary 双入口)缩围为**仅接受 user_version N 与 N1** 前置 B 的不可信恢复(SQL/binary 双入口)缩围为**仅接受 user_version N 与 N1**
+43
View File
@@ -0,0 +1,43 @@
# Restore 加固工程:债务登记表(自 Pi 工程移交)
> 状态:立项挂起的独立未来工程。由用户裁决(2026-08-02)自 Pi 支持工程移交:
> Pi 的 PR 聚焦业务点,restore 存量产品面的对抗加固不属于其范围。
> 移交时点:前置 B 终止 checkpoint `3fa6b1f158b4e609b1588affb366bd90fb11878e`。
> 表中 D1–D4 为 **Pi 工程之前即存在的产品面债务**(非 Pi 引入、非回归);
> **D5 例外**:该死代码由 Pi 工程自身的 `4f784514`(typed provider writes /
> canonical restore)引入,属本工程遗留物,未来工程处理时应优先直接删除。
> Pi 耦合面(设备本地台账重建、弱化 schema 不可发布、token 不入可迁移产物)
> 已实现并在两套认证套件中持续全绿。
## Pi 工程期间已完成并保留的加固(随 Pi PR 交付)
- Data-only canonical restore:`UntrustedScratch`/`CanonicalStage` 类型屏障,
导入 schema 永不成为发布权威;
- `MigrationSourceSpec`(v16/v17)源规格先行 + 全函数 typed 映射;
- 不可信恢复版本门:仅接受 user_version N/N1(16/17),含升级前备份回滚专测;
- `MigrationRunContext { LocalUpgrade, UntrustedRestore }` 语义分离;
- 值域强制(sort_index、circuit_error_rate_threshold ∈ [0,1] 等)、
auto_vacuum 继承、设备本地台账 canonical 重建;
- 恶意 schema 语料、预算、文件边界等既有测试全绿(Rust 2372 passed)。
## 未决 finding(终止时点,均已双 reviewer/主审确认)
| # | 严重度 | 描述 | 现有缓解 |
|---|---|---|---|
| D1 | High | `Database` 未持有不可变 live 路径:配置目录变更"稍后重启"期间,旧连接与新目录缓存并存,safety backup 与 canonical publish 可作用于不同库(mod.rs:90、backup.rs:2295、app_store.rs:104、useSettings.ts:363)。根治需 `DatabaseHome`/`BackupRepository` 不可变注入架构 | 无(建议未来工程首项;临时缓解可为"待重启状态禁用备份/恢复操作") |
| D2 | High | restore 行校验未复用写面校验链:可发布空 ID/app_type(`ProviderKey::new` 会拒)、空白或尾斜杠 URL 的 endpoint(尾斜杠规范化在 `NewProviderAggregate::from_input` 而非 `NewEndpoint` 内,恢复路径两者都绕过),恢复后无法被 remove/touch 命中(backup.rs:549) | 写面自身拒绝此类值;仅恢复路径可引入 |
| D3 | High | safety backup 直写可预测最终文件名;dangling symlink 绕过 `Path::exists()`,`Connection::open` 跟随链接;nofollow 检查晚于内容写出(backup.rs:2307) | 需本机文件系统攻击者前置 |
| D4 | Medium | 全表 decoder sweep 不完整:`proxy_live_backup.original_config` 非法 JSON、`usage_daily_rollups.date` 非法日期可发布,生产读取时失败(backup.rs:633) | 读取侧失败即暴露,无静默数据错 |
| D5 | Medium | 取消标志固定 false,无 setter/外部 token,cancellation check 不可触发(backup.rs:1248) | 纯死代码,建议未来删除或接真实 token |
## 一并挂起的既定立项
- v1–v15 历史备份导入(缩围裁决 2026-08-02;`MigrationSourceSpec` 架构为其地基);
- 主审否决记录:两位 reviewer 建议"升级前备份失败应阻断启动",因违反
"LocalUpgrade 既有行为不变"裁决未采信——未来工程重议时须重新评估。
## 机制锁定
Pi PR 内 restore 面已被前置 A 认证套件的 infra SHA-256 基线重冻
(schema.rs/migration.rs/backup.rs @ 3fa6b1f1 状态):任何再触碰即认证红,
防止范围回潮。未来工程启动时由其裁决方解冻。
@@ -9,8 +9,9 @@
//! //!
//! ## 与前置 A 的衔接(解冻声明) //! ## 与前置 A 的衔接(解冻声明)
//! 前置 A 套件以 SHA-256 冻结了 schema.rs/migration.rs/backup.rs。本前置 //! 前置 A 套件以 SHA-256 冻结了 schema.rs/migration.rs/backup.rs。本前置
//! 工程的实现必然修改它们,因此:**pre-B 实现期间,pre-A 的 //! 工程的实现必然修改它们,因此:**pre-B 实现期间(已于 2026-08-02
//! `certify_infra_files_frozen_until_preproject_b` 预期为红**,这是裁决内 //! 终止关账,该窗口已闭合),pre-A 的
//! `certify_infra_files_stay_frozen_after_restore_closeout` 预期为红**,这是裁决内
//! 的解冻,不是违规;pre-B 认证通过后由裁决方以终态哈希重冻基线。除此之外 //! 的解冻,不是违规;pre-B 认证通过后由裁决方以终态哈希重冻基线。除此之外
//! pre-A 的其余 34 项必须全程保持绿——restore 改动破坏写面契约同样是失败。 //! pre-A 的其余 34 项必须全程保持绿——restore 改动破坏写面契约同样是失败。
//! //!
@@ -35,6 +35,10 @@
//! 旧符号加入禁止清单。 //! 旧符号加入禁止清单。
//! 4. infra 时序缺口机械冻结:`schema.rs`/`migration.rs`/`backup.rs` 由 //! 4. infra 时序缺口机械冻结:`schema.rs`/`migration.rs`/`backup.rs` 由
//! SHA-256 基线钉死;任何变更使前置 A 认证失效,须回裁决方重审。 //! SHA-256 基线钉死;任何变更使前置 A 认证失效,须回裁决方重审。
//! 【2026-08-02 重冻】基线更新为前置 B 终止点(3fa6b1f1)状态。用户
//! 裁决:本 PR 聚焦 Pi 业务,restore 存量面自此锁定——通用恢复对抗
//! 加固连同未决 finding 移交独立"Restore 加固工程"(见
//! docs/restore-hardening-debt-zh.md);本 PR 内再触碰这三个文件即红。
//! //!
//! ## 扫描器 authority 表(精确相对路径 × DML 种类 × 列集合) //! ## 扫描器 authority 表(精确相对路径 × DML 种类 × 列集合)
//! - `database/dao/provider_write.rs`:全部 provider DML 允许(写面本体); //! - `database/dao/provider_write.rs`:全部 provider DML 允许(写面本体);
@@ -315,15 +319,15 @@ const STATE_COLUMNS_FAILOVER_RS: [&str; 1] = ["in_failover_queue"];
const INFRA_DEFERRED_TO_B: [(&str, &str); 3] = [ const INFRA_DEFERRED_TO_B: [(&str, &str); 3] = [
( (
"database/schema.rs", "database/schema.rs",
"f884c15ee5447f28c321a4fef0c1c50010833d7c05f2e5d9530961f5ca17ed27", "996eae9d77456edc7e56fa755bd888e0bc545bb90153449e67ac9d8bec19890d",
), ),
( (
"database/migration.rs", "database/migration.rs",
"7e9125e55ca75837242225b462707ad4cde1d0ebfb09c4d66cd4ccc950360bfa", "2cf6d466af026e13991bfc2c99189d50e902e8e4ff9f7931ef6c2d92e3c933be",
), ),
( (
"database/backup.rs", "database/backup.rs",
"31c42a61d43395e09751ffc413a822d034274e092d38c446f9ae89a5fc86f249", "024991651ed52bc366ec6923c53da47d9dfddb8044721c9526d80fa13a70f809",
), ),
]; ];
@@ -814,16 +818,18 @@ fn certify_forbidden_symbols_are_zero_treewide() {
} }
#[test] #[test]
fn certify_infra_files_frozen_until_preproject_b() { fn certify_infra_files_stay_frozen_after_restore_closeout() {
// infra 时序缺口的机械冻结:前置 B 认证前,这三个文件的任何字节变更都 // infra 时序缺口的机械冻结前置 B 已按用户裁决终止关账(2026-08-02,
// 使前置 A 失效。变更必须回裁决方重审并更新基线。 // 见 docs/pi-support-restructure-zh.md §4.6 与 restore-hardening-debt-zh.md):
// 基线以 3fa6b1f1 状态重冻,**本 PR 内这三个文件不再有解冻窗口**,
// 任何字节变更都使前置 A 失效并须回裁决方重审。
let root = source_root(); let root = source_root();
for (relative, expected) in INFRA_DEFERRED_TO_B { for (relative, expected) in INFRA_DEFERRED_TO_B {
let bytes = fs::read(root.join(relative)).expect("read infra file"); let bytes = fs::read(root.join(relative)).expect("read infra file");
let actual = format!("{:x}", Sha256::digest(&bytes)); let actual = format!("{:x}", Sha256::digest(&bytes));
assert_eq!( assert_eq!(
actual, expected, actual, expected,
"{relative} changed while deferred to prerequisite B; \ "{relative} changed after the adjudicated restore close-out; \
pre-A certification is invalidated, escalate for adjudication" pre-A certification is invalidated, escalate for adjudication"
); );
} }