Skip to content

Commit 3fa1fa8

Browse files
committed
feat(view): extend root chain
Signed-off-by: Eli Ma <eli@patch.sh>
1 parent 7261317 commit 3fa1fa8

10 files changed

Lines changed: 2514 additions & 5 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mega2"
3-
version = "0.42.1"
3+
version = "0.42.2"
44
edition = "2024"
55
license = "MIT OR Apache-2.0"
66
description = "Monorepo engine and Git hosting server for AI agents: trunk-based storage, Git Smart HTTP, Git LFS, OCI registry, and Agent Session Capture"

‎docs/plan/plan-20261002.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
| 2026-10-06 | HP-07 实施与 ER-05 复审 | 新增 P0 `[views]` 配置、严格白名单、范围和 trunk + sha1 前提校验,以及七个热字段与两个重启字段的 reload 分类;四条定向验证、两条命令层金丝雀检查、样例与双语文档整表校验、格式、Clippy、全量测试与两个构建门通过。Claude HP07-R1 与 Codex HP07-R2 均 PASS;`v0.41.80` 的 Docker D 组按发布者指令不等待、不重试。 | 无拆卡 | HP-07、Review log、ER-04 C/D |
160160
| 2026-10-06 | HP-08 实施与 ER-05 复审 | 保留 `.view` / `.filter` 作为真实路径首段:配置、创建与写入策略立即拒绝;启用视图时在监听前 fail-closed 扫描根树与 `mega_refs`。五条定向验证、监听顺序与 17 个文档落点检查、格式、Clippy、全量测试与两个构建门通过。Claude HP08-R1 与 Codex HP08-R2 均 PASS;`v0.42.0` 的 Docker D 组按发布者指令不等待、不重试。 | 无拆卡 | HP-08、Review log、ER-04 C/D |
161161
| 2026-10-06 | HP-09 实施与 ER-05 复审 | 新增八张视图数据表、八个 SeaORM 实体及注册迁移;六条目录级迁移用例覆盖精确 schema、无外键、重放幂等、既有 schema 不变、实体往返、故障回滚和恢复。格式、Clippy、全量测试与两个构建门通过。Claude HP09-R1 与 Codex HP09-R2 均 PASS;`v0.42.1` 的 Docker D 组按发布者指令不等待、不重试。 | 无拆卡 | HP-09、Review log、ER-04 C/D |
162+
| 2026-10-06 | HP-10 实施与 ER-05 复审 | 新增根链扩展、段事务与预算、SQLSTATE 锁超时处理、根链冲突核对、共享进程内告警去重、锁键和真实对象夹具;设计 §4.4 补 `hash32` 定义,§4.6 将 L_V(F) 的键改为 `(VIEW_FILTER_LOCK_NS, hash32(F.pk))`。六个定向用例覆盖首父链、预算/重启/双连接交错、不连续与冲突、等锁失败和锁键隔离。格式、Clippy、全量测试与两个构建门通过。Docker D 组按发布者指令不等待、不重试。 | 无拆卡 | HP-10、设计 §4.4/§4.6、Review log、ER-04 C/D |
162163

163164
## 已决议设计决策
164165

@@ -2045,7 +2046,7 @@ EMPTY_TREE 的依据:
20452046

20462047
**Task type:** `implementation`
20472048

2048-
**Lifecycle / Acceptance:** `pending` / 空
2049+
**Lifecycle / Acceptance:** `remote-pending` / `locally-accepted`(本地验收已完成;承载发布 `v0.42.2` 的 Docker D 组按发布者指令不等待、不重试)
20492050

20502051
**Description:** 在 `ViewStorage` 中实现设计 §3.3 的 `extend_root_chain(budget, batch_size, lock_mode) -> Result<RootChainOutcome, MegaError>`,唯一的行为轴是「根历史 → 根链表的扩展(带连续性校验)」。结果枚举为 `RootChainOutcome::{CaughtUp, NotCaughtUp, Discontinuous(DiscontinuityReason)}`,下文简写为 `CaughtUp`、`NotCaughtUp`、`Discontinuous(reason)`;除第 1 步的等锁超时外,数据库错误都以 `Err(MegaError::Db(_))` 返回。本卡的签名都写 `Result<T, MegaError>`,不写 `MegaResult<T>`:仓内的 `MegaResult` 是 `Result<(), MegaError>` 的别名,不带类型参数。拆出 HP-28(`root_chain_halted` 谓词、不连续结论的持久复现、清空暂存表后的重新判定)。本卡范围:
20512052

@@ -6003,6 +6004,11 @@ Result 只允许 `PASS` 或 `FAIL`。`FAIL` 必须列出 P0/P1 条目,并在
60036004
| HP08-R2 | HP-08 当前差异、保留名边界、启动 fail-closed 与文档同步,Codex 独立复审 | PASS | P0 0;P1 0;P2 0 | 首段精确比较、根树与 `mega_refs` 扫描、脱敏错误、监听顺序与中英文同步均逐项核对 | `libra diff --check`、Verification 1–7、C 组 |
60046005
| HP09-R1 | HP-09 当前差异、八表 schema、迁移原子性、实体映射与文档同步,Claude Code 独立只读复审 | PASS | P0 0;P1 0;P2 0 | 精确缺省值、双向外键目录查询、约束定义快照及 BIGSERIAL 所属序列均已核对 | 当前差异、六条迁移测试、Verification 1–7、C 组 |
60056006
| HP09-R2 | HP-09 当前差异、设计 §3.1–§3.5 / §6.5 与任务验收,Codex 独立复审 | PASS | P0 0;P1 0;P2 0 | 八表 DDL、实体、注册、回放、故障恢复与文档落点逐项核对 | `libra diff --check`、Verification 1–7、C 组 |
6007+
| HP10-R1 | HP-10 初始实现:根链预算、段事务、锁与测试,Claude Code 2.1.288 独立只读复审 | FAIL | P0 0;P1 5:递归 CTE 预算边界、生产 `expect`、交错覆盖、预算恢复与不连续用例不足;后续修订关闭 | P2 5:锁键断言、接入测试、文档与夹具细节;后续修订关闭 | Claude Code 只读输出;复审见 HP10-R2 |
6008+
| HP10-R2 | HP-10 修订:根链事务、夹具与可观测性,Claude Code 2.1.288 独立只读复审 | PASS | P0 0;P1 0 | P2 6:RowConflict 事件与断言、模块 target、真实夹具与默认树、取消锁的时限、文档与修订历史;均在 HP10-R3 前关闭 | Claude Code 2.1.288 只读输出 |
6009+
| HP10-R3 | HP-10 修订:事件、文档与锁隔离,Claude Code 2.1.288 独立只读复审 | PASS | P0 0;P1 0 | P2 3:§4.4/§4.6 修订历史、`hash32` 公式、锁常量第六对;均在 HP10-R4 前关闭 | Claude Code 2.1.288 只读输出 |
6010+
| HP10-R4 | HP-10 P2 关闭复审:根链实现、设计与任务卡,Claude Code 2.1.288 独立只读复审 | PASS | P0 0;P1 0;P2 0 | 无 | Claude Code 只读输出;§4.4/§4.6、HP-10 文档及锁键断言 |
6011+
| HP10-R5 | HP-10 最终差异、设计 §3.3/§4.4 与六项验证,Codex 独立复审 | PASS | P0 0;P1 0;P2 0 | 根链预算、段事务、冲突回滚、六种不连续、SQLSTATE 锁超时、进程内告警、锁隔离、共享聚合和文档已逐项核对 | `libra diff --check`、六项 HP-10 Verification、格式、Clippy、构建门与两次全量测试 |
60066012

60076013
## 非目标与延后项
60086014

‎docs/refactoring/history-projection.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,7 @@ fn mark_ready_if_covered(F, tip) -> bool: # 只在持
855855
- **就绪与水位同事务【决策】。** 首次就绪在把水位推进到 tip 的那个批事务内写入,与 3.1、6.5 所说的‘视图首次就绪的那个 catch_up 批事务’一致,只有该批读到的 `main@/` 已经超出根链时,才留给之后 s0 ≥ tip 分支的事务置就绪;6.9 的候选谓词保证它在一个周期内被选中。如果放在不同的事务里,最后一批提交之后、置就绪之前,只要发生以下任一情形,视图就会停在‘水位已到链尾、`ready_seq` 仍为 NULL’的状态:进程退出,该事务遇到瞬时数据库错误,或者后台 worker 对 L_G 试锁失败后返回(4.6)。此后入口要到下一个补偿周期才不再返回 503,名额也多占一个周期。判定是否覆盖 `main@/` 时,与根链中 seq = tip 那一行的 `commit_id` 比较,不重读链尾。【推断】READ COMMITTED 下,`max_seq()` 与链尾是两条语句,各取一次快照;其间 `extend_root_chain` 可能接入新行,重读链尾会把落后的水位误判为已覆盖 `main@/`。
856856
- **锁【决策】。** 视图锁和根链锁都用两个整数作键:
857857
- `VIEW_LOCK_NS` 与 `VIEW_FILTER_LOCK_NS` 互不相同,也与 `MONO_WRITE_LOCK` 及初始化锁的 key1 都不同;
858-
- 单例锁(根链锁、L_G、L_R)用 `key1 = VIEW_LOCK_NS`,`key2` 分别取 `ROOT_CHAIN_KEY`、`OBJECT_GC_KEY`、`REGISTER_KEY`。视图锁改用独立的 `key1 = VIEW_FILTER_LOCK_NS`,`key2 = hash32(filter_pk)`,与单例锁不会撞键;
858+
- 单例锁(根链锁、L_G、L_R)用 `key1 = VIEW_LOCK_NS`,`key2` 分别取 `ROOT_CHAIN_KEY`、`OBJECT_GC_KEY`、`REGISTER_KEY`。视图锁改用独立的 `key1 = VIEW_FILTER_LOCK_NS`,`key2 = hash32(filter_pk)`(`hash32(x) = ((x as u64) ^ ((x as u64) >> 32)) as u32 as i32`,即 64 位 id 的高低 32 位异或折叠),与单例锁不会撞键;
859859
- `cfg(test)` 下,key2 改为 `hashtext(current_schema() || ':' || d)`,其中 d 是 filter_pk 的十进制文本,或者 `'root_chain'`、`'object_gc'`、`'register'` 之一。这样并行的测试 schema 之间互不争锁,同一 schema 内根链锁与各视图锁也仍然互不相同。`mono_write_lock_sql` 只有一把锁,可以整体替换 key2;这里有多把锁,不能照搬。
860860

861861
投影写入只发生在同时持有视图锁和 L_G 共享锁的 worker 事务中;回收、清扫与 rebuild 的删除按 4.6 执行;B3 等写入判定路径对这些表只读(5.2)。对象维护锁 L_G、各类事务的取锁方式和全局锁顺序,见 4.6。投影是确定的,重复计算无害:写入都是 `ON CONFLICT DO NOTHING`,水位只增不减。抢锁失败时直接返回,不设进程内标记。【推断】这样做不影响正确性:B3 的写入判定只用锁内算出的 `tip_at_R`(4.5);advertise 与 B0 的同步路径或者带 `lock_timeout` 阻塞等锁,或者在追不上时按 3.3、4.4 广告已投影的 tip、返回可重试错误,都不依赖 worker 何时被唤醒。活性只由周期补偿保证,前提是周期任务的候选谓词覆盖所有未完成的活跃视图(6.9 `worker_interval_secs`)。代价是:持锁者最后一次读取链尾之后落地的提交,在本副本上最多多等一个 `worker_interval_secs`。原来的 dirty 握手也给不出更强的保证:持锁者检查标记之后、提交并释放锁之前,竞争者仍可能置位后离开;而且标记只在进程内可见,跨副本无效。如果实测需要更低的延迟,再加回在释放锁之后复查的版本。
@@ -899,7 +899,7 @@ fn mark_ready_if_covered(F, tip) -> bool: # 只在持
899899
|---|---|---|
900900
| L_M:`MONO_WRITE_LOCK` | 现有 | B3、物化插入、ADR-TP-20 的对账与巡检批次、reaper(试锁) |
901901
| L_C:根链锁 | `(VIEW_LOCK_NS, ROOT_CHAIN_KEY)` | `extend_root_chain`、全局 rebuild |
902-
| L_V(F):视图锁 | `(VIEW_LOCK_NS, view_key(F.pk))` | catch_up(F)、回收与单视图 rebuild |
902+
| L_V(F):视图锁 | `(VIEW_FILTER_LOCK_NS, hash32(F.pk))` | catch_up(F)、回收与单视图 rebuild |
903903
| L_G:对象维护锁(新增,分共享与排他) | `(VIEW_LOCK_NS, OBJECT_GC_KEY)`;`cfg(test)` 下 d 取 `'object_gc'`(4.4) | 共享:catch_up 批事务、回收;排他:清扫、全局 rebuild |
904904
| L_R:注册准入锁 | `(VIEW_LOCK_NS, REGISTER_KEY)`;`cfg(test)` 下 d 取 `'register'`(4.4) | 6.5 的注册准入事务与重新预热准入事务 |
905905

‎docs/refactoring/integration.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,47 @@ schema;八个 SeaORM 实体各写入后按主键读回;以及预建同名
171171
`mega_view_register_log` view 使最终索引失败时,八张表和迁移记录全部回滚,
172172
移除冲突对象后可直接重跑。
173173

174+
## DB 模块覆盖:视图根链(HP-10)
175+
176+
`ViewStorage::extend_root_chain(budget, batch_size, lock_mode)` 在短事务中持有
177+
根链锁 L_C,返回 `CaughtUp`、`NotCaughtUp` 或 `RolledBack`、`Forked`、
178+
`UnrelatedHistory`、`MultiParent`、`MissingFirstParent`、`RowConflict` 六种不连续原因。扫描提交的首父链
179+
先落入 `mega_view_root_chain_scan`,再按 `seq` 递增分段写入
180+
`mega_view_root_chain`;每段把插入和锚点切换放在同一个事务中。预算同时计算暂存
181+
写入与根链插入,每批回走和每段接入都按剩余预算截断;锚点判定不写行,也不耗
182+
预算。耗尽时保留锚点供下次继续。`insert_segment` 在调用方的段事务内
183+
执行;主键冲突核对失败时,调用方回滚整个段,因此不会留下该段已先插入的部分行。
184+
不连续事务不改根链表,同一调用先前已经提交的段保持有效。
185+
186+
锁键使用 `VIEW_LOCK_NS`、`VIEW_FILTER_LOCK_NS`、`ROOT_CHAIN_KEY`、
187+
`OBJECT_GC_KEY` 与 `REGISTER_KEY`;段上限是
188+
`ROOT_CHAIN_SEGMENT_ROWS = 10_000`。`hash32(x) = ((x as u64) ^ ((x as u64) >> 32)) as u32 as i32`
189+
折叠 64 位 id 的高低两半。
190+
测试 schema 把第二键映射为 `hashtext(current_schema() || ':' || d)`,使并行 schema
191+
不相互阻塞。`Blocking` 模式以绑定的 `set_config('lock_timeout', $1, true)` 设置
192+
`VIEW_LOCK_TIMEOUT`(两秒)等待上限;超时按 SQLSTATE `55P03` 返回未追上,取消等
193+
其他数据库错误原样上抛。返回 `Ok(false)` 的事务已经 aborted,调用方只能回滚;
194+
成功设置的 `lock_timeout` 一直持续到事务结束。取锁语句由 `view_lock_stmt_prod` 与
195+
`view_lock_stmt_test` 单独拼装,语句本身不带 `lock_timeout`;需要不限时等待的调用方
196+
可直接执行 `Blocking` 语句。
197+
198+
`ViewStorage` 聚合在 `AppService` 中,访问器返回克隆而不新建实例,以便同一进程
199+
共享告警记录。它的 `base` 字段保持私有;同级实现经 `Deref<Target = BaseStorage>`
200+
取得连接。`view_test_fixtures` 以 `pub(crate)` 提供参数绑定的路径到 blob 字节和
201+
预构造 tree 列表、线性/多父/首父缺失/无关根历史,以及可回退的 root `main` CAS
202+
夹具;它写入 tree 层、返回 blobs,但不写 blob。
203+
204+
六个 lib 用例分别覆盖:线性冷启动和增量;预算耗尽后的续接及回走中途重启;分段
205+
锚点、每段后的重启、两个连接池按 Try/Blocking 换手,以及扫描期间 `main@/` 前进;
206+
五类根历史不连续、三种 `insert_segment` 既有行输入和第二段冲突;不成功的
207+
Try/Blocking/取消等锁;以及全部锁键与同 schema 的两两隔离。
208+
不连续事件仅从 `view_root_chain` 发出,带 `reason` 与 `commit_id`,按进程中的
209+
`(reason, commit_id)` 去重,并在 `CaughtUp` 后清除记录。带数据库的事件捕获在
210+
单线程 Tokio 测试中安装按该模块 target 过滤的线程局部 subscriber,同时持有第二个
211+
`tracing::Dispatch`,避免并行测试首次命中 callsite 时缓存 no-op interest。取消等锁
212+
用持锁事务之外的自动提交查询在一秒内轮询 `pg_stat_activity`;事务快照不会看到之后
213+
才出现的等待者。代码审查点是根链只按 `seq` 递增插入,绝不以 `max(seq)+1` 追加单行。
214+
174215
## 迁移覆盖:`merge_queue.requester` nullable 列(UN-18)
175216

176217
**历史表。** [`plan-20260910.md`](../plan/plan-20260910.md) MW-05 已 `DROP TABLE merge_queue`;下列描述 UN-18 当时的加列迁移,不再是现行 schema。

‎src/jupiter/storage/mod.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ pub mod push_queue_storage;
2323
pub mod stg_common;
2424
pub mod user_storage;
2525
pub mod vault_storage;
26+
pub mod view_root_chain;
27+
pub mod view_storage;
28+
#[cfg(test)]
29+
pub(crate) mod view_test_fixtures;
2630
pub mod webhook_storage;
2731

2832
use std::sync::Arc;
@@ -71,6 +75,7 @@ use crate::{
7175
push_queue_storage::PushQueueStorage,
7276
user_storage::UserStorage,
7377
vault_storage::VaultStorage,
78+
view_storage::ViewStorage,
7479
webhook_storage::WebhookStorage,
7580
},
7681
},
@@ -85,6 +90,7 @@ pub struct AppService {
8590
pub user_storage: UserStorage,
8691
pub group_storage: GroupStorage,
8792
pub vault_storage: VaultStorage,
93+
pub view_storage: ViewStorage,
8894
pub cl_storage: ClStorage,
8995
pub issue_storage: IssueStorage,
9096
pub conversation_storage: ConversationStorage,
@@ -112,6 +118,7 @@ impl AppService {
112118
user_storage: UserStorage { base: mock.clone() },
113119
group_storage: GroupStorage { base: mock.clone() },
114120
vault_storage: VaultStorage { base: mock.clone() },
121+
view_storage: ViewStorage::new(mock.clone()),
115122
cl_storage: ClStorage { base: mock.clone() },
116123
issue_storage: IssueStorage { base: mock.clone() },
117124
conversation_storage: ConversationStorage { base: mock.clone() },
@@ -252,6 +259,7 @@ impl Storage {
252259
user_storage,
253260
group_storage,
254261
vault_storage,
262+
view_storage: ViewStorage::new(base.clone()),
255263
cl_storage: cl_storage.clone(),
256264
issue_storage,
257265
conversation_storage,
@@ -573,6 +581,10 @@ impl Storage {
573581
self.app_service.vault_storage.clone()
574582
}
575583

584+
pub fn view_storage(&self) -> ViewStorage {
585+
self.app_service.view_storage.clone()
586+
}
587+
576588
pub fn cl_storage(&self) -> ClStorage {
577589
self.app_service.cl_storage.clone()
578590
}

0 commit comments

Comments
 (0)