rust: Borg Pattern(续)

rust 复制代码
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:博格模式  Borg Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/9 22:50 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : BorgPattern 
//!# File      : borg_audit.rs
//! ★ 契约审计:「句柄共享成功」不等于「共享正确」。
//!
//! ## 四条规则
//!
//! | 规则 | 核对什么 | 为什么必须单独核 |
//! |---|---|---|
//! | `HANDLE_COUNT_CONSERVED` | 句柄条数 == 期望 | 少一个说明有人没挂上,多一个说明有人重复挂 |
//! | `SINGLE_SHARED_STATE` | 所有句柄指向同一份状态 | 博格的定义;不成立就不是博格 |
//! | `HANDLES_ARE_DISTINCT` | 句柄两两不是同一个对象 | ★ 若这条不成立,那是**单例**不是博格 |
//! | `CONTENT_CONSISTENT` | 所有句柄读到相同内容指纹 | 状态共享了但内容被各自改坏时,只有这条例外能抓出来 |
//!
//! ## ⚠️ 恒真的检查没有价值
//!
//! `HANDLES_ARE_DISTINCT` 与 `SINGLE_SHARED_STATE` 必须**同时**核:
//! 只核前者,快照组(各自一份状态)也全部通过;
//! 只核后者,单例也通过。**两条合起来才是博格的定义。**
//!
//! 此外,审计自身也必须被审计:本工程在 `main.rs` 里构造一组
//! **已知应当失败**的句柄(混入快照句柄),并断言失败条数 ≥ 1。

use crate::borg::BorgHandle;

/// 一条审计规则的执行结果。
#[derive(Debug, Clone)]
pub struct BorgAuditRule {
    /// 规则编码。
    rule_code: &'static str,
    /// 期望(人能读)。
    expectation_text: String,
    /// 实测(人能读)。
    actual_text: String,
    /// 是否通过。
    passed: bool,
}

impl BorgAuditRule {
    /// 构造一条结果。
    pub fn new(rule_code: &'static str, expectation_text: String, actual_text: String, passed: bool) -> Self {
        BorgAuditRule { rule_code, expectation_text, actual_text, passed }
    }

    /// 规则编码。
    pub const fn rule_code(&self) -> &'static str {
        self.rule_code
    }

    /// 期望。
    pub fn expectation_text(&self) -> &str {
        &self.expectation_text
    }

    /// 实测。
    pub fn actual_text(&self) -> &str {
        &self.actual_text
    }

    /// 是否通过。
    pub const fn passed(&self) -> bool {
        self.passed
    }

    /// 结论文本(`通过` / `不通过`)。
    pub const fn verdict_text(&self) -> &'static str {
        if self.passed {
            "通过"
        } else {
            "不通过"
        }
    }
}

/// 一组审计结果。
#[derive(Debug, Clone)]
pub struct BorgAudit {
    /// 逐条规则的结果(**顺序固定**:与规则声明顺序同序)。
    rules: Vec<BorgAuditRule>,
}

impl BorgAudit {
    /// 构造。
    pub fn new(rules: Vec<BorgAuditRule>) -> Self {
        BorgAudit { rules }
    }

    /// 逐条结果。
    pub fn rules(&self) -> &[BorgAuditRule] {
        &self.rules
    }

    /// 通过条数。
    pub fn passed_total(&self) -> usize {
        self.rules.iter().filter(|rule| rule.passed()).count()
    }

    /// 不通过条数。
    pub fn failed_total(&self) -> usize {
        self.rules.iter().filter(|rule| !rule.passed()).count()
    }

    /// 是否全部通过。
    pub fn all_passed(&self) -> bool {
        self.failed_total() == 0
    }

    /// 不通过的规则编码(逗号分隔;全通过时为 `无`)。
    pub fn failed_codes_text(&self) -> String {
        let mut codes: Vec<&'static str> = Vec::new();
        for rule in self.rules.iter() {
            if !rule.passed() {
                codes.push(rule.rule_code());
            }
        }
        if codes.is_empty() {
            "无".to_string()
        } else {
            codes.join("+")
        }
    }
}

/// 对一组句柄执行四条规则。
///
/// - `handles`:待核的句柄(**引用切片**:句柄本身不可复制,报表持有的是引用);
/// - `expected_handle_total`:期望的句柄条数;
/// - `fingerprint_of`:从载荷取出**内容指纹**的函数(由调用方给出,
///   机制层不认识载荷,也就无从自己算指纹)。
pub fn audit_borg_handles<S: 'static>(
    handles: &[&BorgHandle<S>],
    expected_handle_total: usize,
    fingerprint_of: &dyn Fn(&S) -> String,
) -> BorgAudit {
    let mut rules: Vec<BorgAuditRule> = Vec::new();

    // 规则一:句柄条数守恒。
    rules.push(BorgAuditRule::new(
        "HANDLE_COUNT_CONSERVED",
        format!("{} 个", expected_handle_total),
        format!("{} 个", handles.len()),
        handles.len() == expected_handle_total,
    ));

    // 规则二:所有句柄指向同一份状态。
    let mut single_state: bool = true;
    for index in 1..handles.len() {
        if !handles[0].is_same_state(handles[index]) {
            single_state = false;
            break;
        }
    }
    rules.push(BorgAuditRule::new(
        "SINGLE_SHARED_STATE",
        "所有句柄指向同一份状态".to_string(),
        if single_state { "同一份".to_string() } else { "存在不同份".to_string() },
        single_state,
    ));

    // 规则三:句柄两两不是同一个对象(★ 这条不成立就是单例,不是博格)。
    let mut all_distinct: bool = true;
    for left in 0..handles.len() {
        for right in (left + 1)..handles.len() {
            if handles[left].is_same_handle(handles[right]) {
                all_distinct = false;
            }
        }
    }
    rules.push(BorgAuditRule::new(
        "HANDLES_ARE_DISTINCT",
        "句柄两两不同(是博格不是单例)".to_string(),
        if all_distinct { "两两不同".to_string() } else { "存在同一句柄".to_string() },
        all_distinct,
    ));

    // 规则四:所有句柄读到相同内容指纹。
    let mut fingerprints: Vec<String> = Vec::new();
    for handle in handles.iter() {
        fingerprints.push(handle.read(|state| fingerprint_of(state)));
    }
    let mut content_consistent: bool = true;
    for index in 1..fingerprints.len() {
        if fingerprints[0] != fingerprints[index] {
            content_consistent = false;
            break;
        }
    }
    let distinct_fingerprint_total: usize = {
        let mut unique: Vec<String> = Vec::new();
        for item in fingerprints.iter() {
            if !unique.contains(item) {
                unique.push(item.clone());
            }
        }
        unique.len()
    };
    rules.push(BorgAuditRule::new(
        "CONTENT_CONSISTENT",
        "内容指纹 1 种".to_string(),
        format!("内容指纹 {} 种", distinct_fingerprint_total),
        content_consistent,
    ));

    BorgAudit::new(rules)
}

//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:博格模式  Borg Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/9 22:50 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : BorgPattern 
//!# File      : shared_state.rs
//! ★ 博格机制的核心:**一份共享状态 + 任意多个句柄**。
//!
//! ## 博格与单例的分界线(本工程最重要的一句话)
//!
//! - **单例**管控的是「实例能不能有多个」:第二个 `install` 被拒,`Err`。
//! - **博格**管控的是「状态有几份」:**不限制句柄数量**,只保证状态份数 = 1。
//!
//! 因此 [`BorgSharedState::spawn_handle`] 的接收者是 `&'static self`------
//! 这不是随手写的签名,而是把「只有活到程序结束的状态才配发句柄」
//! 这条约束交给类型系统:一个临时状态根本传不进来。
//!
//! ## ★ 为什么用 `RwLock` 而不是 `RefCell`
//!
//! `static` 要求 `Sync`,而 `RefCell<T>` **不是** `Sync`(它把借用检查挪到了运行期,
//! 因此无法安全跨线程共享)。要住进 `static` 就必须用同步原语。
//! 也就是说:**在别的语言里要靠纪律保证「共享状态不会并发写坏」,
//! 在这里由类型系统保证**------这条与单例工程「不需要 `unsafe`」是同一类结论,
//! 值得写在显著位置,它是选 Rust 的理由。
//!
//! ## ⚠️ 锁中毒(poison)不静默恢复
//!
//! `RwLock` 在持锁线程 panic 后会「中毒」。本实现取回数据继续服务,
//! 但把次数记进 `poison_recoveries` 并纳入台账------
//! **恢复是可观测的数,不是悄悄吞掉**。

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::RwLock;

/// 共享状态槽位。
///
/// `S` 是**载荷类型**(本工程是 `domain::BusinessContextState`)。
/// 本类型**完全不认识** `S` 里有什么------这是机制层能复用的前提。
pub struct BorgSharedState<S> {
    /// 载荷(内部可变)。
    value: RwLock<S>,
    /// ★ 已发出的句柄总数(`&'static self` 才能发,故它与「活着的句柄数」是两回事)。
    handle_total: AtomicUsize,
    /// 变更次数(**只增不减**):博格「一处改处处见」的代价凭证。
    mutation_total: AtomicUsize,
    /// 读取次数(**只增不减**)。
    read_total: AtomicUsize,
    /// 锁中毒后恢复的次数。正常为 0;**非 0 就是事故**,必须进报表。
    poison_recoveries: AtomicUsize,
}

impl<S> BorgSharedState<S> {
    /// 造一份共享状态。
    pub fn new(initial: S) -> Self {
        BorgSharedState {
            value: RwLock::new(initial),
            handle_total: AtomicUsize::new(0),
            mutation_total: AtomicUsize::new(0),
            read_total: AtomicUsize::new(0),
            poison_recoveries: AtomicUsize::new(0),
        }
    }

    /// ★ 发一个句柄。
    ///
    /// 接收者是 `&'static self`:**只有 `'static` 的状态能发句柄**。
    /// 于是「用一个临时状态造句柄、句柄比状态活得久」这个动作
    /// 在类型层就不存在------不需要注释提醒,也不需要运行期检查。
    pub fn spawn_handle(&'static self) -> BorgHandle<S> {
        // fetch_add 返回的是「加之前的值」,故 +1 才是序号。
        let sequence: u64 = self.handle_total.fetch_add(1, Ordering::SeqCst) as u64 + 1;
        BorgHandle { state: self, handle_sequence: sequence }
    }

    /// 读一次(闭包形式,避免要求 `S: Clone`)。
    ///
    /// ⚠️ 用闭包 `FnOnce(&S) -> T` 而不是 `fn read(&self) -> S`:
    /// 后者要 `S: Clone`,等于给每一种载荷都强加一个约束,
    /// 而「读一个字段」根本不需要克隆整份状态。
    pub fn read<T>(&self, reader: impl FnOnce(&S) -> T) -> T {
        self.read_total.fetch_add(1, Ordering::SeqCst);
        let guard = match self.value.read() {
            Ok(guard) => guard,
            Err(poisoned) => {
                // 中毒只说明「某个持写锁的线程 panic 了」,数据仍在;
                // 但必须留下痕迹,否则这种事故永远不会被发现。
                self.poison_recoveries.fetch_add(1, Ordering::SeqCst);
                poisoned.into_inner()
            }
        };
        reader(&guard)
    }

    /// 改一次(`&self` 即可,不需要 `&mut`------这正是「共享可变」的形态)。
    pub fn mutate(&self, mutation: impl FnOnce(&mut S)) {
        self.mutation_total.fetch_add(1, Ordering::SeqCst);
        let mut guard = match self.value.write() {
            Ok(guard) => guard,
            Err(poisoned) => {
                self.poison_recoveries.fetch_add(1, Ordering::SeqCst);
                poisoned.into_inner()
            }
        };
        mutation(&mut guard);
    }

    /// 已发出的句柄总数。
    pub fn handle_total(&self) -> usize {
        self.handle_total.load(Ordering::SeqCst)
    }

    /// 变更次数。
    pub fn mutation_total(&self) -> usize {
        self.mutation_total.load(Ordering::SeqCst)
    }

    /// 读取次数。
    pub fn read_total(&self) -> usize {
        self.read_total.load(Ordering::SeqCst)
    }

    /// 锁中毒恢复次数。
    pub fn poison_recoveries(&self) -> usize {
        self.poison_recoveries.load(Ordering::SeqCst)
    }
}

/// 句柄:**指向共享状态的一个入口**。
///
/// 句柄自己几乎不占状态(一个引用 + 一个序号),
/// 这正是博格与「每台终端各持一份完整状态」的成本差别所在。
///
/// ⚠️ `S: 'static` 不是随手加的约束:句柄持有 `&'static BorgSharedState<S>`,
/// 若 `S` 里可能装着短命的引用,那么「句柄活得比数据久」就又变得可能。
/// 加上它之后,这条约束由编译器保证,不需要写进注释让人自觉。
pub struct BorgHandle<S: 'static> {
    /// 指向的共享状态(`'static` 由 `spawn_handle` 的接收者保证)。
    state: &'static BorgSharedState<S>,
    /// 句柄序号(**只增不减**,从 1 开始)。
    handle_sequence: u64,
}

impl<S: 'static> BorgHandle<S> {
    /// 句柄序号。
    pub const fn handle_sequence(&self) -> u64 {
        self.handle_sequence
    }

    /// 指向的共享状态。
    pub const fn shared_state(&self) -> &'static BorgSharedState<S> {
        self.state
    }

    /// 读一次(转发到共享状态)。
    pub fn read<T>(&self, reader: impl FnOnce(&S) -> T) -> T {
        self.state.read(reader)
    }

    /// 改一次(转发到共享状态)。
    ///
    /// ★ 注意接收者是 `&self`:改共享状态**不需要独占句柄**。
    /// 这正是博格与「每个实例各持一份」最直观的差别。
    pub fn mutate(&self, mutation: impl FnOnce(&mut S)) {
        self.state.mutate(mutation)
    }

    /// ★ 判「是不是同一个句柄」:**用 `ptr::eq`,绝不打印地址**。
    ///
    /// 地址在两次运行之间会变(ASLR),打印它会让
    /// 「两次运行逐字节一致」当场失效。要印的是 `handle_sequence()`。
    pub fn is_same_handle(&self, other: &BorgHandle<S>) -> bool {
        std::ptr::eq(self as *const BorgHandle<S>, other as *const BorgHandle<S>)
    }

    /// ★★ 判「是不是指向同一份共享状态」------**博格的核心判据**。
    ///
    /// 真博格:句柄两两**不同**(`is_same_handle` = false)
    /// 但状态**相同**(`is_same_state` = true)。
    /// 这一对相反的结果就是博格的定义,也是它与单例、与普通多实例的分界。
    pub fn is_same_state(&self, other: &BorgHandle<S>) -> bool {
        std::ptr::eq(self.state, other.state)
    }
}
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:博格模式  Borg Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/9 22:50 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : BorgPattern 
//!# File      : state_census.rs
//! 共享状态台账:**快照**。
//!
//! ## ★ 为什么台账必须做成「快照」而不是实时读数
//!
//! 本层的计数(句柄数 / 变更数 / 读数)**只增不减**。
//! 因此「在报表渲染过程中读一次」与「在装配完成后读一次」会得到不同的数------
//! 若报表各处各自去读,`handle_total` 会在同一份输出里前后不一致,
//! 而「两次运行逐字节一致」也就守不住了。
//!
//! 对策:在**确定的时刻**取一次快照,报表一律印快照,并注明快照时刻。
//! 这是单例工程「台账在两个时刻各印一次」那条经验的直接沿用。

use crate::borg::BorgSharedState;

/// 共享状态台账快照。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StateCensus {
    /// 已发出的句柄总数。
    handle_total: usize,
    /// 报表实际持有的句柄数(**与 `handle_total` 可能不同**:
    /// 中间若有人取过句柄又丢弃,计数仍在,句柄没了)。
    observed_handle_total: usize,
    /// 变更次数。
    mutation_total: usize,
    /// 读取次数。
    read_total: usize,
    /// 锁中毒恢复次数(正常应为 0)。
    poison_recoveries: usize,
    /// ★ 状态份数。
    state_copy_total: usize,
}

impl StateCensus {
    /// 在给定时刻取一次快照。
    ///
    /// - `state`:共享状态;
    /// - `observed_handle_total`:调用方此刻实际持有的句柄数。
    ///
    /// ⚠️ 两个句柄数**分开记**而不是只记一个:
    /// 只记「发出过几个」会掩盖「有几个还在用」,
    /// 只记「还有几个」会让「曾经发过多少」无法追溯。
    pub fn capture<S>(state: &BorgSharedState<S>, observed_handle_total: usize) -> Self {
        StateCensus {
            handle_total: state.handle_total(),
            observed_handle_total,
            mutation_total: state.mutation_total(),
            read_total: state.read_total(),
            poison_recoveries: state.poison_recoveries(),
            // ★ 机制层在**结构上**保证只有一份:句柄只持有引用,
            // 没有任何第二条路径能造出第二份状态。这个 1 是结构的结论,不是约定。
            state_copy_total: 1,
        }
    }

    /// 已发出的句柄总数。
    pub const fn handle_total(&self) -> usize {
        self.handle_total
    }

    /// 实际持有的句柄数。
    pub const fn observed_handle_total(&self) -> usize {
        self.observed_handle_total
    }

    /// 变更次数。
    pub const fn mutation_total(&self) -> usize {
        self.mutation_total
    }

    /// 读取次数。
    pub const fn read_total(&self) -> usize {
        self.read_total
    }

    /// 锁中毒恢复次数。
    pub const fn poison_recoveries(&self) -> usize {
        self.poison_recoveries
    }

    /// 状态份数。
    pub const fn state_copy_total(&self) -> usize {
        self.state_copy_total
    }

    /// 一句话摘要(进报表注解)。
    ///
    /// ⚠️ 摘要里的每个数字都取自快照本身,不在此处重算------
    /// 一句与相邻数字矛盾的注解,比没有注解更糟。
    pub fn summary_text(&self) -> String {
        format!(
            "发出句柄 {} 个 / 在用 {} 个 / 状态 {} 份 / 变更 {} 次 / 读取 {} 次 / 锁中毒 {} 次",
            self.handle_total,
            self.observed_handle_total,
            self.state_copy_total,
            self.mutation_total,
            self.read_total,
            self.poison_recoveries
        )
    }
}

//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:博格模式  Borg Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/9 22:50 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : BorgPattern 
//!# File      : context_carrier.rs
//! ★ 三种「上下文载体」的**统一接口**。
//!
//! ## 为什么要有这个 trait
//!
//! 本工程要回答的不是「博格怎么写」,而是「**怎么证明它是博格**」。
//! 要证明就得有对照:真博格 vs 快照 vs 独立实例。
//! 三者内部机制完全不同(`RwLock` 共享 / `RefCell` 私有 / `RefCell` 私有),
//! 若各写各的方法名,对照实验就得写三套分支,
//! 而「三套分支」正是这类实验最容易写出偏差的地方。
//!
//! 统一成一个 trait 之后,对照实验只有**一份代码**------
//! 于是「三种载体结果不同」必然来自载体本身,不可能来自实验代码。
//!
//! ## ★ 签名一律 `&self`(不是 `&mut self`)
//!
//! 真博格改共享状态**不需要独占句柄**,因此它的变更方法是 `&self`。
//! 若 trait 用 `&mut self`,真博格就得自己再加一层内部可变性去迁就接口------
//! 那是把接口的缺陷塞进实现里。内部可变性由**各自**按需解决。

use crate::domain::{BusinessContextState, ShiftCode};

/// 载体种类。
///
/// ★ 这是一个**封闭集合**(三种对照形态是实验设计的一部分,不会扩展),
/// 因此用 `enum` 换编译期穷尽性检查------与 `ShiftCode` / `TradeKind` 同理。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CarrierKind {
    /// 真博格:多个句柄,一份共享状态。
    Shared,
    /// ★ 快照陷阱:创建时拷一份,之后与共享状态**再无关系**。
    Snapshot,
    /// 独立实例:压根不认识共享状态,初值也不同。
    Isolated,
}

impl CarrierKind {
    /// 中文名。
    pub const fn chinese_name(&self) -> &'static str {
        match self {
            CarrierKind::Shared => "真博格",
            CarrierKind::Snapshot => "快照副本",
            CarrierKind::Isolated => "独立实例",
        }
    }

    /// 机器编码。
    pub const fn code(&self) -> &'static str {
        match self {
            CarrierKind::Shared => "SHARED",
            CarrierKind::Snapshot => "SNAPSHOT",
            CarrierKind::Isolated => "ISOLATED",
        }
    }

    /// ★ 判据:改了它之后,**别人**应不应该看到。
    ///
    /// 这是三种载体唯一的本质差别,也是对照实验要看的那一列。
    pub const fn propagates_change(&self) -> bool {
        match self {
            CarrierKind::Shared => true,
            CarrierKind::Snapshot => false,
            CarrierKind::Isolated => false,
        }
    }
}

/// 载体全集(**顺序固定**:真博格 → 快照 → 独立)。
pub const BUILTIN_CARRIER_KINDS: [CarrierKind; 3] =
    [CarrierKind::Shared, CarrierKind::Snapshot, CarrierKind::Isolated];

/// 上下文载体的统一接口。
pub trait BusinessContextCarrier {
    /// 载体种类。
    fn carrier_kind(&self) -> CarrierKind;

    /// 载体标签(进报表第一列)。
    fn label_text(&self) -> String;

    /// 读一份当前状态的**副本**。
    ///
    /// ⚠️ 返回值必然是副本(快照)------这是免不了的:
    /// Rust 不允许把借用跨过可变点传出来。
    /// 关键区别在于**这个副本是不是唯一的真相**:
    /// 真博格的副本只是「此刻的读数」,快照的副本就是它全部的世界。
    fn current_state(&self) -> BusinessContextState;

    /// 交接班:换班次 + 换操作员。
    fn apply_shift_handover(&self, shift: ShiftCode, operator_name: &str);

    /// 内容指纹。
    fn content_fingerprint(&self) -> String;
}
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:博格模式  Borg Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/9 22:50 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : BorgPattern 
//!# File      : isolated_context.rs
//! 独立实例:**压根不认识共享状态**的对照组。
//!
//! ## 为什么还要有这一档
//!
//! 快照档证明的是「初值相同也不等于共享」。
//! 这一档证明的是另一件事:**不共享的形态里,有一种是一眼就能看出来的**。
//!
//! 有了三档,判别才有层次:
//!
//! | | 初值是否一致 | 改后是否跟随 | 危险程度 |
//! |---|---|---|---|
//! | 真博格 | 一致 | **跟随** | ---(正确) |
//! | 快照副本 | 一致 | 不跟随 | ★★ 最高(看起来像博格) |
//! | 独立实例 | **不一致** | 不跟随 | 低(一眼可见) |
//!
//! 若只有两档(真博格 vs 独立实例),「初值一致」就会被误当成共享的充分条件,
//! 快照这档缺陷就永远不会被测出来。

use std::cell::RefCell;

use crate::client::{BusinessContextCarrier, CarrierKind};
use crate::domain::{BusinessContextState, ShiftCode};

/// 独立实例:自己持有一份完整状态。
pub struct IsolatedContext {
    /// 载体标签。
    label_text: String,
    /// 自己那份状态(与共享状态**无任何关系**)。
    own_state: RefCell<BusinessContextState>,
}

impl IsolatedContext {
    /// 造一个独立实例。
    ///
    /// ⚠️ 初值由调用方**显式给出**而不是从共享状态拷:
    /// 拷贝出来的就不叫独立实例了,那是快照。
    /// 本工程在装配点给它一个**不同门店**的初值,
    /// 好让「初值不一致」成为这一档可核对的特征。
    pub fn new(label_text: String, initial_state: BusinessContextState) -> Self {
        IsolatedContext { label_text, own_state: RefCell::new(initial_state) }
    }

    /// 载体标签(不含任何版本号后缀------它不是从别处拷来的)。
    pub fn label_text(&self) -> &str {
        &self.label_text
    }
}

impl BusinessContextCarrier for IsolatedContext {
    fn carrier_kind(&self) -> CarrierKind {
        CarrierKind::Isolated
    }

    fn label_text(&self) -> String {
        self.label_text.clone()
    }

    fn current_state(&self) -> BusinessContextState {
        self.own_state.borrow().clone()
    }

    fn apply_shift_handover(&self, shift: ShiftCode, operator_name: &str) {
        self.own_state.borrow_mut().hand_over(shift, operator_name.to_string());
    }

    fn content_fingerprint(&self) -> String {
        self.own_state.borrow().content_fingerprint()
    }
}


//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:博格模式  Borg Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/9 22:50 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : BorgPattern 
//!# File      : snapshot_context.rs
//! ★★ 快照副本:**伪装成博格的独立副本**(本工程最危险的对照)。
//!
//! ## 为什么它比「独立实例」危险得多
//!
//! 独立实例从初值起就与别人不同,一眼看得出「这不是共享」。
//! 快照副本则是**创建时拷一份**------初值与共享状态**完全相同**,
//! 于是创建完的那一刻,它的指纹、它的每个字段都与真博格一模一样。
//!
//! 只有**共享状态后来被改过**之后,两者才分叉:
//! - 真博格:跟着变;
//! - 快照:停在旧值。
//!
//! 而「停���旧值」在报表上表现为「这台机器今天的汇率是旧的」------
//! 收港币时按旧汇率折算,报表完全正常,只是钱错了。
//! **这正是静默降级最典型的形态**。
//!
//! ## ★ 判别它的唯一可靠指标
//!
//! 「初值一致」不足以证明共享,**必须看改过之后是否还一致**。
//! 因此本工程的对照实验一律跑三步:建 → 改 → 再看,
//! 只跑前两步的实验会把快照判成博格。

use std::cell::RefCell;

use crate::borg::BorgSharedState;
use crate::client::{BusinessContextCarrier, CarrierKind};
use crate::domain::{BusinessContextState, ShiftCode};

/// 快照副本:创建时拷一份,此后与共享状态再无关系。
pub struct SnapshotContext {
    /// 载体标签。
    label_text: String,
    /// 冻结在副本里的状态(`RefCell` 是为了让 trait 的 `&self` 签名成立)。
    frozen_state: RefCell<BusinessContextState>,
    /// ★ 取自共享状态的那一刻的版本号(**诊断用**:它与共享状态当前版本号
    /// 的差,就是「这份副本落后了多少」)。
    captured_revision: u64,
}

impl SnapshotContext {
    /// 从共享状态拷一份出来。
    ///
    /// ⚠️ 刻意**不留**指向共享状态的引用:
    /// 留了就不是快照了,那是「缓存 + 回源」,属于另一种设计(而且更难推理)。
    pub fn capture_from(
        label_text: String,
        shared_state: &BorgSharedState<BusinessContextState>,
    ) -> Self {
        let snapshot: BusinessContextState = shared_state.read(|state| state.clone());
        let captured_revision: u64 = snapshot.revision();
        SnapshotContext { label_text, frozen_state: RefCell::new(snapshot), captured_revision }
    }

    /// 载体标签。
    pub fn label_text(&self) -> &str {
        &self.label_text
    }

    /// 拷取时的版本号。
    pub const fn captured_revision(&self) -> u64 {
        self.captured_revision
    }
}

impl BusinessContextCarrier for SnapshotContext {
    fn carrier_kind(&self) -> CarrierKind {
        CarrierKind::Snapshot
    }

    fn label_text(&self) -> String {
        format!("{}(副本 rev{})", self.label_text, self.captured_revision)
    }

    fn current_state(&self) -> BusinessContextState {
        self.frozen_state.borrow().clone()
    }

    fn apply_shift_handover(&self, shift: ShiftCode, operator_name: &str) {
        // ★ 只改自己那份:共享状态一动不动。这正是它与真博格的全部差别。
        self.frozen_state
            .borrow_mut()
            .hand_over(shift, operator_name.to_string());
    }

    fn content_fingerprint(&self) -> String {
        self.frozen_state.borrow().content_fingerprint()
    }
}
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:博格模式  Borg Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/9 22:50 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : BorgPattern 
//!# File      : store_service.rs
//! 门店服务:**营业动作的入口**。
//!
//! ## ★ 为什么这些动作要收在一个服务里,而不是让终端各自改共享状态
//!
//! 博格的共享状态**任何句柄都能改**------这是它的价值,也是它的代价。
//! 若每台终端都能随手 `mutate`,那么「今天汇率被谁改的」就无从追溯。
//!
//! 收进服务之后,改动的**动作**是有限的一组方法,
//! 而改动留下的**痕迹**(`mutation_total` 与 `revision`)仍在机制层------
//! 于是「能改」与「能查被改了几次」两件事同时成立。
//!
//! ## ⚠️ 服务持有的是「共享状态」而不是「某一台终端的句柄」
//!
//! 交接班是**全店的事**,不是某台机器的事。
//! 若服务持有某台终端的句柄,那么「一号机交接班」会变成
//! 「通过一号机的句柄改共享状态」------语义上没差别,
//! 但读者会以为这件事只与一号机有关。

use crate::borg::BorgSharedState;
use crate::domain::{BusinessContextState, CurrencyCode, ShiftCode, StoreCode};

/// 门店服务。
pub struct StoreService {
    /// 共享营业上下文(`'static`:服务不拥有它,只是持有引用)。
    shared_state: &'static BorgSharedState<BusinessContextState>,
}

impl StoreService {
    /// 构造。
    pub fn new(shared_state: &'static BorgSharedState<BusinessContextState>) -> Self {
        StoreService { shared_state }
    }

    /// 共享状态。
    pub const fn shared_state(&self) -> &'static BorgSharedState<BusinessContextState> {
        self.shared_state
    }

    /// 交接班:换班次 + 换操作员(**一次变更**)。
    pub fn hand_over(&self, shift: ShiftCode, operator_name: &str) {
        self.shared_state.mutate(|state| state.hand_over(shift, operator_name.to_string()));
    }

    /// 刷新某币种汇率。
    ///
    /// ★ 把「没找到这个币种」如实返回:
    /// 悄悄新增一条会让「币种清单」与「汇率表」两处口径漂移,
    /// 而这种漂移只在跨币种折算时才显形。
    pub fn refresh_exchange_rate(&self, currency: CurrencyCode, basis_points: i64) -> bool {
        let mut applied: bool = false;
        self.shared_state
            .mutate(|state| applied = state.refresh_exchange_rate(currency, basis_points));
        applied
    }

    /// 切换门店(移动柜台 / 跨店支援)。
    pub fn switch_store(&self, store_code: StoreCode) {
        self.shared_state.mutate(|state| state.set_store_code(store_code));
    }

    /// 换促销档期。
    pub fn advance_promotion(&self) {
        self.shared_state.mutate(|state| state.advance_promotion_version());
    }

    /// 当前状态的一行描述(**不**取快照,是实时读数)。
    ///
    /// ⚠️ 文档里必须写明它是实时读数:它与台账快照可能不一致,
    /// 而这正是「只增不减的量要取快照」那条纪律的反面教材。
    pub fn describe_text(&self) -> String {
        self.shared_state.read(|state| state.compact_text())
    }
}
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:博格模式  Borg Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/9 22:50 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : BorgPattern 
//!# File      : terminal_context.rs
//! ★ 真博格的句柄:**一台收银终端一个句柄,共享同一份营业上下文**。
//!
//! ## ★★ 博格的边界:什么进共享状态,什么留在句柄上
//!
//! - **进共享状态**(所有终端必须看到同一个值):
//!   今天在哪家店、什么营业日、什么班次、谁值班、汇率多少、税率多少、促销第几版。
//!   一号机交接班之后,二三四号机必须立刻看到新班次------这就是博格的全部价值。
//! - **留在句柄上**(只与这台机器有关):
//!   `terminal_code`。这台机器是几号机,不随共享状态变化,
//!   也不该被别的机器改掉。
//! - **压根不进本类型**(只与这台机器有关且可变):
//!   钱箱 `TerminalTill`。一号机收的钱被二号机读到,账立刻就错。
//!
//! 判据一句话:**所有句柄必须看到同一个值 → 进共享状态;
//! 只与本句柄有关 → 留在句柄上或另立类型。**
//! 判错了不是「多了个字段」,是账错。
//!
//! ## ⚠️ 严格 Borg 没有「实例私有状态」
//!
//! Python 的 Borg 把 `instance.__dict__` 指向类级 dict,
//! 因此**实例上没有任何私有属性**。本工程给句柄留了 `terminal_code`,
//! 它是**身份**不是状态------`const` 语义、创建后不可变。
//! 若哪天要给终端加「本机的现金结余」,那就不该放进这里,
//! 而该放进 `TerminalTill`(见上)。

use crate::borg::BorgHandle;
use crate::borg::BorgSharedState;
use crate::client::{BusinessContextCarrier, CarrierKind};
use crate::domain::{BusinessContextState, ShiftCode, TerminalCode};

/// 一台终端的营业上下文(**博格句柄**)。
pub struct TerminalContext {
    /// 句柄身份:我是几号机(**不可变**,不进共享状态)。
    terminal_code: TerminalCode,
    /// 指向共享营业上下文的博格句柄。
    handle: BorgHandle<BusinessContextState>,
}

impl TerminalContext {
    /// 开一台终端:向共享状态要一个句柄。
    pub fn new(
        terminal_code: TerminalCode,
        shared_state: &'static BorgSharedState<BusinessContextState>,
    ) -> Self {
        // ★ 每调一次就多一个句柄;共享状态**不会**因为多开一台机器而多出一份。
        let handle: BorgHandle<BusinessContextState> = shared_state.spawn_handle();
        TerminalContext { terminal_code, handle }
    }

    /// 我是几号机。
    pub const fn terminal_code(&self) -> TerminalCode {
        self.terminal_code
    }

    /// 底层博格句柄(供契约审计用:审计要拿 `&[BorgHandle<S>]`)。
    pub const fn handle(&self) -> &BorgHandle<BusinessContextState> {
        &self.handle
    }

    /// 句柄序号(**可打印的身份号**;不打印地址,地址会因 ASLR 变化)。
    pub const fn handle_sequence(&self) -> u64 {
        self.handle.handle_sequence()
    }

    /// 读一次共享状态(闭包形式,不克隆整份状态)。
    pub fn read<T>(&self, reader: impl FnOnce(&BusinessContextState) -> T) -> T {
        self.handle.read(reader)
    }

    /// 改一次共享状态(`&self` 即可------这正是「共享可变」的形态)。
    pub fn mutate(&self, mutation: impl FnOnce(&mut BusinessContextState)) {
        self.handle.mutate(mutation)
    }
}

impl BusinessContextCarrier for TerminalContext {
    fn carrier_kind(&self) -> CarrierKind {
        CarrierKind::Shared
    }

    fn label_text(&self) -> String {
        format!("{}(共享句柄 #{})", self.terminal_code.code(), self.handle_sequence())
    }

    fn current_state(&self) -> BusinessContextState {
        // 克隆一份返回:这是「此刻的读数」,共享状态仍在共享槽位里。
        self.read(|state| state.clone())
    }

    fn apply_shift_handover(&self, shift: ShiftCode, operator_name: &str) {
        self.mutate(|state| state.hand_over(shift, operator_name.to_string()));
    }

    fn content_fingerprint(&self) -> String {
        self.read(|state| state.content_fingerprint())
    }
}
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:博格模式  Borg Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/9 22:50 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : BorgPattern 
//!# File      : terminal_fleet.rs
//! 终端机群:**N 台终端 + N 个钱箱 + 1 份共享营业上下文**。
//!
//! ## ★ 这一张表就是博格的成本模型
//!
//! - 终端 N 台 → **N 个句柄**(`TerminalContext`,每个只占一个引用 + 一个序号);
//! - 钱箱 N 个 → **N 份私有数据**(本来就该各有一份);
//! - 营业上下文 → **1 份**(共享)。
//!
//! 若不共享,营业上下文也要 N 份,而它的内容(汇率、班次、税率)
//! 每台机器都一样------那 N−1 份就是纯粹的内存浪费,
//! 外加一个「改汇率要通知 N 台机器」的同步问题。
//! 博格把「N 份相同内容」压成「1 份 + N 个引用」,
//! 顺带把同步问题变成**不存在**(根本没得同步)。

use crate::borg::BorgHandle;
use crate::borg::BorgSharedState;
use crate::client::TerminalContext;
use crate::domain::{BusinessContextState, TenderLine, TerminalCode, TerminalTill};

/// 机群里的一台终端:一个共享句柄 + 一个私有钱箱。
pub struct FleetMember {
    /// 共享营业上下文的句柄。
    context: TerminalContext,
    /// 本机的钱箱(**私有**,不共享)。
    till: TerminalTill,
}

impl FleetMember {
    /// 构造。
    pub fn new(context: TerminalContext, till: TerminalTill) -> Self {
        FleetMember { context, till }
    }

    /// 营业上下文句柄。
    pub const fn context(&self) -> &TerminalContext {
        &self.context
    }

    /// 钱箱(只读)。
    pub const fn till(&self) -> &TerminalTill {
        &self.till
    }

    /// 钱箱(可写:记一笔)。
    pub fn till_mut(&mut self) -> &mut TerminalTill {
        &mut self.till
    }
}

/// 终端机群。
pub struct TerminalFleet {
    /// 成员清单(**顺序 = 开台顺序**,不排序------开台顺序本身是运营信息)。
    members: Vec<FleetMember>,
}

impl TerminalFleet {
    /// 开台:为每台终端向共享状态要一个句柄,并配一个空钱箱。
    pub fn open(
        shared_state: &'static BorgSharedState<BusinessContextState>,
        terminal_codes: &[TerminalCode],
    ) -> Self {
        let mut members: Vec<FleetMember> = Vec::new();
        for terminal_code in terminal_codes.iter() {
            // ★ 每一次 new 都是「多一个句柄」,不是「多一份状态」。
            let context: TerminalContext = TerminalContext::new(*terminal_code, shared_state);
            let till: TerminalTill = TerminalTill::new(*terminal_code);
            members.push(FleetMember::new(context, till));
        }
        TerminalFleet { members }
    }

    /// 成员清单。
    pub fn members(&self) -> &[FleetMember] {
        &self.members
    }

    /// 台数。
    pub fn terminal_total(&self) -> usize {
        self.members.len()
    }

    /// 全部底层博格句柄(供契约审计用)。
    pub fn handles(&self) -> Vec<&BorgHandle<BusinessContextState>> {
        self.members.iter().map(|member| member.context().handle()).collect()
    }

    /// 按终端编码找成员。
    pub fn member_of(&self, terminal_code: TerminalCode) -> Option<&FleetMember> {
        self.members
            .iter()
            .find(|member| member.context().terminal_code() == terminal_code)
    }

    /// 按终端编码找成员(可写)。
    pub fn member_mut_of(&mut self, terminal_code: TerminalCode) -> Option<&mut FleetMember> {
        self.members
            .iter_mut()
            .find(|member| member.context().terminal_code() == terminal_code)
    }

    /// 记一笔到指定终端的钱箱。
    ///
    /// ★ 返回 `bool` 而不是静默忽略:
    /// 记到一台不存在的机器上若被悄悄接受,那笔钱就从报表里消失了。
    pub fn record(&mut self, terminal_code: TerminalCode, line: TenderLine) -> bool {
        match self.member_mut_of(terminal_code) {
            Some(member) => {
                member.till_mut().push(line);
                true
            }
            None => false,
        }
    }
}

调用:

rust 复制代码
//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:博格模式  Borg Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1 
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/9 22:50 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : BorgPattern 
//!# File      : main.rs
//! # 博格模式(Borg Pattern)------ 连锁珠宝门店终端共享营业上下文
//!
//! 一家门店开 5 台收银终端。它们必须看到**同一份**营业上下文
//! (今天在哪家店、什么班次、谁值班、汇率多少、税率多少、促销第几版),
//! 但每台机器有**自己的钱箱**。
//! 本工程用博格把「N 份相同的上下文」压成「1 份状态 + N 个句柄」。
//!
//! ## 严格分层结构
//!
//! ```text
//! main                     装配点:造共享状态、开机群、记账、编排十一幕、工程外扩展区
//!  └── app                 表示层:表格引擎 + 十类报表(**不认识 borg / client**)
//!       └── analysis       分析层:载体对照 / 审计视图 / 账本 / 价值量化 / 体检
//!            ├── client    调用层:三种载体 + 终端机群 + 门店服务
//!            │    └── borg     ★ 博格机制层:共享状态槽位 + 句柄 + 契约审计
//!            └── domain    领域层:营业上下文 / 钱箱 / 金额 / 汇率表 / 币种
//!                 └── support   支撑层:日期 / 确定性编码 / 显示宽度
//! ```
//!
//! ## 依赖方向(**实测**,由 `scripts/check_layer_dependencies.sh` 得出,非设计意图)
//!
//! ```text
//! app      -> analysis domain support
//! analysis -> borg client domain          (另直接依赖 borg:要读 BorgAudit / StateCensus)
//! client   -> borg domain
//! domain   -> support
//! borg     -> none                        ★ 与 support 同秩,两个叶子
//! support  -> none
//! ```
//!
//! ★★ 图上必须写 `borg -> none` 而不是把它画在 `domain` 下面。
//! 机制层**不认识载荷**------这是它能被整体搬进别的代码库的前提。
//! 一旦它开始引用 `domain`,上面的实测邻接表立刻变化,
//! 于是「机制层可复用」这句话从结构结论退化成口头承诺。
//!
//! 另有四条**图看不见**的红线,由 `scripts/check_layer_redlines.sh` 实测:
//! 领域层不出现 `Borg` / `RwLock` / `Handle` / `std::sync`;
//! 表示层不出现 `crate::borg` / `crate::client`;
//! 全工程无 `#[allow(...)]` / `unsafe` / `HashMap`;
//! 汇率缺失不得按 1:1 兜底。
//!
//! ## ★★ 博格与单例的分界线(本工程最重要的一句话)
//!
//! - **单例**管控的是「实例能不能有多个」:装第二次被拒。
//! - **博格**管控的是「状态有几份」:**不限制句柄数量**,只保证状态份数 = 1。
//!
//! 因此 [`borg::BorgSharedState::spawn_handle`] 的接收者是 `&'static self`------
//! 「只有活到程序结束的状态才配发句柄」这件事交给类型系统,
//! 不需要写进注释让人自觉。
//!
//! ## ★ 博格的边界:什么进共享状态,什么不进
//!
//! 判据一句话:**所有句柄必须看到同一个值 → 进共享状态;
//! 只与本句柄有关 → 留在句柄上或另立类型。**
//!
//! - 进共享:门店 / 营业日 / 班次 / 值班操作员 / 汇率表 / 税率 / 促销版本;
//! - 留在句柄:`terminal_code`(身份,`const` 语义、创建后不可变);
//! - 另立类型:`TerminalTill` 钱箱(一号机收的钱被二号机读到,账立刻就错)。
//!
//! ## ★ 本工程要实证的五件事
//!
//! 全部由脚本复验,不是口头声明:
//! `bash scripts/check_layer_dependencies.sh src`(依赖方向)
//! 与 `bash scripts/check_layer_redlines.sh src`(红线)。
//!
//! 1. **机制层零依赖**:`src/borg` 里一个 `crate::` 都没有。
//!    机制对载荷一无所知------这是它能被复用的前提。
//! 2. **领域层不认识博格**:`src/domain` 里没有 `Borg` / `RwLock` / `Handle`。
//!    一旦领域层知道「自己会被共享」,博格就从一个结构退化成一个标志位。
//! 3. **表示层不认识机制与调用层**:`src/app` 里没有 `crate::borg` / `crate::client`。
//! 4. **状态只有一份 且 句柄两两不同**:两条**同时**核,缺一条就退化成别的东西
//!    (只核前者单例也过;只核后者快照组也过)。
//! 5. **扩展不改既有文件**:第 5 台终端、澳门元、澳门氹仔店、第 4 位操作员
//!    全部写在下面的「工程外扩展区」,不动任何分层文件,并自动进入统计与报表。
//!
//! ## 十一幕
//!
//! | 幕 | 内容 | 回答什么问题 |
//! |---|---|---|
//! 一 | 装配总览 | 后面每个数受哪些参数影响 |
//! 二 | 币种与汇率 | 两条折算路径的共同基线 |
//! 三 | 机群装配 | N 个句柄 + N 个钱箱 + 1 份状态 |
//! 四 | 三档载体对照 | ★ 改一次共享状态,看谁跟着变 |
//! 五 | 契约审计四条 | ★ 状态相同 **且** 句柄不同(含已知失败用例) |
//! 六 | 钱箱账本 | 双路径互盯 + 汇率波动影响额 |
//! 七 | 币种分桶守恒式 | 分桶求和 vs 逐笔求和 |
//! 八 | 价值量化 | N 份与 1 份差多少;台账快照 |
//! 九 | 工程外扩展 | 新增的东西有没有自动进入统计 |
//! 十 | 接口清点 | 对外只读接口逐条真调一遍 |
//! 十一 | 体检 | 所有「应当 == 实测」收在一处 |

mod analysis;
mod app;
mod borg;
mod client;
mod domain;
mod support;

use std::sync::OnceLock;

use crate::analysis::{
    audit_rule_view, audit_rule_views, audit_summary_view, AuditRuleView, AuditSummaryView,
    BorgValueReport, CarrierProbe, CensusView, CheckLine, CheckReport, SettlementLedger,
    TillLedgerEntry,
};
use crate::app::{
    basis_points_text, bool_text, byte_text, gram_display_text, note_lines, render_assembly_table,
    render_audit_negative_table, render_audit_table, render_carrier_table, render_census_table,
    render_check_table, render_currency_bucket_table, render_exchange_table,
    render_extension_table, render_fleet_table, render_interface_table, render_till_table,
    render_value_table, render_cell, section_header, table_rule, ColumnAlign, ExtensionRowView,
    FleetRowView, Table, TableColumn, DOCUMENT_WIDTH, INTERFACE_NAME_WIDTH, INTERFACE_OUTPUT_WIDTH,
};
use crate::borg::{audit_borg_handles, BorgAudit, BorgHandle, BorgSharedState, StateCensus};
use crate::client::{
    BusinessContextCarrier, CarrierKind, FleetMember, IsolatedContext, SnapshotContext,
    StoreService, TerminalFleet,
};
use crate::domain::{
    divide_rounded, next_shift, BusinessContextState, CurrencyCode, ExchangeRateEntry,
    ExchangeRateTable, Money, ShiftCode, StoreCode, TenderLine, TerminalCode, TradeKind, CNY, HKD,
    STORE_CN_01, STORE_CN_02, STORE_HK_01, TERMINAL_POS_01, TERMINAL_POS_02, TERMINAL_POS_03,
    TERMINAL_POS_04, USD, BUILTIN_CURRENCY_CODES, BUILTIN_SHIFT_CODES, BUILTIN_STORE_CODES,
    BUILTIN_TERMINAL_CODES, BUILTIN_TRADE_KINDS,
};
use crate::support::{
    days_between, days_in_month, deterministic_code, display_width, elide_text, is_leap_year,
    pad_center, prefixed_code, report_cell_text, self_check_width_model, stable_hash,
    truncate_to_width, CalendarDate,
};

// ==================================================================
// ★★★ 工程外扩展区:以下内容**全部**在 `main.rs` 内声明
// ==================================================================
// 判据:`src/` 下的分层文件一个都没有被修改。
// 新增的东西必须**自动进入统计与报表**,而不是「接进来就算了」。
//
// 1. 第 5 台终端(新增一台机器 → 多一个句柄,不多一份状态);
// 2. 澳门元 MOP(新增一个币种 → 自动进币种分桶,因为分桶币种来自实际流水);
// 3. 澳门氹仔店(新增一家门店 → 自动进「切换门店」动作);
// 4. 第 4 位值班操作员(新增一位值班员 → 自动进交接班动作)。
// ------------------------------------------------------------------

/// 扩展一:第 5 台终端。
///
/// ⚠️ 声明在这里而不是 `domain/terminal_code.rs`:
/// 后者是分层文件,「新增终端不改分层文件」这条验证必须站得住。
/// 能做到是因为 `TerminalCode` 是**开放型** `const fn new` 结构体而不是 `enum`。
const TERMINAL_POS_05: TerminalCode = TerminalCode::new("POS-05", "二层翡翠柜");

/// 扩展二:澳门元(本工程第 4 种币种)。
///
/// ★ 它能自动进分桶,靠的是 `TerminalTill::native_net_by_currency()` 的
/// 币种清单**从实际流水提取**------若从内置清单取,MOP 会表现为
/// 「这个币种今天没收款」,报表完全正常,只是少了一整个币种的账。
const MOP: CurrencyCode = CurrencyCode::new("MOP", "澳门元", "MOP$");

/// 扩展二配套:澳门元对人民币的万分点(1 MOP ≈ 0.8420 CNY)。
const MOP_RATE_BASIS_POINTS: i64 = 8_420;

/// 扩展三:澳门氹仔店。
const STORE_MO_01: StoreCode = StoreCode::new("STORE-MO-01", "澳门氹仔店");

/// 扩展四:第 4 位值班操作员。
const OPERATOR_EXTENDED: &str = "何俊杰";

// ==================================================================
// 共享状态:**全工程唯一的一份**营业上下文
// ==================================================================

/// ★ 共享状态槽位(`'static`)。
///
/// ## 为什么用 `OnceLock` 而不是 `Box::leak`
///
/// 两者都能拿到 `&'static`,但 `OnceLock` 额外保证「**只会初始化一次**」------
/// 而「状态只有一份」正是博格要守的东西。用 `Box::leak` 的话,
/// 「有人又泄漏了第二个」这件事无法被发现;用 `OnceLock`,第二次取
/// 拿到的仍是同一个引用,**结构上**不可能有第二份。
///
/// ⚠️ `get_or_init` 返回 `&'static T`(`static` 的生命周期就是 `'static`),
/// 这正是 `spawn_handle(&'static self)` 需要的接收者类型。
static SHARED_CONTEXT: OnceLock<BorgSharedState<BusinessContextState>> = OnceLock::new();

/// 取共享状态(首次调用时初始化)。
fn shared_context() -> &'static BorgSharedState<BusinessContextState> {
    SHARED_CONTEXT.get_or_init(|| {
        let rate_entries: Vec<ExchangeRateEntry> = vec![
            ExchangeRateEntry::new(CNY, 10_000),
            ExchangeRateEntry::new(HKD, 9_215),
            ExchangeRateEntry::new(USD, 71_250),
            // ★ 扩展区新增的币种:直接进初始汇率表,不需要改任何分层文件。
            ExchangeRateEntry::new(MOP, MOP_RATE_BASIS_POINTS),
        ];
        BorgSharedState::new(BusinessContextState::new(
            STORE_CN_01,
            CalendarDate::new(2026, 10, 8),
            ShiftCode::Morning,
            "陈美琪".to_string(),
            ExchangeRateTable::new(rate_entries),
            1_300,
            3,
        ))
    })
}

/// 独立实例档的初值:**刻意与共享档不同**。
///
/// ⚠️ 不从共享状态拷------拷出来的就不叫独立实例,那是快照。
/// 给它一个不同门店、不同班次、不同汇率,好让「初值不一致」
/// 成为这一档可核对的特征。
fn isolated_initial_state() -> BusinessContextState {
    let rate_entries: Vec<ExchangeRateEntry> = vec![
        ExchangeRateEntry::new(CNY, 10_000),
        // ★ 与共享档的两个数字都不同,确保内容指纹必然不同。
        ExchangeRateEntry::new(HKD, 9_180),
        ExchangeRateEntry::new(USD, 71_300),
    ];
    BusinessContextState::new(
        STORE_HK_01,
        CalendarDate::new(2026, 10, 8),
        ShiftCode::Evening,
        "黄伟文".to_string(),
        ExchangeRateTable::new(rate_entries),
        0,
        1,
    )
}

// ==================================================================
// 编排
// ==================================================================

fn main() {
    // ---------- 装配 ----------
    let service: StoreService = StoreService::new(shared_context());
    let business_date: CalendarDate = CalendarDate::new(2026, 10, 8);

    // ★ 终端清单:4 台内置 + 1 台扩展区新增。
    let terminal_codes: Vec<TerminalCode> = vec![
        TERMINAL_POS_01,
        TERMINAL_POS_02,
        TERMINAL_POS_03,
        TERMINAL_POS_04,
        TERMINAL_POS_05,
    ];

    // ---------- 幕一:装配总览 ----------
    let assembly_items: Vec<(String, String)> = vec![
        ("门店".to_string(), STORE_CN_01.display_text()),
        (
            "营业日".to_string(),
            format!(
                "{}({})",
                business_date.machine_text(),
                business_date.weekday_chinese_text()
            ),
        ),
        (
            "班次".to_string(),
            format!(
                "{} {}",
                ShiftCode::Morning.chinese_name(),
                ShiftCode::Morning.period_text()
            ),
        ),
        ("值班操作员".to_string(), "陈美琪".to_string()),
        ("税率(万分点)".to_string(), "1300".to_string()),
        ("促销版本".to_string(), "v3".to_string()),
        (
            "记账基准币种".to_string(),
            format!("{} {}", CNY.code(), CNY.chinese_name()),
        ),
        ("币种数(含扩展 MOP)".to_string(), "4".to_string()),
        (
            "终端台数(含扩展 POS-05)".to_string(),
            terminal_codes.len().to_string(),
        ),
        ("共享状态初始版本号".to_string(), "0".to_string()),
        (
            "机制层依赖".to_string(),
            "零(borg 层不出现任何 crate::)".to_string(),
        ),
        ("支撑层".to_string(), "日期 / 确定性编码 / 显示宽度".to_string()),
    ];
    print_lines(&render_assembly_table(&assembly_items));

    // ---------- 幕二:币种与汇率 ----------
    let initial_table: ExchangeRateTable =
        shared_context().read(|state| state.exchange_table().clone());
    let mut exchange_rows: Vec<(String, String, String, String)> = Vec::new();
    for entry in initial_table.entries().iter() {
        exchange_rows.push((
            entry.currency().code().to_string(),
            entry.currency().chinese_name().to_string(),
            basis_points_text(entry.basis_points()),
            if entry.currency() == MOP {
                "★ 工程外扩展区新增的币种".to_string()
            } else if entry.currency() == CNY {
                "基准币种,恒为 1.0000".to_string()
            } else {
                "盘中会被刷新(本工程港币会被刷新)".to_string()
            },
        ));
    }
    print_lines(&render_exchange_table(&exchange_rows));

    // ---------- 开台 + 记账 ----------
    let mut fleet: TerminalFleet = TerminalFleet::open(shared_context(), &terminal_codes);
    assert!(
        record_demo_trades(&mut fleet),
        "12 笔演示流水必须全部记进钱箱:记到不存在的终端会被拒绝而不是静默丢弃"
    );

    // ---------- 幕三:机群装配 ----------
    let (fleet_rows, context_texts): (Vec<FleetRowView>, Vec<String>) = build_fleet_rows(&fleet);
    print_lines(&render_fleet_table(&fleet_rows, &context_texts));

    // ---------- 幕四:三档载体对照实验 ----------
    // ★ 快照档必须在**任何变更之前**拷取,否则它拷到的就是变更后的值,
    //   「初值一致」这一列会变成「否」,快照与独立档就此混为一谈。
    let snapshot: SnapshotContext =
        SnapshotContext::capture_from("快照副本档".to_string(), shared_context());
    let isolated: IsolatedContext =
        IsolatedContext::new("独立实例档".to_string(), isolated_initial_state());
    let shared_carrier: &dyn BusinessContextCarrier = fleet.members()[0].context();
    let carriers: Vec<&dyn BusinessContextCarrier> = vec![shared_carrier, &snapshot, &isolated];
    // ★ 快照档拷取时的版本号:此时共享状态还是 revision 0。
    //   (此后交接班 / 刷汇率 / 切店 / 换促销档期各 +1,
    //     于是它与当前版本的差就是「变更过几次」------这个差在幕十被印出来。)
    let isolated_label: String = isolated.label_text().to_string();

    // ★ 交接班的目标班次由**领域函数**算出来,不在这里写死「晚班」:
    //   写死之后,哪天班次全集变了(例如加了第四种班次),
    //   这一处的 `Evening` 不会跟着变,实验就悄悄跑在了旧的制度上。
    let current_shift: ShiftCode = shared_context().read(|state| state.shift_code());
    let next_shift_code: ShiftCode = next_shift(current_shift);
    let probes: Vec<CarrierProbe> = crate::analysis::probe_carriers(&carriers, &|| {
        // 变更动作:**交接班**(换班次 + 换操作员,一次变更一个 revision)。
        // ⚠️ 操作员取自工程外扩展区(第 4 位值班员),
        //    于是「扩展区新增的人自动进共享状态」这件事在幕九之外也有实证。
        service.hand_over(next_shift_code, OPERATOR_EXTENDED);
    });
    print_lines(&render_carrier_table(&crate::analysis::carrier_probe_views(&probes)));

    // ★★ 附加实证:对**快照档**施加一次交接班,共享状态必须纹丝不动。
    //
    // 为什么值得单独做一次:上面的对照实验只证明了「快照没有跟随」,
    // 那是**被动**的(它没动,可能只是因为没人去动它)。
    // 这里**主动**去改它,再确认共享状态不变------
    // 「改不动共享状态」与「没被共享状态改」是两件不同的事,两件都要有证据。
    let shared_before_snapshot_change: String =
        shared_context().read(|state| state.content_fingerprint());
    snapshot.apply_shift_handover(ShiftCode::FullDay, "快照档自己的值班员");
    let shared_after_snapshot_change: String =
        shared_context().read(|state| state.content_fingerprint());

    // ---------- 盘中刷新汇率(为双路径制造差额) ----------
    let refreshed: bool = service.refresh_exchange_rate(HKD, 9_330);
    assert!(refreshed, "港币必须在汇率表里,刷新失败说明装配错了");

    // ---------- 切店到扩展区新增的澳门氹仔店 ----------
    service.switch_store(STORE_MO_01);

    // ---------- 换促销档期(第 4 类营业动作) ----------
    // ★ 前三次变更是「交接班 / 刷汇率 / 切店」,这一次是**换促销档期**。
    //   四类动作都要「一处改、处处见」;只验三类等于默认第四类成立,
    //   而「默认成立」正是这类工程最容易漏掉的地方(第四类往往是后加的)。
    service.advance_promotion();

    // ---------- 幕五:契约审计四条 ----------
    let handles: Vec<&BorgHandle<BusinessContextState>> = fleet.handles();
    let audit: BorgAudit =
        audit_borg_handles(&handles, fleet.terminal_total(), &|state: &BusinessContextState| {
            state.content_fingerprint()
        });
    let audit_views: Vec<AuditRuleView> = audit_rule_views(&audit);
    let audit_summary: AuditSummaryView = audit_summary_view(&audit);
    print_lines(&render_audit_table(
        &audit_views,
        &format!(
            "共 {} 条:通过 {} 条,未通过 {} 条(未通过规则:{})。",
            audit_summary.rule_total(),
            audit_summary.passed_total(),
            audit_summary.failed_total(),
            audit_summary.failed_codes_text()
        ),
    ));

    // ★★ 已知应当失败的用例:**另造一份内容完全相同**的共享状态,
    // 从它发一个句柄混进真博格的句柄组。
    //
    // 为什么要让内容完全相同:若内容也不同,`CONTENT_CONSISTENT` 会跟着失败,
    // 于是「到底是哪条规则抓出来的」就说不清了。
    // 内容一致而仍然被抓出来,才证明 `SINGLE_SHARED_STATE` 这一条**不是摆设**。
    let decoy_state: &'static BorgSharedState<BusinessContextState> =
        Box::leak(Box::new(BorgSharedState::new(
            shared_context().read(|state| state.clone()),
        )));
    let decoy_handle: BorgHandle<BusinessContextState> = decoy_state.spawn_handle();
    let mut mixed_handles: Vec<&BorgHandle<BusinessContextState>> = fleet.handles();
    mixed_handles.push(&decoy_handle);
    let negative_audit: BorgAudit = audit_borg_handles(
        &mixed_handles,
        mixed_handles.len(),
        &|state: &BusinessContextState| state.content_fingerprint(),
    );
    print_lines(&render_audit_negative_table(
        &audit_rule_views(&negative_audit),
        &negative_audit.failed_codes_text(),
    ));

    // ---------- 幕六 / 幕七:钱箱账本 ----------
    let closing_table: ExchangeRateTable =
        shared_context().read(|state| state.exchange_table().clone());
    let ledger: SettlementLedger = SettlementLedger::build(&fleet, &closing_table);
    print_lines(&render_till_table(&ledger));
    print_lines(&render_currency_bucket_table(
        &ledger.native_net_by_currency_total(),
        &ledger.native_net_by_currency_direct(),
        ledger.closing_table(),
    ));

    // ---------- 幕八:价值量化 + 台账快照 ----------
    // ★ 台账在此刻取一次快照;此后(接口清点等)不再重新读取。
    let census: StateCensus = StateCensus::capture(shared_context(), fleet.terminal_total());
    let census_view: CensusView = crate::analysis::census_view(&census);
    let context_bytes: usize = shared_context().read(|state| state.compact_text().len());
    let value_report: BorgValueReport = BorgValueReport::measure(
        fleet.terminal_total(),
        census.handle_total(),
        census.mutation_total(),
        context_bytes,
        census.state_copy_total(),
    );
    print_lines(&render_value_table(&value_report));
    print_lines(&render_census_table(&census_view, "幕八渲染之前(切店与换促销档期之后)"));

    // ---------- 幕九:工程外扩展对照 ----------
    let current_revision: u64 = shared_context().read(|state| state.revision());
    let extension_rows: Vec<ExtensionRowView> = vec![
        ExtensionRowView::new(
            "第 5 台终端 POS-05".to_string(),
            "机群句柄 + 私有钱箱".to_string(),
            true,
            "幕三多一行(句柄 #5)、幕六多一行(钱箱指纹不同)".to_string(),
        ),
        ExtensionRowView::new(
            "澳门元 MOP".to_string(),
            "汇率表 + 币种分桶".to_string(),
            true,
            "幕二多一行、幕七多一行(分桶币种来自实际流水)".to_string(),
        ),
        ExtensionRowView::new(
            "澳门氹仔店".to_string(),
            "门店清单 + 切换门店动作".to_string(),
            true,
            "幕三「共享上下文」一列 5 行同时变成澳门氹仔店".to_string(),
        ),
        ExtensionRowView::new(
            format!("第 4 位操作员 {}", OPERATOR_EXTENDED),
            "交接班 / 值班操作员".to_string(),
            true,
            format!(
                "共享状态版本号已自增到 {}(`StoreService::hand_over` 可再换人)",
                current_revision
            ),
        ),
    ];
    print_lines(&render_extension_table(&extension_rows));

    // ---------- 幕十:接口清点(**真调一遍**) ----------
    print_lines(&render_interface_table(&interface_items(
        business_date,
        &fleet,
        &service,
        &snapshot,
        &ledger,
        &value_report,
        &census_view,
        &probes,
        &audit,
        &isolated_label,
    )));

    // ---------- 幕十一:体检 ----------
    let health: CheckReport = health_check_lines(
        &probes,
        &audit,
        &negative_audit,
        &ledger,
        &value_report,
        &fleet,
        &shared_before_snapshot_change,
        &shared_after_snapshot_change,
    );
    print_lines(&render_check_table(&health));
}

/// 打印一幕(逐行输出)。
fn print_lines(lines: &[String]) {
    for line in lines.iter() {
        println!("{}", line);
    }
}

/// 12 笔演示流水:销售 / 退货 / 回收,人民币 / 港币 / 美元 / 澳门元。
///
/// ★ 每一笔都带上**落笔当时的汇率万分点**:
/// 这是「两条折算路径能互盯」的前提(见 `domain/tender_line.rs`)。
///
/// ⚠️ 返回 `bool` 而不是「记了几笔」:任何一笔记到不存在的终端上都会被拒绝,
/// 而 `TerminalFleet::record` 正是靠返回 `bool` 让这件事显形。
fn record_demo_trades(fleet: &mut TerminalFleet) -> bool {
    let mut all_recorded: bool = true;
    // 一号机:人民币销售 + 港币销售 + 港币退货。
    all_recorded &= fleet.record(
        TERMINAL_POS_01,
        TenderLine::new(
            "T-0001".to_string(),
            TradeKind::Sale,
            CNY,
            Money::new(1_280_000, CNY),
            0,
            10_000,
        ),
    );
    all_recorded &= fleet.record(
        TERMINAL_POS_01,
        TenderLine::new(
            "T-0002".to_string(),
            TradeKind::Sale,
            HKD,
            Money::new(388_000, HKD),
            12_500,
            9_215,
        ),
    );
    all_recorded &= fleet.record(
        TERMINAL_POS_01,
        TenderLine::new(
            "T-0005".to_string(),
            TradeKind::Return,
            HKD,
            Money::new(128_000, HKD),
            4_200,
            9_215,
        ),
    );
    // 二号机:人民币销售 + 美元销售 + 回收旧金。
    all_recorded &= fleet.record(
        TERMINAL_POS_02,
        TenderLine::new(
            "T-0003".to_string(),
            TradeKind::Sale,
            CNY,
            Money::new(268_000, CNY),
            0,
            10_000,
        ),
    );
    all_recorded &= fleet.record(
        TERMINAL_POS_02,
        TenderLine::new(
            "T-0006".to_string(),
            TradeKind::Sale,
            USD,
            Money::new(96_000, USD),
            7_800,
            71_250,
        ),
    );
    all_recorded &= fleet.record(
        TERMINAL_POS_02,
        TenderLine::new(
            "T-0007".to_string(),
            TradeKind::Buyback,
            CNY,
            Money::new(455_000, CNY),
            23_100,
            10_000,
        ),
    );
    // 三号机:人民币销售 + 港币销售。
    all_recorded &= fleet.record(
        TERMINAL_POS_03,
        TenderLine::new(
            "T-0004".to_string(),
            TradeKind::Sale,
            CNY,
            Money::new(880_000, CNY),
            15_600,
            10_000,
        ),
    );
    all_recorded &= fleet.record(
        TERMINAL_POS_03,
        TenderLine::new(
            "T-0008".to_string(),
            TradeKind::Sale,
            HKD,
            Money::new(216_000, HKD),
            9_800,
            9_215,
        ),
    );
    // 四号机:人民币销售 + 港币销售。
    all_recorded &= fleet.record(
        TERMINAL_POS_04,
        TenderLine::new(
            "T-0009".to_string(),
            TradeKind::Sale,
            CNY,
            Money::new(1_560_000, CNY),
            0,
            10_000,
        ),
    );
    all_recorded &= fleet.record(
        TERMINAL_POS_04,
        TenderLine::new(
            "T-0010".to_string(),
            TradeKind::Sale,
            HKD,
            Money::new(74_500, HKD),
            3_200,
            9_215,
        ),
    );
    // ★ 五号机(扩展区新增):澳门元销售 + 人民币销售。
    all_recorded &= fleet.record(
        TERMINAL_POS_05,
        TenderLine::new(
            "T-0011".to_string(),
            TradeKind::Sale,
            MOP,
            Money::new(128_000, MOP),
            5_400,
            MOP_RATE_BASIS_POINTS,
        ),
    );
    all_recorded &= fleet.record(
        TERMINAL_POS_05,
        TenderLine::new(
            "T-0012".to_string(),
            TradeKind::Sale,
            CNY,
            Money::new(336_000, CNY),
            0,
            10_000,
        ),
    );
    all_recorded
}

/// 幕三的行视图与「本机读到的共享上下文」文本。
///
/// ★ 两份数据**同序**:`context_texts[index]` 就是 `fleet_rows[index]` 那台机器的读数。
fn build_fleet_rows(fleet: &TerminalFleet) -> (Vec<FleetRowView>, Vec<String>) {
    let mut rows: Vec<FleetRowView> = Vec::new();
    let mut context_texts: Vec<String> = Vec::new();
    for member in fleet.members().iter() {
        let till = member.till();
        rows.push(FleetRowView::new(
            member.context().terminal_code().code().to_string(),
            member.context().terminal_code().chinese_name().to_string(),
            member.context().handle_sequence().to_string(),
            member_line_count(member).to_string(),
            till.deal_count().to_string(),
            gram_display_text(till.weight_milligrams()),
            till.content_fingerprint(),
        ));
        // ★ 这一列是博格的实证:N 台机器读到的一行文本必须**完全相同**。
        context_texts.push(member.context().read(|state| state.compact_text()));
    }
    (rows, context_texts)
}

/// 幕十:对外只读接口**逐条真调一遍**。
///
/// ⚠️ 每个「输出样例」都是真的跑出来的结果,不是手写的示例文本------
/// 手写的样例会在接口改了之后继续印旧值,那比没有样例更糟。
///
/// ## 分组顺序就是依赖顺序
///
/// 支撑 → 领域 → 机制 → 调用 → 分析 → 表示,与 `main.rs` 顶部的分层图同序。
/// 读者顺着往下读,就是顺着依赖方向走了一遍;
/// 哪一组里出现「支撑层接口返回的值不对」,后面所有组都得重看。
fn interface_items(
    business_date: CalendarDate,
    fleet: &TerminalFleet,
    service: &StoreService,
    snapshot: &SnapshotContext,
    ledger: &SettlementLedger,
    value_report: &BorgValueReport,
    census_view: &CensusView,
    probes: &[CarrierProbe],
    audit: &BorgAudit,
    isolated_label: &str,
) -> Vec<(String, String)> {
    let mut items: Vec<(String, String)> = Vec::new();

    // ---- support ----
    items.push((
        "support::CalendarDate::weekday_chinese_text()".to_string(),
        CalendarDate::new(2026, 10, 8).weekday_chinese_text().to_string(),
    ));
    items.push((
        "support::is_leap_year(2100)  // ★ 用 2100 做探针".to_string(),
        bool_text(is_leap_year(2100)).to_string(),
    ));
    items.push((
        "support::deterministic_code(&[\"CNY\"], 10)".to_string(),
        deterministic_code(&["CNY"], 10),
    ));
    items.push((
        "support::display_width(\"港币\")".to_string(),
        display_width("港币").to_string(),
    ));
    items.push((
        "support::elide_text(\"澳门氹仔店\", 6)".to_string(),
        elide_text("澳门氹仔店", 6),
    ));
    items.push((
        "support::self_check_width_model()".to_string(),
        format!(
            "{} 项,未通过 {} 项",
            self_check_width_model().len(),
            self_check_width_model().iter().filter(|(_, ok, _)| !ok).count()
        ),
    ));
    items.push((
        "support::stable_hash(&[\"CNY\", \"HKD\"])".to_string(),
        stable_hash(&["CNY", "HKD"]).to_string(),
    ));
    items.push((
        "support::prefixed_code(\"FP\", &[\"STORE-CN-01\"], 10)".to_string(),
        prefixed_code("FP", &[STORE_CN_01.code()], 10),
    ));
    items.push((
        "support::pad_center(\"合计\", 10)".to_string(),
        format!("[{}]", pad_center("合计", 10)),
    ));
    items.push((
        "support::truncate_to_width(\"澳门氹仔店澳门氹仔店\", 10)".to_string(),
        truncate_to_width("澳门氹仔店澳门氹仔店", 10),
    ));
    items.push((
        "support::days_in_month(2026, 2) / days_in_month(2024, 2)".to_string(),
        format!("{} / {}(平年 / 闰年)", days_in_month(2026, 2), days_in_month(2024, 2)),
    ));
    items.push((
        "support::days_between(2026-10-08, 2026-10-11)".to_string(),
        days_between(business_date, business_date.add_days(3)).to_string(),
    ));
    items.push((
        "support::report_cell_text(2026-10-08)".to_string(),
        report_cell_text(business_date),
    ));

    // ---- support::CalendarDate(日期值对象的只读口) ----
    items.push((
        "support::CalendarDate::year() / month() / day()".to_string(),
        format!("{} / {} / {}", business_date.year(), business_date.month(), business_date.day()),
    ));
    items.push((
        "support::CalendarDate::is_valid()".to_string(),
        bool_text(business_date.is_valid()).to_string(),
    ));
    items.push((
        "support::CalendarDate::weekday_index()  // 0=周一".to_string(),
        business_date.weekday_index().to_string(),
    ));
    items.push((
        "support::CalendarDate::is_weekend()".to_string(),
        bool_text(business_date.is_weekend()).to_string(),
    ));
    items.push((
        "support::CalendarDate::chinese_text()".to_string(),
        business_date.chinese_text(),
    ));
    items.push((
        "support::CalendarDate::chinese_text_with_weekday()".to_string(),
        business_date.chinese_text_with_weekday(),
    ));
    items.push((
        "support::CalendarDate::add_days(30)".to_string(),
        business_date.add_days(30).machine_text(),
    ));
    items.push((
        "support::CalendarDate::add_working_days(3)".to_string(),
        business_date.add_working_days(3).machine_text(),
    ));

    // ---- domain ----
    items.push((
        "domain::divide_rounded(388000 * 9330, 10000)".to_string(),
        divide_rounded(i128::from(388_000) * i128::from(9_330), 10_000).to_string(),
    ));
    items.push((
        "domain::Money::new(128000, CNY).chinese_text()".to_string(),
        Money::new(128_000, CNY).chinese_text(),
    ));
    items.push((
        "domain::ExchangeRateTable::basis_points_of(HKD)  // 收盘".to_string(),
        match ledger.closing_table().basis_points_of(HKD) {
            Some(points) => basis_points_text(points),
            None => "无汇率".to_string(),
        },
    ));
    items.push((
        "domain::ExchangeRateTable::convert_to_base(MOP$1,280.00)".to_string(),
        match ledger.closing_table().convert_to_base(Money::new(128_000, MOP)) {
            Some(money) => money.chinese_text(),
            None => "不可用".to_string(),
        },
    ));
    items.push((
        "domain::TenderLine::cny_at_trade_rate()  // T-0002 落笔汇率".to_string(),
        fleet
            .member_of(TERMINAL_POS_01)
            .and_then(|member| member.till().lines().get(1))
            .map(|line| line.cny_at_trade_rate().chinese_text())
            .unwrap_or_else(|| "不可用".to_string()),
    ));
    items.push((
        "domain::TerminalTill::native_net_by_currency()  // 一号机".to_string(),
        fleet
            .member_of(TERMINAL_POS_01)
            .map(|member| {
                member
                    .till()
                    .native_net_by_currency()
                    .iter()
                    .map(|(currency, money)| format!("{}{}", currency.code(), money.amount_text()))
                    .collect::<Vec<String>>()
                    .join(" / ")
            })
            .unwrap_or_else(|| "不可用".to_string()),
    ));
    items.push((
        "domain::BusinessContextState::content_fingerprint()".to_string(),
        shared_context().read(|state| state.content_fingerprint()),
    ));
    items.push((
        "domain::BusinessContextState::compact_text()".to_string(),
        shared_context().read(|state| state.compact_text()),
    ));
    items.push((
        "domain::BusinessContextState::revision()".to_string(),
        shared_context().read(|state| state.revision()).to_string(),
    ));
    items.push((
        "domain::Money::zero(CNY) / is_zero()".to_string(),
        format!(
            "{} / {}",
            Money::zero(CNY).amount_text(),
            bool_text(Money::zero(CNY).is_zero())
        ),
    ));
    items.push((
        "domain::Money::plus(¥12.80 + ¥25.00)".to_string(),
        Money::new(1_280, CNY).plus(Money::new(2_500, CNY)).chinese_text(),
    ));
    items.push((
        "domain::TenderLine::trade_rate_basis_points()  // 一号机首笔".to_string(),
        fleet
            .member_of(TERMINAL_POS_01)
            .and_then(|member| member.till().lines().first())
            .map(|line| basis_points_text(line.trade_rate_basis_points()))
            .unwrap_or_else(|| "不可用".to_string()),
    ));
    items.push((
        "domain::BUILTIN_CURRENCY_CODES  // 内置币种全集".to_string(),
        builtin_currency_text(),
    ));
    items.push((
        "domain::BUILTIN_STORE_CODES  // 内置门店全集(含未用到的)".to_string(),
        builtin_store_text(),
    ));
    items.push((
        "domain::STORE_CN_02  // 内置但本场没切过去的门店".to_string(),
        format!("{} {}", STORE_CN_02.code(), STORE_CN_02.chinese_name()),
    ));
    items.push((
        "domain::BUILTIN_TERMINAL_CODES  // 内置终端全集".to_string(),
        builtin_terminal_text(),
    ));
    items.push((
        "domain::BUILTIN_TRADE_KINDS  // 交易种类全集".to_string(),
        builtin_trade_kind_text(),
    ));
    items.push((
        "domain::BUILTIN_SHIFT_CODES  // 班次全集(经营制度,非数据)".to_string(),
        builtin_shift_text(),
    ));
    items.push((
        "domain::next_shift(早班)  // 交接班算下一班,不写死".to_string(),
        next_shift(ShiftCode::Morning).chinese_name().to_string(),
    ));
    items.push((
        "domain::BusinessContextState::business_date()".to_string(),
        shared_context().read(|state| state.business_date().machine_text()),
    ));
    items.push((
        "domain::BusinessContextState::shift_code()".to_string(),
        shared_context().read(|state| format!("{}({})", state.shift_code().chinese_name(), state.shift_code().period_text())),
    ));
    items.push((
        "domain::BusinessContextState::operator_name()  // 扩展区第 4 位".to_string(),
        shared_context().read(|state| state.operator_name().to_string()),
    ));
    items.push((
        "domain::BusinessContextState::tax_rate_basis_points()".to_string(),
        basis_points_text(shared_context().read(|state| state.tax_rate_basis_points())),
    ));
    items.push((
        "domain::BusinessContextState::promotion_version()".to_string(),
        format!("v{}", shared_context().read(|state| state.promotion_version())),
    ));

    // ---- borg ----
    items.push((
        "borg::BorgSharedState::handle_total()".to_string(),
        shared_context().handle_total().to_string(),
    ));
    items.push((
        "borg::BorgSharedState::poison_recoveries()  // 正常为 0".to_string(),
        shared_context().poison_recoveries().to_string(),
    ));
    items.push((
        "borg::BorgHandle::handle_sequence()  // 一号机".to_string(),
        fleet.members()[0].context().handle_sequence().to_string(),
    ));
    items.push((
        "borg::BorgHandle::is_same_state(一号机, 二号机)  // ★ 博格判据".to_string(),
        bool_text(
            fleet.members()[0]
                .context()
                .handle()
                .is_same_state(fleet.members()[1].context().handle())
        )
        .to_string(),
    ));
    items.push((
        "borg::BorgHandle::is_same_handle(一号机, 二号机)  // ★ 不是单例".to_string(),
        bool_text(
            fleet.members()[0]
                .context()
                .handle()
                .is_same_handle(fleet.members()[1].context().handle())
        )
        .to_string(),
    ));
    items.push((
        "borg::StateCensus::summary_text()  // 快照".to_string(),
        census_view.summary_text().to_string(),
    ));
    items.push((
        "borg::BorgHandle::shared_state()  // ★ 句柄只持有引用".to_string(),
        format!(
            "同一槽位,已发句柄 {} 个",
            fleet.members()[0].context().handle().shared_state().handle_total()
        ),
    ));
    items.push((
        "borg::audit_borg_handles()  // 返回 BorgAudit".to_string(),
        format!("{} 条规则", audit.rules().len()),
    ));
    items.push((
        "borg::BorgAuditRule 首条(经 audit_rule_view 转换)".to_string(),
        match audit.rules().first() {
            Some(rule) => {
                let view: AuditRuleView = audit_rule_view(rule);
                format!(
                    "{} → {}(passed={})",
                    view.rule_code(),
                    view.verdict_text(),
                    bool_text(view.passed())
                )
            }
            None => "无".to_string(),
        },
    ));

    // ---- client ----
    items.push((
        "client::CarrierKind::Shared::propagates_change()".to_string(),
        bool_text(CarrierKind::Shared.propagates_change()).to_string(),
    ));
    items.push((
        "client::CarrierKind::Snapshot::propagates_change()".to_string(),
        bool_text(CarrierKind::Snapshot.propagates_change()).to_string(),
    ));
    items.push((
        "client::CarrierKind::Isolated::propagates_change()".to_string(),
        bool_text(CarrierKind::Isolated.propagates_change()).to_string(),
    ));
    items.push((
        "client::TerminalFleet::terminal_total()".to_string(),
        fleet.terminal_total().to_string(),
    ));
    items.push((
        "client::TerminalFleet::handles()  // 句柄清单长度".to_string(),
        fleet.handles().len().to_string(),
    ));
    items.push((
        "client::SnapshotContext::label_text()".to_string(),
        snapshot.label_text().to_string(),
    ));
    items.push((
        "client::SnapshotContext::captured_revision()  // ★ 落后多少".to_string(),
        {
            let current: u64 = shared_context().read(|state| state.revision());
            let captured: u64 = snapshot.captured_revision();
            format!("{}(当前 revision {},落后 {} 版)", captured, current, current - captured)
        },
    ));
    items.push((
        "client::StoreService::describe_text()  // 实时读数".to_string(),
        service.describe_text(),
    ));
    items.push((
        "client::StoreService::shared_state()  // 服务持有的是共享状态".to_string(),
        format!(
            "锁中毒恢复 {} 次(正常为 0)",
            service.shared_state().poison_recoveries()
        ),
    ));
    items.push((
        "client::BusinessContextCarrier::current_state()  // 一号机读数副本".to_string(),
        fleet.members()[0].context().current_state().compact_text(),
    ));
    items.push((
        "client::IsolatedContext::label_text()".to_string(),
        isolated_label.to_string(),
    ));

    // ---- analysis ----
    items.push((
        "analysis::probe_carriers()  // 三步实验".to_string(),
        format!("{} 条观测", probes.len()),
    ));
    items.push((
        "analysis::carrier_probe_views(probes)  // 首条视图".to_string(),
        match crate::analysis::carrier_probe_views(probes).first() {
            Some(view) => format!(
                "{}({})→ {}",
                view.kind_code(),
                view.label_text(),
                view.verdict_text()
            ),
            None => "无".to_string(),
        },
    ));
    items.push((
        "analysis::CarrierProbe::shared_initial_fingerprint()  // 变更前".to_string(),
        probes
            .first()
            .map(|probe| probe.shared_initial_fingerprint().to_string())
            .unwrap_or_else(|| "无".to_string()),
    ));
    items.push((
        "analysis::CarrierProbe::shared_final_fingerprint()  // 变更后".to_string(),
        probes
            .first()
            .map(|probe| probe.shared_final_fingerprint().to_string())
            .unwrap_or_else(|| "无".to_string()),
    ));
    items.push((
        "analysis::SettlementLedger::till_total()".to_string(),
        format!("{} 台", ledger.till_total()),
    ));
    items.push((
        "analysis::SettlementLedger::line_total()".to_string(),
        ledger.line_total().to_string(),
    ));
    items.push((
        "analysis::SettlementLedger::deal_count_total()".to_string(),
        ledger.deal_count_total().to_string(),
    ));
    items.push((
        "analysis::SettlementLedger::weight_milligrams_total()".to_string(),
        gram_display_text(ledger.weight_milligrams_total()),
    ));
    items.push((
        "analysis::TillLedgerEntry::path_two_available()  // 每台都能收盘折算".to_string(),
        bool_text(
            !ledger.entries().is_empty()
                && ledger.entries().iter().all(TillLedgerEntry::path_two_available),
        )
        .to_string(),
    ));
    items.push((
        "analysis::SettlementLedger::path_one_cny_total()".to_string(),
        ledger.path_one_cny_total().chinese_text(),
    ));
    items.push((
        "analysis::SettlementLedger::gap_cny_total()  // ★ 波动影响额".to_string(),
        match ledger.gap_cny_total() {
            Some(money) => money.chinese_text(),
            None => "不可用".to_string(),
        },
    ));
    items.push((
        "analysis::SettlementLedger::currencies()".to_string(),
        ledger
            .currencies()
            .iter()
            .map(CurrencyCode::code)
            .collect::<Vec<&str>>()
            .join("+"),
    ));
    items.push((
        "analysis::BorgValueReport::summary_text()".to_string(),
        value_report.summary_text(),
    ));
    items.push((
        "analysis::BorgValueReport::terminal_total() / context_bytes()".to_string(),
        format!("{} 台 / {} 字节", value_report.terminal_total(), value_report.context_bytes()),
    ));
    items.push((
        "analysis::BorgValueReport::saved_bytes_total()".to_string(),
        format!("{} 字节", byte_text(value_report.saved_bytes_total())),
    ));
    items.push((
        "analysis::BorgValueReport::avoided_sync_total()".to_string(),
        value_report.avoided_sync_total().to_string(),
    ));
    items.push((
        "analysis::CheckReport::failed_items_text()  // 价值量化自检".to_string(),
        value_report.self_check().failed_items_text(),
    ));
    items.push((
        "analysis::CheckReport::line_total() / all_passed()".to_string(),
        {
            let value_check: CheckReport = value_report.self_check();
            format!(
                "{} 条 / {}",
                value_check.line_total(),
                bool_text(value_check.all_passed())
            )
        },
    ));
    items.push((
        "analysis::AuditSummaryView::all_passed()  // 契约审计汇总".to_string(),
        {
            let summary: AuditSummaryView = audit_summary_view(audit);
            format!(
                "{}({} 条中通过 {} 条)",
                bool_text(summary.all_passed()),
                summary.rule_total(),
                summary.passed_total()
            )
        },
    ));

    // ---- app ----
    items.push((
        "app::gram_display_text(12500)".to_string(),
        gram_display_text(12_500),
    ));
    items.push(("app::basis_points_text(9330)".to_string(), basis_points_text(9_330)));
    items.push(("app::bool_text(true)".to_string(), bool_text(true).to_string()));
    items.push(("app::byte_text(1234567)".to_string(), byte_text(1_234_567)));
    items.push((
        "app::section_header(标题, 文档宽度)  // 长度".to_string(),
        format!("{} 列", display_width(&section_header("幕十", DOCUMENT_WIDTH))),
    ));
    items.push((
        "app::table_rule(文档宽度)  // 长度".to_string(),
        format!("{} 列", display_width(&table_rule(DOCUMENT_WIDTH))),
    ));
    items.push((
        "app::INTERFACE_NAME_WIDTH + INTERFACE_OUTPUT_WIDTH + 开销".to_string(),
        format!(
            "{}+{}+7 = {} 列(文档宽度 {})",
            INTERFACE_NAME_WIDTH,
            INTERFACE_OUTPUT_WIDTH,
            INTERFACE_NAME_WIDTH + INTERFACE_OUTPUT_WIDTH + 7,
            DOCUMENT_WIDTH
        ),
    ));

    items
}

/// 内置币种全集的一行文本(`条数:编码 中文名 ...`)。
///
/// ⚠️ 条数由 [`BUILTIN_CURRENCY_CODES`] 自己给出,不写死 `3`:
/// 内置清单将来多一种,这一行不需要有人记得来改。
fn builtin_currency_text() -> String {
    let texts: Vec<String> = BUILTIN_CURRENCY_CODES
        .iter()
        .map(|currency| format!("{} {}", currency.code(), currency.chinese_name()))
        .collect();
    format!("{} 种:{}", texts.len(), texts.join(" / "))
}

/// 内置门店全集的一行文本。
fn builtin_store_text() -> String {
    let texts: Vec<String> = BUILTIN_STORE_CODES
        .iter()
        .map(|store| format!("{} {}", store.code(), store.chinese_name()))
        .collect();
    format!("{} 家:{}", texts.len(), texts.join(" / "))
}

/// 内置终端全集的一行文本。
fn builtin_terminal_text() -> String {
    let texts: Vec<String> = BUILTIN_TERMINAL_CODES
        .iter()
        .map(|terminal| format!("{} {}", terminal.code(), terminal.chinese_name()))
        .collect();
    format!("{} 台:{}", texts.len(), texts.join(" / "))
}

/// 交易种类全集的一行文本。
fn builtin_trade_kind_text() -> String {
    let texts: Vec<String> = BUILTIN_TRADE_KINDS
        .iter()
        .map(|kind| format!("{} {}", kind.code(), kind.chinese_name()))
        .collect();
    format!("{} 种:{}", texts.len(), texts.join(" / "))
}

/// 班次全集的一行文本(**经营制度**,用 `enum` 换编译期穷尽性检查)。
fn builtin_shift_text() -> String {
    let texts: Vec<String> = BUILTIN_SHIFT_CODES
        .iter()
        .map(|shift| format!("{} {}", shift.chinese_name(), shift.period_text()))
        .collect();
    format!("{} 种:{}", texts.len(), texts.join(" / "))
}

/// 取某台终端钱箱里的流水笔数(**显式写出 `FleetMember` 类型**)。
///
/// ⚠️ 这里刻意标注类型而不是让编译器推断:`FleetMember` 是调用层对外暴露的
/// 「**一台终端 = 一个共享句柄 + 一个私有钱箱**」这一对偶关系,
/// 写出来读者才看得见------这正是博格边界的落点。
fn member_line_count(member: &FleetMember) -> usize {
    member.till().line_count()
}

/// 幕十一(版面部分):**排版引擎自检**。
///
/// ## ★ 为什么体检要检排版
///
/// 前面十幕的全部结论都靠这张版面传达。
/// 版面若会静默截断,那么「结论正确」与「读者看到正确」就是两件事------
/// 而后者无法从数据里看出来。
///
/// ## 「已知会失败的输入」分别是什么
///
/// | 检查 | 会让它失败的输入 |
/// |---|---|
/// | `section_header` 补白 | 有人给标题加了额外前缀却没同步补白 |
/// | `table_rule` 长度 | 有人把框线字符改成宽字符却按 1 列算 |
/// | `render_cell` 右对齐 | 把数字列建成 `TableColumn::new`(左对齐) |
/// | `note_lines` 折行 | 折行按续行 2 列留白(首行会溢出 2 列) |
/// | `Table::row_count` | `with_row` 改成去重追加 |
/// | 接口表两列宽 | 单独改了 `INTERFACE_NAME_WIDTH` 却忘了它是反算出来的 |
fn layout_engine_check_lines() -> Vec<CheckLine> {
    let mut lines: Vec<CheckLine> = Vec::new();

    // 一:幕标题必须补白到**正好**文档宽度(否则右边缘会参差)。
    let header_width: usize = display_width(&section_header("版面自检", DOCUMENT_WIDTH));
    lines.push(CheckLine::new(
        "版面:section_header 补白".to_string(),
        format!("{} 列", DOCUMENT_WIDTH),
        format!("{} 列", header_width),
        header_width == DOCUMENT_WIDTH,
    ));

    // 二:横线长度必须等于入参(框线字符若被换成宽字符,这里立刻失败)。
    let rule_width: usize = display_width(&table_rule(DOCUMENT_WIDTH));
    lines.push(CheckLine::new(
        "版面:table_rule 长度".to_string(),
        format!("{} 列", DOCUMENT_WIDTH),
        format!("{} 列", rule_width),
        rule_width == DOCUMENT_WIDTH,
    ));

    // 三:数字列必须**右对齐**,且对齐方式由 `ColumnAlign` 显式给出。
    let numeric_column: TableColumn = TableColumn::numeric("自检", 8);
    let rendered_cell: String = render_cell(&numeric_column, "42");
    let left_padding: usize = rendered_cell.chars().take_while(|character| *character == ' ').count();
    lines.push(CheckLine::new(
        "版面:render_cell 右对齐".to_string(),
        "左补白 6 列".to_string(),
        format!(
            "左补白 {} 列,align=Right 为 {}",
            left_padding,
            bool_text(numeric_column.align() == ColumnAlign::Right)
        ),
        left_padding == 6 && numeric_column.align() == ColumnAlign::Right,
    ));

    // 四:注解折行后**每一行**都不能超过文档宽度。
    let long_note: String = "★ ".repeat(80);
    let wrapped: Vec<String> = note_lines(&long_note, DOCUMENT_WIDTH);
    let overflow_total: usize = wrapped
        .iter()
        .filter(|line| display_width(line) > DOCUMENT_WIDTH)
        .count();
    lines.push(CheckLine::new(
        "版面:note_lines 折行".to_string(),
        format!("折成多行,每行 ≤ {} 列", DOCUMENT_WIDTH),
        format!("{} 行,超宽 {} 行", wrapped.len(), overflow_total),
        wrapped.len() >= 2 && overflow_total == 0,
    ));

    // 五:`row_count()` 必须等于加进去的行数(不含表头)。
    let two_rows: Table = Table::new("版面自检", vec![TableColumn::new("列", 6)])
        .with_row(vec!["甲".to_string()])
        .with_row(vec!["乙".to_string()]);
    lines.push(CheckLine::new(
        "版面:Table::row_count".to_string(),
        "2 行".to_string(),
        format!("{} 行(列数 {})", two_rows.row_count(), two_rows.column_count()),
        two_rows.row_count() == 2 && two_rows.column_count() == 1,
    ));

    // 六:接口表两列宽之和 + 开销必须正好填满文档宽度(它是**反算**出来的)。
    let interface_width: usize = INTERFACE_NAME_WIDTH + INTERFACE_OUTPUT_WIDTH + 3 * 2 + 1;
    lines.push(CheckLine::new(
        "版面:接口表两列宽".to_string(),
        format!("{} 列", DOCUMENT_WIDTH),
        format!("{} 列", interface_width),
        interface_width == DOCUMENT_WIDTH,
    ));

    lines
}

/// 幕十一:把所有「应当 == 实测」收成一组体检项。
fn health_check_lines(
    probes: &[CarrierProbe],
    audit: &BorgAudit,
    negative_audit: &BorgAudit,
    ledger: &SettlementLedger,
    value_report: &BorgValueReport,
    fleet: &TerminalFleet,
    shared_before_snapshot_change: &str,
    shared_after_snapshot_change: &str,
) -> CheckReport {
    let mut report: CheckReport = CheckReport::new();

    // 宽度模型自检(**工具自己也要被检**)。
    let width_probes = self_check_width_model();
    let width_failed: usize = width_probes.iter().filter(|(_, ok, _)| !ok).count();
    report = report.with_line(CheckLine::new(
        "宽度模型自检".to_string(),
        format!("{} 项全过", width_probes.len()),
        format!("{} 项,未通过 {} 项", width_probes.len(), width_failed),
        width_failed == 0,
    ));

    // 契约审计:正常用例必须全过。
    report = report.with_line(CheckLine::new(
        "契约审计四条(正常用例)".to_string(),
        format!("{} 条全过", audit.rules().len()),
        format!(
            "未通过 {} 条({})",
            audit.failed_total(),
            audit.failed_codes_text()
        ),
        audit.all_passed(),
    ));

    // ★★ 审计也要被审计:已知应当失败的用例**必须**至少失败一条。
    report = report.with_line(CheckLine::new(
        "契约审计(已知失败用例)".to_string(),
        "至少 1 条不过".to_string(),
        format!(
            "未通过 {} 条({})",
            negative_audit.failed_total(),
            negative_audit.failed_codes_text()
        ),
        negative_audit.failed_total() >= 1,
    ));

    // 三档载体对照。
    for line in crate::analysis::carrier_check(probes).lines().iter() {
        report = report.with_line(line.clone());
    }

    // 钱箱账本自检。
    for line in ledger.self_check().lines().iter() {
        report = report.with_line(line.clone());
    }

    // ★ 已知会失败的输入:收盘汇率表**故意缺澳门元**,路径二必须整店不可用。
    let incomplete_table: ExchangeRateTable = ExchangeRateTable::new(vec![
        ExchangeRateEntry::new(CNY, 10_000),
        ExchangeRateEntry::new(HKD, 9_330),
        ExchangeRateEntry::new(USD, 71_250),
        // 刻意不配 MOP:这就是「已知会失败的输入」。
    ]);
    let incomplete_ledger: SettlementLedger = SettlementLedger::build(fleet, &incomplete_table);
    let missing: Vec<CurrencyCode> = incomplete_ledger.missing_rate_currencies();
    let missing_text: String = if missing.is_empty() {
        "无".to_string()
    } else {
        missing.iter().map(CurrencyCode::code).collect::<Vec<&str>>().join("+")
    };
    report = report.with_line(CheckLine::new(
        "缺 MOP 汇率时路径二被拒".to_string(),
        "不可用,且缺币种 = MOP".to_string(),
        format!(
            "{},缺币种 = {}",
            match incomplete_ledger.path_two_cny_total() {
                Some(_) => "竟然可用".to_string(),
                None => "不可用".to_string(),
            },
            missing_text
        ),
        incomplete_ledger.path_two_cny_total().is_none() && missing.len() == 1,
    ));

    // 切店之后全店必须同时看到新门店(**博格的实证**)。
    let mut store_consistent: bool = true;
    for member in fleet.members().iter() {
        let store: StoreCode = member.context().read(|state| state.store_code());
        if store != STORE_MO_01 {
            store_consistent = false;
        }
    }
    report = report.with_line(CheckLine::new(
        "切店后全店一致".to_string(),
        format!("{} 台全部读到 {}", fleet.terminal_total(), STORE_MO_01.code()),
        if store_consistent {
            format!("{} 台一致", fleet.terminal_total())
        } else {
            "存在读到旧门店的机器".to_string()
        },
        store_consistent,
    ));

    // 价值量化自检。
    for line in value_report.self_check().lines().iter() {
        report = report.with_line(line.clone());
    }

    // 唯一性:句柄两两不同(**用 ptr::eq,绝不打印地址**)。
    let mut all_distinct: bool = true;
    let handles: Vec<&BorgHandle<BusinessContextState>> = fleet.handles();
    for left in 0..handles.len() {
        for right in (left + 1)..handles.len() {
            if handles[left].is_same_handle(handles[right]) {
                all_distinct = false;
            }
        }
    }
    report = report.with_line(CheckLine::new(
        "句柄两两不同".to_string(),
        "是博格不是单例".to_string(),
        if all_distinct {
            "两两不同".to_string()
        } else {
            "存在同一句柄".to_string()
        },
        all_distinct,
    ));

    // ★★ 改快照档**不影响**共享状态(主动去改,而不是「它没动」)。
    //
    // 已知会失败的输入:把 `SnapshotContext::apply_shift_handover` 写成
    // 「转发给共享状态」------那一瞬间它就变成真博格,这条立刻失败。
    report = report.with_line(CheckLine::new(
        "改快照档不影响共享状态".to_string(),
        "共享指纹不变".to_string(),
        if shared_before_snapshot_change == shared_after_snapshot_change {
            format!("不变({})", shared_after_snapshot_change)
        } else {
            format!("{} → {}", shared_before_snapshot_change, shared_after_snapshot_change)
        },
        shared_before_snapshot_change == shared_after_snapshot_change,
    ));

    // ★ 汇率波动影响额**不得为零**:为零说明两条折算路径重合,双路径实验白跑。
    //
    // 已知会失败的输入:把盘中刷新汇率那一步删掉(或刷成与原来相同的值)。
    let gap_nonzero: bool = match ledger.gap_cny_total() {
        Some(gap) => !gap.is_zero(),
        None => false,
    };
    report = report.with_line(CheckLine::new(
        "波动影响额不为零".to_string(),
        "路径一 ≠ 路径二".to_string(),
        match ledger.gap_cny_total() {
            Some(gap) => format!("{}", gap.chinese_text()),
            None => "不可用".to_string(),
        },
        gap_nonzero,
    ));

    // 每一台终端都必须能按收盘汇率折算(缺任一台的任一币种 → 整店不可用)。
    let all_available: bool = !ledger.entries().is_empty()
        && ledger.entries().iter().all(TillLedgerEntry::path_two_available);
    report = report.with_line(CheckLine::new(
        "所有终端路径二可用".to_string(),
        format!("{} 台全部可用", ledger.till_total()),
        format!(
            "{} 台可用",
            ledger.entries().iter().filter(|entry| entry.path_two_available()).count()
        ),
        all_available,
    ));

    // ★ 营业日**进了内容指纹**:翻牌到次日,指纹必须变。
    //
    // 已知会失败的输入:给 `BusinessContextState` 加了字段却忘了把它写进
    // `content_fingerprint()` 的入参------那时「换了个店/换了个班次但指纹不变」,
    // 幕五的 `CONTENT_CONSISTENT` 会从此恒真。
    let fingerprint_before: String = shared_context().read(|state| state.content_fingerprint());
    let mut rolled_state: BusinessContextState = shared_context().read(|state| state.clone());
    let rolled_date: CalendarDate = shared_context().read(|state| state.business_date()).add_days(1);
    rolled_state.set_business_date(rolled_date);
    let fingerprint_after: String = rolled_state.content_fingerprint();
    report = report.with_line(CheckLine::new(
        "营业日进内容指纹".to_string(),
        "翻牌到次日,指纹改变".to_string(),
        format!(
            "{} → {}(营业日 {})",
            fingerprint_before,
            fingerprint_after,
            rolled_state.business_date().machine_text()
        ),
        fingerprint_before != fingerprint_after,
    ));

    // 换促销档期:版本必须自增,且**所有句柄**都看到新版本。
    let mut promotion_consistent: bool = true;
    let mut promotion_version: u32 = 0;
    for member in fleet.members().iter() {
        let version: u32 = member.context().read(|state| state.promotion_version());
        if promotion_version == 0 {
            promotion_version = version;
        } else if version != promotion_version {
            promotion_consistent = false;
        }
    }
    report = report.with_line(CheckLine::new(
        "换促销档期全店一致".to_string(),
        format!("{} 台读到同一版本", fleet.terminal_total()),
        if promotion_consistent {
            format!("{} 台一致(v{})", fleet.terminal_total(), promotion_version)
        } else {
            "存在读到旧版本的机器".to_string()
        },
        promotion_consistent,
    ));

    // 排版引擎自检(**工具自己也要被检**)。
    for line in layout_engine_check_lines().into_iter() {
        report = report.with_line(line);
    }

    // 唯一舍入口径自检:`divide_rounded(-a,b) == -divide_rounded(a,b)`。
    let positive: i64 = divide_rounded(2_500_000, 10_000);
    let symmetric: bool = divide_rounded(-2_500_000, 10_000) == -positive;
    report = report.with_line(CheckLine::new(
        "舍入口径对称性".to_string(),
        format!("divide_rounded(-2500000,10000) == -{}", positive),
        divide_rounded(-2_500_000, 10_000).to_string(),
        symmetric,
    ));

    report
}

输出:

相关推荐
aqiu1111113 小时前
【C++算法打怪专栏】8. 字符串转换整数 (atoi)
开发语言·c++·算法
爱吃香菜的初学者3 小时前
十四.Linux——消息队列
linux·运维·开发语言
迅猛龙办公室3 小时前
python实现倒序输出数字
开发语言·python
夏幻灵9 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
程序猿编码9 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
苍何9 小时前
开源微信流 Windows,微信聊天记录,可以直接给 Codex 和 Obsidan 了
后端
bkspiderx11 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
代码什么用11 小时前
Spring基础使用
java·后端·spring
程序员老陆11 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计