Skip to main content

zenith_foundation/
ledger.rs

1//! 分层资源账本模块
2//!
3//! 实现根账本 → 域账本 → 队列账本 → 连接账本的四级分层结构。
4//! 每层都有硬上限,子账本总配额不超过父账本。
5//!
6//! 设计原则:
7//! - 单一所有者:账本树由一个 Supervisor 独占拥有,无 Arc/Rc 引用计数
8//! - 无锁设计:所有操作为 &mut self,Rust 类型系统保证并发安全
9//! - 类型化令牌:每种资源类型独立跟踪,编译期安全
10//! - 检查算术:所有加减操作使用 checked_*,防止溢出
11
12use std::collections::BTreeMap;
13
14use crate::error::{CoreError, CoreResult};
15
16/// 资源账本类型层级
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum LedgerType {
19    /// 根账本(全局唯一)
20    Root,
21    /// 域账本(每 Worker 一个)
22    Domain,
23    /// 队列账本(每队列一个)
24    Queue,
25    /// 连接账本(每连接一个)
26    Connection,
27}
28
29impl LedgerType {
30    /// 获取层级深度(根=0,连接=3)
31    pub fn depth(&self) -> u8 {
32        match self {
33            LedgerType::Root => 0,
34            LedgerType::Domain => 1,
35            LedgerType::Queue => 2,
36            LedgerType::Connection => 3,
37        }
38    }
39}
40
41/// 资源类型(令牌化)
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub enum ResourceType {
44    /// 内存资源(字节)
45    Memory,
46    /// Frame 资源(帧数量)
47    Frames,
48    /// 连接资源(活动连接数)
49    Connections,
50}
51
52impl ResourceType {
53    /// 获取资源类型名称
54    pub fn name(&self) -> &'static str {
55        match self {
56            ResourceType::Memory => "memory",
57            ResourceType::Frames => "frames",
58            ResourceType::Connections => "connections",
59        }
60    }
61}
62
63/// 账本令牌数据
64#[derive(Debug, Clone, Default)]
65pub struct LedgerQuota {
66    /// 内存配额(字节)
67    pub memory_bytes: u64,
68    /// Frame 配额
69    pub frame_count: u64,
70    /// 连接配额
71    pub connection_count: u64,
72}
73
74impl LedgerQuota {
75    /// 创建零配额
76    #[inline]
77    pub const fn zero() -> Self {
78        Self {
79            memory_bytes: 0,
80            frame_count: 0,
81            connection_count: 0,
82        }
83    }
84
85    /// 创建无限配额(仅用于根账本配置边界)
86    #[inline]
87    pub const fn unlimited() -> Self {
88        Self {
89            memory_bytes: u64::MAX,
90            frame_count: u64::MAX,
91            connection_count: u64::MAX,
92        }
93    }
94
95    /// 逐项检查子配额是否可以被父账本容纳
96    #[inline]
97    pub fn can_allocate(&self, child: &LedgerQuota) -> CoreResult<()> {
98        if child.memory_bytes > self.memory_bytes {
99            return Err(CoreError::quota_exceeded(
100                "memory",
101                self.memory_bytes,
102                child.memory_bytes,
103            ));
104        }
105        if child.frame_count > self.frame_count {
106            return Err(CoreError::quota_exceeded(
107                "frames",
108                self.frame_count,
109                child.frame_count,
110            ));
111        }
112        if child.connection_count > self.connection_count {
113            return Err(CoreError::quota_exceeded(
114                "connections",
115                self.connection_count,
116                child.connection_count,
117            ));
118        }
119        Ok(())
120    }
121
122    /// 消耗指定类型的配额(checked arithmetic)
123    #[inline]
124    pub fn consume(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
125        let field = match resource {
126            ResourceType::Memory => &mut self.memory_bytes,
127            ResourceType::Frames => &mut self.frame_count,
128            ResourceType::Connections => &mut self.connection_count,
129        };
130        *field = field
131            .checked_sub(amount)
132            .ok_or_else(|| CoreError::arithmetic_overflow("sub", *field, amount))?;
133        Ok(())
134    }
135
136    /// 归还指定类型的配额(checked arithmetic)
137    #[inline]
138    pub fn restore(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
139        let field = match resource {
140            ResourceType::Memory => &mut self.memory_bytes,
141            ResourceType::Frames => &mut self.frame_count,
142            ResourceType::Connections => &mut self.connection_count,
143        };
144        *field = field
145            .checked_add(amount)
146            .ok_or_else(|| CoreError::arithmetic_overflow("add", *field, amount))?;
147        Ok(())
148    }
149
150    /// 获取指定类型的值
151    #[inline]
152    pub fn get(&self, resource: ResourceType) -> u64 {
153        match resource {
154            ResourceType::Memory => self.memory_bytes,
155            ResourceType::Frames => self.frame_count,
156            ResourceType::Connections => self.connection_count,
157        }
158    }
159
160    /// 计算剩余配额 = total - used(饱和减法)
161    pub fn remaining(&self, used: &LedgerQuota) -> LedgerQuota {
162        LedgerQuota {
163            memory_bytes: self.memory_bytes.saturating_sub(used.memory_bytes),
164            frame_count: self.frame_count.saturating_sub(used.frame_count),
165            connection_count: self.connection_count.saturating_sub(used.connection_count),
166        }
167    }
168}
169
170/// 层级资源账本
171///
172/// # 设计
173/// - 单所有者:整个账本树由一个 Supervisor 独占拥有
174/// - 无子账本用 Arc/Rc,所有子账本为 owned 存储
175/// - 父账本只通过层级路径索引子账本,不保留反向引用
176/// - 所有操作为 `&mut self`,Rust 类型系统自然保证线程安全
177#[derive(Debug)]
178pub struct ResourceLedger {
179    /// 账本名称(唯一 ID)
180    name: String,
181    /// 账本类型
182    ledger_type: LedgerType,
183    /// 总配额
184    total: LedgerQuota,
185    /// 已使用配额(子树汇总:本账本自身直接用量 + 所有子账本 roll-up 用量)
186    used: LedgerQuota,
187    /// 本账本自身直接分配量(**不含**子账本 roll-up)
188    ///
189    /// 与 `used` 双轨记账,解决「used 同时承担直接用量与子树汇总两语义」导致的
190    /// 双重计数问题:`allocate` 分配校验用 `used_direct + allocated_to_children <= total`,
191    /// 而 `used` 保持子树汇总语义(供 `used()`/`remaining()` 返回)。
192    used_direct: LedgerQuota,
193    /// 已分配给子账本的总配额(用于校验子配额之和不超过父配额)
194    allocated_to_children: LedgerQuota,
195    /// 子账本集合(按名称有序,使用 BTreeMap 保证迭代确定性)
196    children: BTreeMap<String, ResourceLedger>,
197}
198
199impl ResourceLedger {
200    /// 创建新的资源账本(Root 级)
201    pub fn new(name: impl Into<String>, ledger_type: LedgerType, total: LedgerQuota) -> Self {
202        Self {
203            name: name.into(),
204            ledger_type,
205            total,
206            used: LedgerQuota::zero(),
207            used_direct: LedgerQuota::zero(),
208            allocated_to_children: LedgerQuota::zero(),
209            children: BTreeMap::new(),
210        }
211    }
212
213    /// 创建根账本
214    pub fn root(total: LedgerQuota) -> Self {
215        Self::new("root", LedgerType::Root, total)
216    }
217
218    /// 获取账本名称
219    #[inline]
220    pub fn name(&self) -> &str {
221        &self.name
222    }
223
224    /// 获取账本类型
225    #[inline]
226    pub fn ledger_type(&self) -> LedgerType {
227        self.ledger_type
228    }
229
230    /// 获取层级深度
231    #[inline]
232    pub fn depth(&self) -> u8 {
233        self.ledger_type.depth()
234    }
235
236    /// 获取总配额
237    #[inline]
238    pub fn total(&self) -> &LedgerQuota {
239        &self.total
240    }
241
242    /// 获取已使用配额
243    #[inline]
244    pub fn used(&self) -> &LedgerQuota {
245        &self.used
246    }
247
248    /// 获取已分配给子账本的配额
249    #[inline]
250    pub fn allocated_to_children(&self) -> &LedgerQuota {
251        &self.allocated_to_children
252    }
253
254    /// 获取子账本数量
255    #[inline]
256    pub fn child_count(&self) -> usize {
257        self.children.len()
258    }
259
260    /// 迭代子账本名称
261    pub fn child_names(&self) -> impl Iterator<Item = &str> {
262        self.children.keys().map(|s| s.as_str())
263    }
264
265    /// 查找子账本(不可变)
266    pub fn get_child(&self, name: &str) -> Option<&ResourceLedger> {
267        self.children.get(name)
268    }
269
270    /// 查找子账本(可变)
271    pub fn get_child_mut(&mut self, name: &str) -> Option<&mut ResourceLedger> {
272        self.children.get_mut(name)
273    }
274
275    /// 在当前账本下创建子账本
276    ///
277    /// 父账本校验(fail-closed,逐项资源):
278    /// `allocated_to_children + child_quota <= total - used`
279    ///
280    /// 即「已承诺给现有子账本的配额 + 新子账本配额」不得超过父账本
281    /// 扣除自身已用后的剩余配额,防止多子账本配额之和超卖父账本。
282    ///
283    /// # 失败
284    /// - 配额不足(含兄弟账本已占用部分)→ CoreError::QuotaExceeded
285    /// - 子账本名称已存在 → CoreError::ResourceAlreadyExists
286    /// - 子账本全零配额 → CoreError::InvalidConfig(静默失败防护)
287    pub fn create_child(
288        &mut self,
289        name: impl Into<String>,
290        ledger_type: LedgerType,
291        quota: LedgerQuota,
292    ) -> CoreResult<&mut ResourceLedger> {
293        let name: String = name.into();
294
295        // 零配额防护:三项全零的子账本无任何可用资源,必然导致后续 allocate 静默失败
296        if quota.memory_bytes == 0
297            && quota.frame_count == 0
298            && quota.connection_count == 0
299        {
300            return Err(CoreError::invalid_config(
301                "LedgerQuota",
302                "all quota fields are zero; a child ledger with zero total quota can never allocate any resource",
303            ));
304        }
305
306        if self.children.contains_key(&name) {
307            return Err(CoreError::resource_already_exists(
308                0,
309                "ledger",
310            ));
311        }
312
313        // 超卖防护(checked 算术,逐项资源):
314        // allocated_to_children + child_quota <= total - used
315        for resource in [ResourceType::Memory, ResourceType::Frames, ResourceType::Connections] {
316            let committed = self
317                .allocated_to_children
318                .get(resource)
319                .checked_add(quota.get(resource))
320                .ok_or_else(|| {
321                    CoreError::arithmetic_overflow(
322                        "add",
323                        self.allocated_to_children.get(resource),
324                        quota.get(resource),
325                    )
326                })?;
327            // used <= total 由 allocate 的校验保证;checked_sub 兜底不变量损坏场景
328            let available = self
329                .total
330                .get(resource)
331                .checked_sub(self.used.get(resource))
332                .ok_or_else(|| {
333                    CoreError::arithmetic_overflow(
334                        "sub",
335                        self.total.get(resource),
336                        self.used.get(resource),
337                    )
338                })?;
339            if committed > available {
340                return Err(CoreError::quota_exceeded(
341                    resource.name(),
342                    available,
343                    committed,
344                ));
345            }
346        }
347
348        // 累加子账本配额;溢出错误必须上报真实操作数(allocated_to_children
349        // 当前值),禁止硬编码 u64::MAX 谎报现场,便于事后审计定位
350        self.allocated_to_children.memory_bytes = self
351            .allocated_to_children
352            .memory_bytes
353            .checked_add(quota.memory_bytes)
354            .ok_or_else(|| {
355                CoreError::arithmetic_overflow(
356                    "add",
357                    self.allocated_to_children.memory_bytes,
358                    quota.memory_bytes,
359                )
360            })?;
361        self.allocated_to_children.frame_count = self
362            .allocated_to_children
363            .frame_count
364            .checked_add(quota.frame_count)
365            .ok_or_else(|| {
366                CoreError::arithmetic_overflow(
367                    "add",
368                    self.allocated_to_children.frame_count,
369                    quota.frame_count,
370                )
371            })?;
372        self.allocated_to_children.connection_count = self
373            .allocated_to_children
374            .connection_count
375            .checked_add(quota.connection_count)
376            .ok_or_else(|| {
377                CoreError::arithmetic_overflow(
378                    "add",
379                    self.allocated_to_children.connection_count,
380                    quota.connection_count,
381                )
382            })?;
383
384        let child = ResourceLedger::new(name.clone(), ledger_type, quota);
385        self.children.insert(name.clone(), child);
386
387        self.children.get_mut(&name).ok_or_else(|| CoreError::internal("child ledger not found after insertion"))
388    }
389
390    /// 注销子账本(回收其配额)
391    ///
392    /// 子账本的 `total` 配额会从父账本的 `allocated_to_children` 中扣除。
393    /// 同时回滚子树曾向父账本 roll-up 的使用量(`used`):
394    /// 子树被移除后其占用的资源视为全部释放,父账本不再为其记账。
395    /// 回滚采用饱和语义——仅回收父账本实际记账的部分
396    /// (子树未经 `allocate_in_child` roll-up 的使用本就不在父账本账上)。
397    pub fn remove_child(&mut self, name: &str) -> CoreResult<()> {
398        let child = self
399            .children
400            .remove(name)
401            .ok_or_else(|| CoreError::resource_not_found(0, "ledger"))?;
402
403        // 回滚子树 roll-up 到本账本的使用量(checked_sub 防 underflow:
404        // 子树 roll-up 用量必然 ≤ 本账本记账,underflow 说明内部状态损坏,fail-closed 上报)
405        // used_direct 不受影响:子账本不占父账本直接用量。
406        self.used.memory_bytes = self
407            .used
408            .memory_bytes
409            .checked_sub(child.used.memory_bytes)
410            .ok_or_else(|| {
411                CoreError::arithmetic_overflow(
412                    "sub",
413                    self.used.memory_bytes,
414                    child.used.memory_bytes,
415                )
416            })?;
417        self.used.frame_count = self
418            .used
419            .frame_count
420            .checked_sub(child.used.frame_count)
421            .ok_or_else(|| {
422                CoreError::arithmetic_overflow(
423                    "sub",
424                    self.used.frame_count,
425                    child.used.frame_count,
426                )
427            })?;
428        self.used.connection_count = self
429            .used
430            .connection_count
431            .checked_sub(child.used.connection_count)
432            .ok_or_else(|| {
433                CoreError::arithmetic_overflow(
434                    "sub",
435                    self.used.connection_count,
436                    child.used.connection_count,
437                )
438            })?;
439
440        // 回滚已分配给子账本的配额
441        self.allocated_to_children.memory_bytes = self
442            .allocated_to_children
443            .memory_bytes
444            .checked_sub(child.total.memory_bytes)
445            .ok_or_else(|| {
446                CoreError::arithmetic_overflow(
447                    "sub",
448                    self.allocated_to_children.memory_bytes,
449                    child.total.memory_bytes,
450                )
451            })?;
452        self.allocated_to_children.frame_count = self
453            .allocated_to_children
454            .frame_count
455            .checked_sub(child.total.frame_count)
456            .ok_or_else(|| {
457                CoreError::arithmetic_overflow(
458                    "sub",
459                    self.allocated_to_children.frame_count,
460                    child.total.frame_count,
461                )
462            })?;
463        self.allocated_to_children.connection_count = self
464            .allocated_to_children
465            .connection_count
466            .checked_sub(child.total.connection_count)
467            .ok_or_else(|| {
468                CoreError::arithmetic_overflow(
469                    "sub",
470                    self.allocated_to_children.connection_count,
471                    child.total.connection_count,
472                )
473            })?;
474
475        Ok(())
476    }
477
478    /// 在当前账本上分配资源(checked arithmetic)
479    ///
480    /// # 超卖防护
481    /// 本账本自身的直接用量(`used_direct`)+ 已承诺给子账本的配额不得超过 `total`。
482    /// 否则父账本可直接分配耗尽 `total`,与子账本承诺配额叠加造成
483    /// "子账本配额 + 父自身用量 > total" 的分层配额超卖。
484    ///
485    /// # 双重计数修复(CORE-013)
486    /// 传统实现用 `used`(子树汇总)参与分配校验,会把子账本实际 roll-up 用量
487    /// 与 `allocated_to_children` 配额重复计算,导致子账本用量 < 其配额时父账本
488    /// 合法分配被误拒。修复后校验仅依赖 `used_direct`(本账本自身直接用量),
489    /// 与子账本已承诺配额做加法,不再叠加子账本实际用量。
490    pub fn allocate(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
491        let direct = self.used_direct.get(resource);
492        let total = self.total.get(resource);
493        let new_direct = direct
494            .checked_add(amount)
495            .ok_or_else(|| CoreError::arithmetic_overflow("add", direct, amount))?;
496
497        // 已承诺给子账本的配额需从父账本可分配预算中预留
498        let committed = self.allocated_to_children.get(resource);
499        let total_need = new_direct
500            .checked_add(committed)
501            .ok_or_else(|| CoreError::arithmetic_overflow("add", new_direct, committed))?;
502        if total_need > total {
503            return Err(CoreError::quota_exceeded(resource.name(), total, total_need));
504        }
505
506        // 双轨记账:直接用量计入 used_direct,同时计入 used(子树汇总)
507        self.used_direct.restore(resource, amount)?;
508        self.used.restore(resource, amount)?;
509        Ok(())
510    }
511
512    /// 祖先 roll-up 专用分配(**不**检查 `allocated_to_children`)。
513    ///
514    /// 子账本的实际用量已由其自身配额约束(该配额已计入父账本
515    /// `allocated_to_children`),故 roll-up 仅需保证不超过 `total`。
516    /// 直接分配一律走 [`allocate`](Self::allocate)(含超卖防护)。
517    fn allocate_rollup(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
518        let current = self.used.get(resource);
519        let total = self.total.get(resource);
520        let new_used = current
521            .checked_add(amount)
522            .ok_or_else(|| CoreError::arithmetic_overflow("add", current, amount))?;
523        if new_used > total {
524            return Err(CoreError::quota_exceeded(resource.name(), total, new_used));
525        }
526        self.used.restore(resource, amount)?;
527        Ok(())
528    }
529
530    /// 在当前账本上释放资源(直接释放,与 [`allocate`](Self::allocate) 互逆)
531    ///
532    /// 双轨回滚:`used_direct`(本账本直接用量)与 `used`(子树汇总)同时扣减。
533    /// 祖先 roll-up 回滚请使用 [`release_rollup`](Self::release_rollup),勿在本方法误用。
534    pub fn release(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
535        self.used_direct.consume(resource, amount)?;
536        self.used.consume(resource, amount)?;
537        Ok(())
538    }
539
540    /// 祖先 roll-up 回滚专用释放(**不**更新 `used_direct`)。
541    ///
542    /// 与 [`allocate_rollup`](Self::allocate_rollup) 互逆:roll-up 只增加 `used`
543    /// (子树汇总),因此回滚只扣减 `used`,避免误扣未发生的父账本直接用量。
544    fn release_rollup(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
545        self.used.consume(resource, amount)?;
546        Ok(())
547    }
548
549    /// 在指定子账本(按名称路径逐层下钻)上分配资源,并沿路径向所有祖先 roll-up
550    ///
551    /// 分层配额语义:子孙的实际使用同时消耗路径上每一层祖先的配额,
552    /// 因此祖先账本的 `used` 始终反映其整棵子树的使用量。
553    ///
554    /// # 原子性
555    /// 路径上任一层配额不足或路径不存在时,已完成的祖先 roll-up 会逐层回滚,
556    /// 调用前后账本树保持一致(fail-closed,不产生部分提交)。
557    ///
558    /// # Arguments
559    /// * `child_path` - 子账本名称路径(空路径等价于 `self.allocate`)
560    /// * `resource` - 资源类型
561    /// * `amount` - 分配数量
562    ///
563    /// # 失败
564    /// - 路径不存在 → CoreError::ResourceNotFound
565    /// - 任一层配额不足 → CoreError::QuotaExceeded
566    pub fn allocate_in_child(
567        &mut self,
568        child_path: &[&str],
569        resource: ResourceType,
570        amount: u64,
571    ) -> CoreResult<()> {
572        match child_path.split_first() {
573            None => self.allocate(resource, amount),
574            Some((head, rest)) => {
575                // 子账本存在性预检(fail-closed:避免祖先 roll-up 后才发现路径不存在)
576                if !self.children.contains_key(*head) {
577                    return Err(CoreError::resource_not_found(0, "ledger"));
578                }
579                // 祖先层先 roll-up:祖先配额不足直接失败,子树零变更。
580                // 用 allocate_rollup(不检查 allocated_to_children),
581                // 避免与子账本已承诺配额重复计数导致合法分配被误拒。
582                self.allocate_rollup(resource, amount)?;
583                let child = self
584                    .children
585                    .get_mut(*head)
586                    .ok_or_else(|| CoreError::internal("child ledger not found after existence check"))?;
587                match child.allocate_in_child(rest, resource, amount) {
588                    Ok(()) => Ok(()),
589                    Err(e) => {
590                        // 子树分配失败 → 回滚祖先层 roll-up,保持账本树守恒
591                        // (不变量:刚加上的 amount 必然能减回;回滚失败说明内部状态损坏,优先上报)
592                        // 祖先层 roll-up 只增加过 used,故用 release_rollup 只扣 used,
593                        // 避免误扣 used_direct
594                        self.release_rollup(resource, amount)?;
595                        Err(e)
596                    }
597                }
598            }
599        }
600    }
601
602    /// 在指定子账本(按名称路径逐层下钻)上释放资源,并沿路径回滚所有祖先的 roll-up
603    ///
604    /// 与 [`ResourceLedger::allocate_in_child`] 互逆:释放时逐层扣减路径上
605    /// 每个账本的 `used`,保证祖先账本记账与子树实际使用一致。
606    ///
607    /// # 原子性
608    /// 路径不存在或任一层 `used` 不足时,已回滚的祖先层会逐层恢复,
609    /// 调用前后账本树保持一致(fail-closed,不产生部分提交)。
610    ///
611    /// # 失败
612    /// - 路径不存在 → CoreError::ResourceNotFound
613    /// - 任一层 `used` 不足(释放量超过已分配量)→ CoreError::ArithmeticOverflow
614    pub fn release_in_child(
615        &mut self,
616        child_path: &[&str],
617        resource: ResourceType,
618        amount: u64,
619    ) -> CoreResult<()> {
620        match child_path.split_first() {
621            None => self.release(resource, amount),
622            Some((head, rest)) => {
623                // 子账本存在性预检(fail-closed)
624                if !self.children.contains_key(*head) {
625                    return Err(CoreError::resource_not_found(0, "ledger"));
626                }
627                // 祖先层先回滚(used 不足直接失败,子树零变更)
628                // 祖先层用量由 roll-up 计入 `used`,不占 `used_direct`,故用 release_rollup
629                self.release_rollup(resource, amount)?;
630                let child = self
631                    .children
632                    .get_mut(*head)
633                    .ok_or_else(|| CoreError::internal("child ledger not found after existence check"))?;
634                match child.release_in_child(rest, resource, amount) {
635                    Ok(()) => Ok(()),
636                    Err(e) => {
637                        // 子树释放失败 → 恢复祖先层记账,保持账本树守恒
638                        // (不变量:刚减去的 amount 加回后等于原值,必然不溢出)
639                        self.used.restore(resource, amount)?;
640                        Err(e)
641                    }
642                }
643            }
644        }
645    }
646
647    /// 检查是否可以分配指定资源
648    ///
649    /// 与 [`allocate`](Self::allocate) 的校验一致:`used_direct + amount + allocated_to_children <= total`。
650    /// 使用 checked_add 判断,加法溢出(不可容纳于 u64)时返回 false(fail-closed,CORE-012)。
651    #[inline]
652    pub fn can_allocate(&self, resource: ResourceType, amount: u64) -> bool {
653        let direct = self.used_direct.get(resource);
654        let total = self.total.get(resource);
655        let committed = self.allocated_to_children.get(resource);
656        match direct
657            .checked_add(amount)
658            .and_then(|v| v.checked_add(committed))
659        {
660            Some(need) => need <= total,
661            // 溢出 → 必然超限,fail-closed 返回 false
662            None => false,
663        }
664    }
665
666    /// 计算剩余配额
667    pub fn remaining(&self) -> LedgerQuota {
668        self.total.remaining(&self.used)
669    }
670
671    /// 递归深度(用于调试)
672    pub fn max_depth(&self) -> u8 {
673        let mut max = self.depth();
674        for child in self.children.values() {
675            max = max.max(child.max_depth());
676        }
677        max
678    }
679
680    /// 账本树节点总数(用于完整性检查)
681    pub fn node_count(&self) -> u64 {
682        // 饱和累加:BTreeMap 子节点数为 usize,四层分层结构下
683        // u64 求和实际不可达溢出;saturating_add 仅为满足
684        // 「生产路径禁止裸算术」的兜底防御
685        self.children
686            .values()
687            .map(ResourceLedger::node_count)
688            .fold(1u64, u64::saturating_add)
689    }
690}
691
692#[cfg(test)]
693mod tests {
694    use super::*;
695
696    fn make_quota(mem: u64, frames: u64, conns: u64) -> LedgerQuota {
697        LedgerQuota {
698            memory_bytes: mem,
699            frame_count: frames,
700            connection_count: conns,
701        }
702    }
703
704    #[test]
705    fn test_root_ledger_create() {
706        let root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
707        assert_eq!(root.name(), "root");
708        assert_eq!(root.ledger_type(), LedgerType::Root);
709        assert_eq!(root.depth(), 0);
710        assert_eq!(root.total().memory_bytes, 1 << 30);
711        assert_eq!(root.used().memory_bytes, 0);
712        assert_eq!(root.child_count(), 0);
713        assert_eq!(root.node_count(), 1);
714    }
715
716    #[test]
717    fn test_create_domain_child() {
718        let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
719
720        root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
721            .unwrap();
722
723        assert_eq!(root.child_count(), 1);
724        assert!(root.get_child("domain_0").is_some());
725
726        let domain = root.get_child("domain_0").unwrap();
727        assert_eq!(domain.ledger_type(), LedgerType::Domain);
728        assert_eq!(domain.depth(), 1);
729        assert_eq!(domain.total().frame_count, 10_000);
730        assert_eq!(root.node_count(), 2);
731    }
732
733    #[test]
734    fn test_create_child_quota_exceeded() {
735        let mut root = ResourceLedger::root(make_quota(1024, 100, 10));
736
737        // 尝试创建超出父账本配额的子账本
738        let result = root.create_child("big", LedgerType::Domain, make_quota(2048, 0, 0));
739        assert!(result.is_err());
740
741        // 父账本不受影响
742        assert_eq!(root.child_count(), 0);
743        assert_eq!(root.total().memory_bytes, 1024);
744    }
745
746    #[test]
747    fn test_duplicate_child_name() {
748        let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
749
750        root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
751            .unwrap();
752
753        let result = root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000));
754        assert!(result.is_err());
755    }
756
757    #[test]
758    fn test_allocate_and_release() {
759        let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(4096, 100, 10));
760
761        // 分配内存
762        ledger.allocate(ResourceType::Memory, 1024).unwrap();
763        assert_eq!(ledger.used().memory_bytes, 1024);
764
765        // 归还
766        ledger.release(ResourceType::Memory, 512).unwrap();
767        assert_eq!(ledger.used().memory_bytes, 512);
768
769        // 剩余配额
770        let remaining = ledger.remaining();
771        assert_eq!(remaining.memory_bytes, 4096 - 512);
772    }
773
774    #[test]
775    fn test_allocate_quota_exceeded() {
776        let mut ledger = ResourceLedger::new("small", LedgerType::Queue, make_quota(1024, 0, 0));
777
778        ledger.allocate(ResourceType::Memory, 512).unwrap();
779
780        let result = ledger.allocate(ResourceType::Memory, 1024);
781        assert!(result.is_err());
782    }
783
784    #[test]
785    fn test_allocate_overflow() {
786        let mut ledger = ResourceLedger::new("max", LedgerType::Queue, make_quota(u64::MAX, 0, 0));
787
788        ledger.allocate(ResourceType::Memory, u64::MAX).unwrap();
789
790        let result = ledger.allocate(ResourceType::Memory, 1);
791        assert!(result.is_err());
792    }
793
794    #[test]
795    fn test_frames_and_connections() {
796        let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(0, 1000, 50));
797
798        ledger.allocate(ResourceType::Frames, 500).unwrap();
799        assert_eq!(ledger.used().frame_count, 500);
800
801        ledger.allocate(ResourceType::Connections, 10).unwrap();
802        assert_eq!(ledger.used().connection_count, 10);
803
804        ledger.release(ResourceType::Frames, 200).unwrap();
805        assert_eq!(ledger.used().frame_count, 300);
806    }
807
808    #[test]
809    fn test_can_allocate() {
810        let ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(1 << 30, 0, 0));
811
812        assert!(ledger.can_allocate(ResourceType::Memory, 1024));
813        assert!(ledger.can_allocate(ResourceType::Memory, 1 << 30));
814        assert!(!ledger.can_allocate(ResourceType::Memory, (1 << 30) + 1));
815    }
816
817    #[test]
818    fn test_nested_hierarchy() {
819        let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
820
821        // 域
822        root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 24, 50_000, 5_000))
823            .unwrap();
824
825        // 在域下创建队列
826        root.get_child_mut("domain_0").unwrap()
827            .create_child("queue_0", LedgerType::Queue, make_quota(1 << 20, 10_000, 1_000))
828            .unwrap();
829
830        // 在队列下创建连接
831        root.get_child_mut("domain_0").unwrap()
832            .get_child_mut("queue_0").unwrap()
833            .create_child("conn_0", LedgerType::Connection, make_quota(1 << 16, 100, 10))
834            .unwrap();
835
836        // 验证层级深度
837        let conn = root.get_child("domain_0").unwrap()
838            .get_child("queue_0").unwrap()
839            .get_child("conn_0").unwrap();
840        assert_eq!(conn.depth(), 3);
841        assert_eq!(conn.ledger_type(), LedgerType::Connection);
842        assert_eq!(root.max_depth(), 3);
843        assert_eq!(root.node_count(), 4);
844
845        // 在连接上分配资源(需要可变访问)
846        root.get_child_mut("domain_0").unwrap()
847            .get_child_mut("queue_0").unwrap()
848            .get_child_mut("conn_0").unwrap()
849            .allocate(ResourceType::Memory, 1024)
850            .unwrap();
851
852        assert_eq!(
853            root.get_child("domain_0").unwrap()
854                .get_child("queue_0").unwrap()
855                .get_child("conn_0").unwrap()
856                .used()
857                .memory_bytes,
858            1024
859        );
860    }
861
862    #[test]
863    fn test_remove_child_reclaims_quota() {
864        let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
865
866        root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
867            .unwrap();
868
869        // 在域下创建队列并分配资源(域自身的 used 被增加)
870        root.get_child_mut("domain_0").unwrap()
871            .create_child("queue_0", LedgerType::Queue, make_quota(1 << 16, 1000, 100))
872            .unwrap();
873
874        root.get_child_mut("domain_0").unwrap()
875            .get_child_mut("queue_0").unwrap()
876            .allocate(ResourceType::Memory, 4096)
877            .unwrap();
878
879        // 验证子账本的 used 正确追踪
880        let queue_used = root.get_child("domain_0").unwrap()
881            .get_child("queue_0").unwrap()
882            .used().memory_bytes;
883        assert_eq!(queue_used, 4096);
884
885        // 父账本的 used = 所有子孙的 used 之和,在 remove_child 时统一回收
886        // 回收域的 total 配额
887        root.remove_child("domain_0").unwrap();
888
889        assert_eq!(root.child_count(), 0);
890        // allocated_to_children 已清零(域的 total 已回收)
891        assert_eq!(root.allocated_to_children().memory_bytes, 0);
892    }
893
894    #[test]
895    fn test_remove_nonexistent_child() {
896        let mut root = ResourceLedger::root(make_quota(1 << 30, 0, 0));
897        let result = root.remove_child("nonexistent");
898        assert!(result.is_err());
899    }
900
901    #[test]
902    fn test_list_child_names() {
903        let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
904
905        root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
906            .unwrap();
907        root.create_child("domain_1", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
908            .unwrap();
909
910        let names: Vec<&str> = root.child_names().collect();
911        assert_eq!(names.len(), 2);
912        assert!(names.contains(&"domain_0"));
913        assert!(names.contains(&"domain_1"));
914    }
915
916    #[test]
917    fn test_allocations_accounted_in_parent() {
918        let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
919
920        root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
921            .unwrap();
922
923        // 子账本消耗资源
924        root.get_child_mut("domain_0").unwrap()
925            .allocate(ResourceType::Memory, 8192)
926            .unwrap();
927
928        // 父账本看到子账本的使用
929        assert_eq!(root.get_child("domain_0").unwrap().used().memory_bytes, 8192);
930
931        // 但子账本自身的 used 也被正确跟踪
932        let domain = root.get_child("domain_0").unwrap();
933        assert!(domain.can_allocate(ResourceType::Memory, (1 << 20) - 8192));
934        assert!(!domain.can_allocate(ResourceType::Memory, (1 << 20) - 8191));
935    }
936
937    // ===== LedgerQuota: zero()/unlimited()/can_allocate 测试 =====
938
939    #[test]
940    fn test_ledger_quota_zero() {
941        let q = LedgerQuota::zero();
942        assert_eq!(q.memory_bytes, 0);
943        assert_eq!(q.frame_count, 0);
944        assert_eq!(q.connection_count, 0);
945    }
946
947    #[test]
948    fn test_ledger_quota_unlimited() {
949        let q = LedgerQuota::unlimited();
950        assert_eq!(q.memory_bytes, u64::MAX);
951        assert_eq!(q.frame_count, u64::MAX);
952        assert_eq!(q.connection_count, u64::MAX);
953    }
954
955    #[test]
956    fn test_ledger_quota_can_allocate_memory() {
957        let parent = make_quota(1024, 0, 0);
958        let child_ok = make_quota(512, 0, 0);
959        let child_exceed = make_quota(2048, 0, 0);
960
961        assert!(parent.can_allocate(&child_ok).is_ok());
962        assert!(parent.can_allocate(&child_exceed).is_err());
963    }
964
965    #[test]
966    fn test_ledger_quota_can_allocate_frames() {
967        let parent = make_quota(0, 100, 0);
968        let child_ok = make_quota(0, 50, 0);
969        let child_exceed = make_quota(0, 200, 0);
970
971        assert!(parent.can_allocate(&child_ok).is_ok());
972        assert!(parent.can_allocate(&child_exceed).is_err());
973    }
974
975    #[test]
976    fn test_ledger_quota_can_allocate_connections() {
977        let parent = make_quota(0, 0, 10);
978        let child_ok = make_quota(0, 0, 5);
979        let child_exceed = make_quota(0, 0, 20);
980
981        assert!(parent.can_allocate(&child_ok).is_ok());
982        assert!(parent.can_allocate(&child_exceed).is_err());
983    }
984
985    #[test]
986    fn test_ledger_quota_can_allocate_exact() {
987        let parent = make_quota(100, 200, 300);
988        let child_exact = make_quota(100, 200, 300);
989        assert!(parent.can_allocate(&child_exact).is_ok());
990    }
991
992    #[test]
993    fn test_ledger_quota_consume_and_restore() {
994        let mut q = make_quota(1000, 100, 10);
995
996        q.consume(ResourceType::Memory, 500).unwrap();
997        assert_eq!(q.memory_bytes, 500);
998
999        q.restore(ResourceType::Memory, 300).unwrap();
1000        assert_eq!(q.memory_bytes, 800);
1001    }
1002
1003    #[test]
1004    fn test_ledger_quota_consume_underflow() {
1005        let mut q = make_quota(100, 0, 0);
1006        let result = q.consume(ResourceType::Memory, 200);
1007        assert!(result.is_err());
1008    }
1009
1010    #[test]
1011    fn test_ledger_quota_restore_overflow() {
1012        let mut q = make_quota(u64::MAX, 0, 0);
1013        let result = q.restore(ResourceType::Memory, 1);
1014        assert!(result.is_err());
1015    }
1016
1017    #[test]
1018    fn test_ledger_quota_get() {
1019        let q = make_quota(10, 20, 30);
1020        assert_eq!(q.get(ResourceType::Memory), 10);
1021        assert_eq!(q.get(ResourceType::Frames), 20);
1022        assert_eq!(q.get(ResourceType::Connections), 30);
1023    }
1024
1025    #[test]
1026    fn test_ledger_quota_remaining() {
1027        let total = make_quota(1000, 100, 50);
1028        let used = make_quota(400, 30, 10);
1029        let remaining = total.remaining(&used);
1030        assert_eq!(remaining.memory_bytes, 600);
1031        assert_eq!(remaining.frame_count, 70);
1032        assert_eq!(remaining.connection_count, 40);
1033    }
1034
1035    #[test]
1036    fn test_ledger_quota_remaining_saturating() {
1037        let total = make_quota(100, 0, 0);
1038        let used = make_quota(200, 0, 0);
1039        let remaining = total.remaining(&used);
1040        assert_eq!(remaining.memory_bytes, 0);
1041    }
1042
1043    // ===== LedgerType: depth() 所有变体测试 =====
1044
1045    #[test]
1046    fn test_ledger_type_depth() {
1047        assert_eq!(LedgerType::Root.depth(), 0);
1048        assert_eq!(LedgerType::Domain.depth(), 1);
1049        assert_eq!(LedgerType::Queue.depth(), 2);
1050        assert_eq!(LedgerType::Connection.depth(), 3);
1051    }
1052
1053    #[test]
1054    fn test_ledger_type_equality() {
1055        assert_eq!(LedgerType::Root, LedgerType::Root);
1056        assert_ne!(LedgerType::Root, LedgerType::Domain);
1057    }
1058
1059    #[test]
1060    fn test_ledger_type_debug() {
1061        assert_eq!(format!("{:?}", LedgerType::Root), "Root");
1062        assert_eq!(format!("{:?}", LedgerType::Connection), "Connection");
1063    }
1064
1065    // ===== ResourceType: name() 所有变体测试 =====
1066
1067    #[test]
1068    fn test_resource_type_name() {
1069        assert_eq!(ResourceType::Memory.name(), "memory");
1070        assert_eq!(ResourceType::Frames.name(), "frames");
1071        assert_eq!(ResourceType::Connections.name(), "connections");
1072    }
1073
1074    #[test]
1075    fn test_resource_type_equality() {
1076        assert_eq!(ResourceType::Memory, ResourceType::Memory);
1077        assert_ne!(ResourceType::Memory, ResourceType::Frames);
1078    }
1079
1080    #[test]
1081    fn test_resource_type_debug() {
1082        assert_eq!(format!("{:?}", ResourceType::Memory), "Memory");
1083        assert_eq!(format!("{:?}", ResourceType::Frames), "Frames");
1084    }
1085
1086    // ===== ResourceLedger: 多层嵌套配额守恒检查 =====
1087
1088    #[test]
1089    fn test_multiple_children_quota_sum() {
1090        let mut root = ResourceLedger::root(make_quota(10000, 1000, 100));
1091
1092        root.create_child("d0", LedgerType::Domain, make_quota(3000, 300, 30)).unwrap();
1093        root.create_child("d1", LedgerType::Domain, make_quota(2000, 200, 20)).unwrap();
1094        root.create_child("d2", LedgerType::Domain, make_quota(5000, 500, 50)).unwrap();
1095
1096        let allocated = root.allocated_to_children();
1097        assert_eq!(allocated.memory_bytes, 10000);
1098        assert_eq!(allocated.frame_count, 1000);
1099        assert_eq!(allocated.connection_count, 100);
1100    }
1101
1102    #[test]
1103    fn test_nested_three_level_quota_conservation() {
1104        let mut root = ResourceLedger::root(make_quota(10000, 1000, 100));
1105
1106        root.create_child("d0", LedgerType::Domain, make_quota(5000, 500, 50)).unwrap();
1107
1108        root.get_child_mut("d0").unwrap()
1109            .create_child("q0", LedgerType::Queue, make_quota(2000, 200, 20)).unwrap();
1110        root.get_child_mut("d0").unwrap()
1111            .create_child("q1", LedgerType::Queue, make_quota(3000, 300, 30)).unwrap();
1112
1113        let domain_allocated = root.get_child("d0").unwrap().allocated_to_children();
1114        assert_eq!(domain_allocated.memory_bytes, 5000);
1115        assert_eq!(domain_allocated.frame_count, 500);
1116        assert_eq!(domain_allocated.connection_count, 50);
1117
1118        let root_allocated = root.allocated_to_children();
1119        assert_eq!(root_allocated.memory_bytes, 5000);
1120    }
1121
1122    #[test]
1123    fn test_child_quota_exceeds_parent_total() {
1124        let mut root = ResourceLedger::root(make_quota(1000, 100, 10));
1125
1126        let result = root.create_child("big", LedgerType::Domain, make_quota(2000, 200, 20));
1127        assert!(result.is_err());
1128
1129        assert_eq!(root.child_count(), 0);
1130        assert_eq!(root.allocated_to_children().memory_bytes, 0);
1131    }
1132
1133    #[test]
1134    fn test_child_quota_exceeds_parent_used_remaining() {
1135        let mut root = ResourceLedger::root(make_quota(1000, 100, 10));
1136
1137        root.allocate(ResourceType::Memory, 600).unwrap();
1138        root.allocate(ResourceType::Frames, 60).unwrap();
1139        root.allocate(ResourceType::Connections, 6).unwrap();
1140
1141        let result = root.create_child("d0", LedgerType::Domain, make_quota(500, 50, 5));
1142        assert!(result.is_err());
1143
1144        assert_eq!(root.child_count(), 0);
1145    }
1146
1147    // ===== release 导致 underflow 的 checked arithmetic =====
1148
1149    #[test]
1150    fn test_release_underflow_memory() {
1151        let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(1000, 0, 0));
1152        ledger.allocate(ResourceType::Memory, 500).unwrap();
1153        ledger.release(ResourceType::Memory, 300).unwrap();
1154        assert_eq!(ledger.used().memory_bytes, 200);
1155    }
1156
1157    #[test]
1158    fn test_release_underflow_frames() {
1159        let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(0, 100, 0));
1160        ledger.allocate(ResourceType::Frames, 50).unwrap();
1161        let result = ledger.release(ResourceType::Frames, 100);
1162        assert!(result.is_err());
1163    }
1164
1165    #[test]
1166    fn test_release_underflow_connections() {
1167        let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(0, 0, 10));
1168        let result = ledger.release(ResourceType::Connections, 1);
1169        assert!(result.is_err());
1170    }
1171
1172    // ===== allocated_to_children 的正确性校验 =====
1173
1174    #[test]
1175    fn test_allocated_to_children_after_remove() {
1176        let mut root = ResourceLedger::root(make_quota(10000, 1000, 100));
1177
1178        root.create_child("d0", LedgerType::Domain, make_quota(3000, 300, 30)).unwrap();
1179        root.create_child("d1", LedgerType::Domain, make_quota(2000, 200, 20)).unwrap();
1180
1181        assert_eq!(root.allocated_to_children().memory_bytes, 5000);
1182        assert_eq!(root.allocated_to_children().frame_count, 500);
1183        assert_eq!(root.allocated_to_children().connection_count, 50);
1184
1185        root.remove_child("d0").unwrap();
1186
1187        assert_eq!(root.allocated_to_children().memory_bytes, 2000);
1188        assert_eq!(root.allocated_to_children().frame_count, 200);
1189        assert_eq!(root.allocated_to_children().connection_count, 20);
1190    }
1191
1192    #[test]
1193    fn test_allocated_to_children_zero_initially() {
1194        let root = ResourceLedger::root(make_quota(1000, 100, 10));
1195        assert_eq!(root.allocated_to_children().memory_bytes, 0);
1196        assert_eq!(root.allocated_to_children().frame_count, 0);
1197        assert_eq!(root.allocated_to_children().connection_count, 0);
1198    }
1199
1200    // ===== LedgerQuota Default 实现 =====
1201
1202    #[test]
1203    fn test_ledger_quota_default() {
1204        let q = LedgerQuota::default();
1205        assert_eq!(q.memory_bytes, 0);
1206        assert_eq!(q.frame_count, 0);
1207        assert_eq!(q.connection_count, 0);
1208    }
1209
1210    // ===== ResourceLedger remaining =====
1211
1212    #[test]
1213    fn test_ledger_remaining() {
1214        let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(1000, 100, 10));
1215        ledger.allocate(ResourceType::Memory, 400).unwrap();
1216        ledger.allocate(ResourceType::Frames, 30).unwrap();
1217        ledger.allocate(ResourceType::Connections, 5).unwrap();
1218
1219        let remaining = ledger.remaining();
1220        assert_eq!(remaining.memory_bytes, 600);
1221        assert_eq!(remaining.frame_count, 70);
1222        assert_eq!(remaining.connection_count, 5);
1223    }
1224
1225    // ===== 深层嵌套 4 级完整链路 =====
1226
1227    #[test]
1228    fn test_four_level_hierarchy_allocated_to_children() {
1229        let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
1230
1231        root.create_child("d0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000)).unwrap();
1232
1233        root.get_child_mut("d0").unwrap()
1234            .create_child("q0", LedgerType::Queue, make_quota(1 << 15, 1_000, 100)).unwrap();
1235
1236        root.get_child_mut("d0").unwrap().get_child_mut("q0").unwrap()
1237            .create_child("c0", LedgerType::Connection, make_quota(1 << 10, 100, 10)).unwrap();
1238
1239        assert_eq!(root.max_depth(), 3);
1240        assert_eq!(root.node_count(), 4);
1241
1242        let conn = root.get_child("d0").unwrap().get_child("q0").unwrap().get_child("c0").unwrap();
1243        assert_eq!(conn.ledger_type(), LedgerType::Connection);
1244        assert_eq!(conn.depth(), 3);
1245    }
1246
1247    // ===== create_child 超卖防护(allocated_to_children 计入校验) =====
1248
1249    #[test]
1250    fn test_create_child_oversubscription_rejected() {
1251        let mut root = ResourceLedger::root(make_quota(1000, 100, 10));
1252
1253        // 第一个子账本占 600:通过
1254        root.create_child("c1", LedgerType::Domain, make_quota(600, 60, 6)).unwrap();
1255        // 第二个子账本再要 600:600 + 600 = 1200 > 1000,必须拒绝(旧实现会放过)
1256        let result = root.create_child("c2", LedgerType::Domain, make_quota(600, 0, 0));
1257        assert!(result.is_err());
1258        assert_eq!(root.child_count(), 1);
1259
1260        // 400 恰好分完:600 + 400 = 1000 <= 1000,通过
1261        root.create_child("c2", LedgerType::Domain, make_quota(400, 40, 4)).unwrap();
1262        assert_eq!(root.child_count(), 2);
1263        assert_eq!(root.allocated_to_children().memory_bytes, 1000);
1264
1265        // 配额已分完,任何非零子账本都必须拒绝
1266        assert!(root.create_child("c3", LedgerType::Domain, make_quota(1, 0, 0)).is_err());
1267    }
1268
1269    #[test]
1270    fn test_create_child_accounts_parent_used() {
1271        let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
1272
1273        // 父账本自身使用 400 后,子账本配额之和不得超过 600
1274        root.allocate(ResourceType::Memory, 400).unwrap();
1275        root.create_child("c1", LedgerType::Domain, make_quota(600, 0, 0)).unwrap();
1276        assert!(root.create_child("c2", LedgerType::Domain, make_quota(1, 0, 0)).is_err());
1277    }
1278
1279    // ===== allocate_in_child / release_in_child 分层 roll-up =====
1280
1281    #[test]
1282    fn test_allocate_in_child_rolls_up_to_ancestors() {
1283        let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
1284        root.create_child("d0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000)).unwrap();
1285        root.get_child_mut("d0").unwrap()
1286            .create_child("q0", LedgerType::Queue, make_quota(1 << 15, 1_000, 100)).unwrap();
1287
1288        // 在队列账本上分配 1024:路径上每层祖先都必须记账
1289        root.allocate_in_child(&["d0", "q0"], ResourceType::Memory, 1024).unwrap();
1290
1291        assert_eq!(root.used().memory_bytes, 1024);
1292        assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 1024);
1293        assert_eq!(
1294            root.get_child("d0").unwrap().get_child("q0").unwrap().used().memory_bytes,
1295            1024
1296        );
1297
1298        // 释放时逐层回滚
1299        root.release_in_child(&["d0", "q0"], ResourceType::Memory, 1024).unwrap();
1300        assert_eq!(root.used().memory_bytes, 0);
1301        assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 0);
1302        assert_eq!(
1303            root.get_child("d0").unwrap().get_child("q0").unwrap().used().memory_bytes,
1304            0
1305        );
1306    }
1307
1308    #[test]
1309    fn test_allocate_in_child_ancestor_quota_enforced() {
1310        let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
1311        root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
1312
1313        // 超卖防护:父账本直接分配受子账本已承诺配额约束。
1314        // 直接用 600 + 已承诺 500 = 1100 > total 1000 → 拒绝
1315        assert!(root.allocate(ResourceType::Memory, 600).is_err());
1316        // 直接用 500 + 已承诺 500 = 1000 <= total → 允许
1317        root.allocate(ResourceType::Memory, 500).unwrap();
1318
1319        // 子树分配:子账本自身配额 500 决定其上限;祖先 roll-up 在父 total 内成功
1320        root.allocate_in_child(&["d0"], ResourceType::Memory, 500).unwrap();
1321        assert_eq!(root.used().memory_bytes, 1000);
1322        assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 500);
1323
1324        // 超额分配:父 total 已占满(1000),子树再分配 100 → 祖先 roll-up 失败,原子回滚
1325        let result = root.allocate_in_child(&["d0"], ResourceType::Memory, 100);
1326        assert!(result.is_err());
1327        assert_eq!(root.used().memory_bytes, 1000);
1328        assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 500);
1329    }
1330
1331    #[test]
1332    fn test_allocate_in_child_rollback_on_descendant_failure() {
1333        let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
1334        root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
1335
1336        // 子账本配额只有 500,分配 600:子树失败,祖先 roll-up 必须回滚
1337        let result = root.allocate_in_child(&["d0"], ResourceType::Memory, 600);
1338        assert!(result.is_err());
1339        assert_eq!(root.used().memory_bytes, 0);
1340        assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 0);
1341    }
1342
1343    // ===== CORE-013 双重计数回归测试 =====
1344
1345    #[test]
1346    fn test_allocate_not_double_counted_with_child_usage() {
1347        let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
1348        root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
1349
1350        // 子账本实际使用 200(roll-up 到 root.used;root.used_direct 仍为 0)
1351        root.allocate_in_child(&["d0"], ResourceType::Memory, 200).unwrap();
1352        assert_eq!(root.used().memory_bytes, 200);
1353        assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 200);
1354
1355        // 修复前:used(200)+400+committed(500)=1100>1000 → 误拒(把子用量与配额重复计数)
1356        // 修复后:used_direct(0)+400+committed(500)=900<=1000 → 成功
1357        // 实际 root 自身 400 + d0 用 200 = 600 <= 1000,且 d0 配额余量 300
1358        root.allocate(ResourceType::Memory, 400).unwrap();
1359
1360        assert_eq!(root.used().memory_bytes, 600);
1361        assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 200);
1362        // 根账本剩余 = total - used = 1000 - 600 = 400
1363        assert_eq!(root.remaining().memory_bytes, 400);
1364    }
1365
1366    #[test]
1367    fn test_allocate_in_child_rollback_does_not_touch_used_direct() {
1368        let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
1369        root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
1370
1371        // 子账本分配失败(配额超限):祖先 roll-up 用 release_rollup 回滚,
1372        // 不得误扣父账本 used_direct
1373        root.allocate(ResourceType::Memory, 300).unwrap();
1374        let result = root.allocate_in_child(&["d0"], ResourceType::Memory, 600);
1375        assert!(result.is_err());
1376        // 父账本 used 回到仅含自身直接用量 300(回滚未越界、未误扣)
1377        assert_eq!(root.used().memory_bytes, 300);
1378    }
1379
1380    #[test]
1381    fn test_allocate_in_child_unknown_path() {
1382        let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
1383        root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
1384
1385        // 路径不存在:任何一层都不得变更
1386        assert!(root.allocate_in_child(&["nope"], ResourceType::Memory, 100).is_err());
1387        assert!(root.allocate_in_child(&["d0", "nope"], ResourceType::Memory, 100).is_err());
1388        assert_eq!(root.used().memory_bytes, 0);
1389        assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 0);
1390    }
1391
1392    #[test]
1393    fn test_release_in_child_restores_on_descendant_failure() {
1394        let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
1395        root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
1396
1397        root.allocate_in_child(&["d0"], ResourceType::Memory, 200).unwrap();
1398
1399        // 子树已分配 200,释放 300:子树 used 不足,祖先已回滚的 300 必须恢复
1400        let result = root.release_in_child(&["d0"], ResourceType::Memory, 300);
1401        assert!(result.is_err());
1402        assert_eq!(root.used().memory_bytes, 200);
1403        assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 200);
1404
1405        // 正常释放路径不受影响
1406        root.release_in_child(&["d0"], ResourceType::Memory, 200).unwrap();
1407        assert_eq!(root.used().memory_bytes, 0);
1408    }
1409
1410    #[test]
1411    fn test_remove_child_reclaims_rolled_up_usage() {
1412        let mut root = ResourceLedger::root(make_quota(1000, 100, 10));
1413        root.create_child("d0", LedgerType::Domain, make_quota(500, 50, 5)).unwrap();
1414
1415        // 子树通过 roll-up 使用 200:父账本记账 200
1416        root.allocate_in_child(&["d0"], ResourceType::Memory, 200).unwrap();
1417        assert_eq!(root.used().memory_bytes, 200);
1418
1419        // 移除子账本:配额与 roll-up 使用量都必须回收,父账本不得泄漏记账
1420        root.remove_child("d0").unwrap();
1421        assert_eq!(root.used().memory_bytes, 0);
1422        assert_eq!(root.allocated_to_children().memory_bytes, 0);
1423
1424        // 回收后配额可重新分配
1425        root.create_child("d1", LedgerType::Domain, make_quota(1000, 100, 10)).unwrap();
1426    }
1427}