rust: Simple Factory Pattern(再续)

 

//!# encoding: utf-8 
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:Simple Factory Pattern  简单工厂模式 Creational Patterns 创建型模式
//!# 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/8 21:48 
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : simplefactorypattern
//!# File      : main.rs
//! # 简单工厂模式(Simple Factory Pattern)—— 珠宝检测实验室的委托单创建
//!
//! ## 一句话与三个角色
//!
//! > **一个工厂类,根据传入的参数决定创建哪一种产品类的实例。**
//!
//! 它**不是 GoF 二十三种模式之一**,而是一种编程习惯。三个角色在本工程的落点是:
//!
//! | 角色 | 本工程的落点 |
//! |---|---|
//! | 工厂(Factory) | `factory::TestingOrderFactory`(编译期白名单)/ `factory::TestingOrderRegistry`(运行期登记表) |
//! | 抽象产品(Abstract Product) | `product::TestingOrder` |
//! | 具体产品(Concrete Product) | `product` 层里五个 `pub(super)` 结构体 |
//!
//! ## 本工程不满足于「写一个 match」,也不满足于「写两版看着差不多」
//!
//! 教科书版本的分派长这样:
//!
//! ```text
//! match code {
//!     "A" => Box::new(ProductA::new()),
//!     "B" => Box::new(ProductB::new()),
//!     _   => panic!("unknown"),
//! }
//! ```
//!
//! 它的结构问题是:**分派表与产品实现是两份东西**,必须手动保持同步。
//! 本工程要把这件事**量化**,因此同时挂着两版机制,用**同一段驱动代码**
//! 跑**同一批送检单**,把「扩展时要改几处」与「会不会失配」变成报表上的数字。
//!
//! ## ★ 本工程最重要的一句结论(初稿写错过,被实测纠正)
//!
//! 简单工厂的代价不是「改一行」,而是**「改两处并保持同步」**。
//! 两份清单会以两个方向失配,而**两个方向都不会「没有报错」**——
//! 准确的说法是:
//!
//! | 失配方向 | `supports` 的回答 | 创建的结果 | 判定 |
//! |---|---|---|---|
//! | 白名单有、构造器表没有 | `true`(承诺支持) | 失败 | **自相矛盾** |
//! | 构造器表有、白名单没有 | `false`(不承诺) | 失败 | **对内完全自洽** |
//!
//! 第二行才是真正难发现的:从对外行为看,它和「这个功能本来就没做」
//! **不可区分**。所以那句话的准确版本是——
//! **不是「没有报错」,而是「报的错看起来完全合理」**。
//!
//! 本工程把这句话变成第三幕、第九幕里的两列读数
//! (「声明与行为矛盾」与「跑完整批后未知编码」),
//! 因为**一个更顺口但不准确的表述,比一句笨拙的准确表述危险得多**。
//!
//! ## 严格分层结构
//!
//! ```text
//! ┌──────────────────────────────────────────────────────────────────────────┐
//! │ main.rs            编排:十幕的顺序、工程外扩展、输出自查                    │
//! └───────────────────────────────┬──────────────────────────────────────────┘
//!                                 │
//! ┌───────────────────────────────▼──────────────────────────────────────────┐
//! │ app      排版层    把已算好的值排成定宽文本表(零判断、零 IO)              │
//! │    layout / dispatch_table_report / outcome_report / comparison_report    │
//! │    profile_report / extension_report / health_check_report                │
//! └───────────────────────────────┬──────────────────────────────────────────┘
//!                                 │ 只读结构
//! ┌───────────────────────────────▼──────────────────────────────────────────┐
//! │ analysis 分析层    只读地说出「这批数据意味着什么」                        │
//! │    check_line(结论是一等值)/ creation_ledger_analysis(账本与两版对照)   │
//! │    dispatch_coverage(覆盖率与失配)/ capacity_profile / cost_profile      │
//! └───────────────────────────────┬──────────────────────────────────────────┘
//!                                 │ 只读 WorkbenchRun
//! ┌───────────────────────────────▼──────────────────────────────────────────┐
//! │ client   调用方层  「送检批次 → 工作台驱动 → 逐条快照」这条唯一的业务主干     │
//! │    consignment_batch(输入)/ laboratory_workbench(过程)/ product_snapshot(输出)│
//! └───────────────────────────────┬──────────────────────────────────────────┘
//!                                 │ &dyn OrderCreator
//! ┌───────────────────────────────▼──────────────────────────────────────────┐
//! │ factory  工厂层    两版分派机制:编译期白名单 / 运行期登记表                │
//! │    order_creator(trait)/ testing_order_factory / testing_order_registry │
//! │    creation_error(不知道造什么)/ creation_ledger(创建账本)             │
//! └───────────────────────────────┬──────────────────────────────────────────┘
//!                                 │ 按编码取构造器(ProductBuilder)
//! ┌───────────────────────────────▼──────────────────────────────────────────┐
//! │ product  产品层    抽象产品 + 五个具体产品(类型名 pub(super),外界写不出来)│
//! │    testing_order(trait + ProductBuilder)/ builtin_product_builders       │
//! │    sample_specification / specification_rejection                         │
//! │    specification_guard、item_fee_sum(模块私有,不对外暴露)                │
//! └───────────────────────────────┬──────────────────────────────────────────┘
//!                                 │
//! ┌───────────────────────────────▼──────────────────────────────────────────┐
//! │ domain   领域层    与「谁创建」无关的业务事实(五个开放型标签 + Money/Rate)│
//! │ support  支持层    零业务语义的纯工具(日历 / 文本宽度 / 确定性哈希)        │
//! └──────────────────────────────────────────────────────────────────────────┘
//! ```
//!
//! ### 实测依赖邻接表(`bash scripts/check_layer_dependencies.sh src`)
//!
//! ```text
//! app      -> analysis client domain support
//! analysis -> client domain factory support
//! client   -> domain factory product support
//! factory  -> domain product
//! product  -> domain support
//! domain   -> none
//! support  -> none
//! ```
//!
//! ⚠️ **实测与设计意图有两处偏差,都留档在这里**:
//!
//! 1. `analysis -> factory`:本层初稿期望「连 `factory` 都不需要」,
//!    但 `audit_support_claims` 的入参里有一个 `&dyn OrderCreator`。
//!    这不是顺手引类型,而是**「声明与实际行为是否矛盾」这条审计必须
//!    站在创建者抽象之上才能问**。分叉点揭示了真实的职责边界。
//! 2. `app` **不**依赖 `factory` 与 `product`:报表要印产品目录与两版机制的清单,
//!    实际做法是把它们**以纯数据形态**从本文件传进去
//!    (`app::DispatchEntry` 与 `analysis::CreatorCoverage`)。
//!    于是报表也不认识实现细节——**这既是依赖表更干净,
//!    也意味着这张报表能给工程外的第三方创建者排版**。
//!
//! 两处都不改图,因为**图必须以实测为准,不以设计意图为准**。
//!
//! ### 这七层各自「不许做什么」
//!
//! | 层 | 不许做什么 | 为什么 |
//! |---|---|---|
//! | `support` | 不许有业务语义 | 一旦它认识「检测类型」,它就必须跟着业务改,最底层会天天动 |
//! | `domain` | 不许认识创建者 | 业务事实不因「谁造的」而改变;引用创建相关的东西,扩展性证明当场失效 |
//! | `product` | 不许认识工厂 | 产品给出的只是「构造器」这种被动数据;若产品反向依赖工厂,新增产品就要改产品层里的登记代码 |
//! | `factory` | 不许认识分析/报表 | 工厂只回答「造得出来吗、造了几张」,解释数据是别人的事 |
//! | `client` | 不许认识具体产品 | 五个具体产品是 `pub(super)`,本层**想违反也违反不了**(类型名写不出来) |
//! | `analysis` | 不许修改任何东西 | 入参是值(`Clone + PartialEq`),没有可变引用可用,「探针污染主链」无处可写 |
//! | `app` | 不许做判断、不许 IO | 报表只返回行、不 `println!`;只要报表不做判断,「显示通过却不对」的源头就一定在上层 |
//!
//! ### 三条贯穿全工程的纪律
//!
//! 1. **整数金额、整数比率**:金额一律「整数分」,比率一律「整数万分点」,
//!    乘除用 `i128` 中间量 + 显式四舍五入。因此本工程**全程无浮点**——
//!    报表上每个数字都能被读者用计算器复算。
//! 2. **两次运行逐字节一致**:凡遍历 `HashMap` 的地方一律「先收集再排序」;
//!    输出里**不印任何地址**(函数指针地址受 ASLR 影响);
//!    不用对勾与叉号那两个符号(码点 U+2713 / U+2717,东亚宽度属性是 `A`,
//!    宽度模型按 1 列而终端常渲染 2 列,会让整列错位)——
//!    改用中文词「一致 / 不一致」。⚠️ 连**说明这句话的文字**里也不许出现它们,
//!    否则那条自查会被自己的说明触发(本工程踩过,见幕十续)。
//! 3. **每个数字可复算**:派生指标必须给出分母(`5/5`、占比、平均值);
//!    合计行只对**可以求和的列**求和,百分比列**不能**求和
//!    (本工程实测 50.00% + 35.71% + 14.28% = 99.99%,故合计行填 `—`
//!    并把和值写进注解)。
//!
//! ### 五处「报表看起来一切正常、其实不对」的缺陷(全部留档)
//!
//! 这五处都是**跑出来**才发现的,而它们有一个共同特征:
//! **排版、对齐、页边距都没坏,坏的是数字或文字的含义。**
//! 按性质分三类:
//!
//! **第一类:判据的口径没写准(最危险,因为它会生成一个看起来合理的错数)**
//!
//! | # | 症状 | 根因 | 修法 |
//! |---|---|---|---|
//! | 1 | 「声明与行为矛盾」在**标准工厂**上也有 5 条 | 把「失败」整体当成了「承诺落空」,没有区分失败发生在**哪一层** | 给 `CreationError` 加 `FailureLayer`,只把**工厂层**(不认识编码)的失败计为矛盾;产品层拒绝另立一列交代 |
//!
//! 第 1 条的后果尤其值得记:它让**报表注解与自己正上方的表格对不上**
//! (注解写「反例 B 的矛盾为 0」,表里印着 5)。**一句与相邻数字矛盾的注解,
//! 比没有注解更糟**——读者会开始怀疑整张表。根因是那个「0」**手写**的,
//! 不是算出来的。因此修法有两步:① 判据正确;② 注解里的每个数字都从数据来。
//!
//! **第二类:算式与标签自己就不成立(读者一按计算器就发现)**
//!
//! | # | 症状 | 根因 | 修法 |
//! |---|---|---|---|
//! | 2 | 注解写「15 条 = 标准批次里 14 条内置编码的 + 3 条珍珠」 | 「14」取的是标准批次总条数,而真正的内置编码条数是 12 | 数一遍(`builtin_coded_request_count`),并加一条体检核对「12 + 3 = 15」 |
//! | 3 | 贵金属室平均承诺印 `1.6 天`,读者按计算器得 `1.7` | 整数除法**截断**而非四舍五入;且报表层还自己算了一遍加权平均 | 抽一个 `average_tenths_text` 统一「放大 10 倍后四舍五入」,并把加权平均挪回分析层 |
//! | 4 | 计价口径表的占比列,读者按表头复算对不上 | 表头两次改动各丢一半:先丢分母(`基准费占比`),再丢分子(`占总合计比`) | 表头写成算式本身:`基准费/总合计` |
//!
//! **第三类:结构与自指(存在层,不体现在某一个数上)**
//!
//! | # | 症状 | 根因 | 修法 |
//! |---|---|---|---|
//! | 5 | 「■ 幕五·…」连着印两遍 | 调用方与报表函数**各发了一次**幕标题,理由是调用方注释里写着「该函数不含标题」——那句注释已经过期 | 删掉重复的那次调用(不是去改注释),并加一条「幕标题共 10 条且互不重复」的自查 |
//!
//! 另有两条不属于「缺陷」但同源(都是**自查自己出问题**),记在下面免得下次再撞:
//!
//! - 「输出中不含禁用符号」这条自查,被它**自己的说明文字**触发。
//!   这类**自指陷阱**的处置不是给它开例外(「跳过这一行」会让检查失去全局性),
//!   而是把口径讲清楚:既然禁的是码点本身,那说明就只能用码点描述它;
//! - 「体检表『检查项』列容得下全部名称」这条自查,**第一次运行就抓到了新加的检查**
//!   (新检查的名字 60 列 > 列宽 58 列)。**加检查也可能撑破列宽**,这条自查因此立刻回本。
//!
//! ### 十幕导览
//!
//! | 幕 | 内容 | 回答什么问题 |
//! |---|---|---|
//! | 一 | 工程总览与阅读约定 | 这份输出是怎么组织的 |
//! | 二 | 分派表装配(产品目录 / 项目价目表 / 两版清单对照) | 工厂到底认识多少种产品 |
//! | 三 | 逐条送检结局与运行小结 | 每一张单子发生了什么 |
//! | 四 | 创建账本与归组明细 | 账本自己可信吗 |
//! | 五 | 产能画像(按类型 / 按部门 / 排期明细) | 排产要多少工时、什么时候交 |
//! | 六 | 两版机制逐条与逐字段对照 | 换个分派机制,结果变了吗 |
//! | 七 | 成本画像(构成 / 口径分布) | 收了多少钱,凭什么这么收 |
//! | 八 | 价目基准对照 | 两条独立计价路径对得上吗 |
//! | 九 | 工程外扩展与失配反例 | 「完全可扩展」的证据是什么 |
//! | 十 | 体检报告与输出自查 | 上述每一条依据成立吗 |
//!
//! ⚠️ 注意:下面的说明文字里出现了 `crate::domain`、`crate::product` 这类路径
//! **作为反例**。依赖检查脚本会先剥离 `//` 行注释再匹配,因此不会被误判成真依赖。
 
// ---------------------------------------------------------------------------
// 模块声明
// ---------------------------------------------------------------------------
//
// ⚠️ 这里的顺序**不是**分层顺序(它按字母排)。分层顺序以上面的实测邻接表为准,
// 图表也以实测为准。依赖检查脚本已升级为「从依赖图自身推断层序」,
// 正是为了避免再维护一份会悄悄过期的顺序清单。
mod analysis;
mod app;
mod client;
mod domain;
mod factory;
mod product;
mod support;
 
use crate::analysis::{
    audit_support_claims, build_capacity_profile, build_cost_profile, compare_runs,
    creator_coverage, dispatch_table_mismatch, elide_text, fee_consistency_report, fee_text,
    reconcile_ledger, CapacityProfile, CheckLine, CheckReport, CostProfile, CreatorCoverage,
    DispatchTableMismatch, RunComparison, SupportClaimAudit,
};
use crate::app::{
    key_value_line, note_lines, render_builtin_catalog, render_capacity_table, render_check_table,
    render_cost_composition_table, render_department_table, render_dispatch_notes,
    render_extension_notes, render_extension_table, render_field_table,
    render_ledger_breakdown_table, render_ledger_table, render_mechanism_comparison,
    render_merged_check_table, render_mismatch_evidence_table, render_outcome_table,
    render_per_request_table, render_price_basis_comparison_table, render_pricing_basis_table,
    render_run_summary, render_schedule_table, render_testing_item_catalog, section_header,
    DispatchEntry, ExtensionStage, MismatchEvidence, CHECK_DETAIL_COLUMN_WIDTH,
    CHECK_ITEM_COLUMN_WIDTH,
};
use crate::client::{
    builtin_consignment_batch, carat_text, run_consignment_batch, ConsignmentBatch, WorkbenchRun,
    CODE_DIAMOND_GRADING_TYPO, CODE_PEARL_AUTHENTICATION_REQUEST,
};
use crate::domain::{
    CertificateKind, Currency, Money, PricingBasis, Rate, SampleKind, TestingItem,
    TestingOrderCode, CERTIFICATE_KIND_SPECIAL_REPORT, CURRENCY_CHINESE_YUAN,
    CURRENCY_HONG_KONG_DOLLAR, PRICING_BASIS_PER_ITEM,
};
use crate::factory::{
    CreationError, FailureLayer, OrderCreator, TestingOrderFactory, TestingOrderRegistry,
    BUILTIN_SUPPORTED_ORDER_CODES,
};
use crate::product::{
    builtin_product_builders, builtin_product_count, ProductBuilder, SampleSpecification,
    SpecificationRejection, TestingOrder,
};
use crate::support::{horizontal_rule, CalendarDate};
 
// ---------------------------------------------------------------------------
// 版面常量
// ---------------------------------------------------------------------------
 
/// 文档总宽度(显示列数)。
///
/// ## 这个数字是**算出来的**,不是挑出来的
///
/// 它是「本工程所有表里最宽的那一张」的总宽。`TextTable::total_width()` 的公式是
/// `Σ(列宽 + 2) + 列数 + 1`,把它代进每张表:
///
/// | 表 | 列宽 | 总宽 |
/// |---|---|---|
/// | 逐条送检结局 | 14+20+24+8+26+12 | 123 |
/// | 两版逐条对照 | 14+24+34+34+10 | 132 |
/// | 两版逐字段对照 | 10+22+30+30+10 | 118 |
/// | 失配反例证据 | 30+12+12+12+12+16+20 | **136** ← 最宽 |
/// | 体检表(合并) | 56+8+54 | 128 |
/// | 扩展前后对照 | 10+32+10+10+12+10+12 | 118 |
/// | 两版机制清单对照 | 32+10+12+10+12+12 | 107 |
///
/// **取最宽的那一张**,而不是「看起来差不多」的一个整数:
/// 分隔线的宽度必须 ≥ 任何一张表的宽度,否则`■ 幕标题`下面那条线会短于表,
/// 整份文档看起来像几份拼起来的。
///
/// ⚠️ 本值从 130 涨到 136,是**修截断的直接后果**:三张表分别放宽了
/// 「机制」(16 → 32)、「工厂」(22 → 30)、「构造器表项数」(10 → 12)、
/// 「检查项」(40 → 56)。**列宽从来不是可以省的地方**——
/// 省下来的每一列,代价都是内容被静默切掉一截而报表看起来一切正常。
///
/// 第十幕的输出自查里有一条**永久自检**:
/// `check_rendered_line_widths` 会逐行核验所有渲染结果都不超过本值——
/// 将来新增一张更宽的表,第一次运行就会当场发现。
const DOCUMENT_WIDTH: usize = 136;
 
/// 编码全集的大小上限(用于分派覆盖率体检的展示口径)。
///
/// 真正的全集由 `builtin_product_builders()` 与工程外扩展共同决定,
/// 不需要在这里写死——这个常量只用来给「自查」提供一个人工核对基准。
const ENGINEERING_EXTERNAL_EXTENSION_COUNT: usize = 1;
 
/// 工程外扩展在**送检单**层面的增量:`build_extension_batch` 里手写的那 3 条珍珠单。
///
/// ## 为什么把「3」写成一个常量,而不是散在注解里
///
/// 因为报表注解里有一句「扩展批次 = 内置编码的 N 条 + 3 条珍珠」。
/// 那个「3」若手写在格式化串里,它就和 `build_extension_batch` 里真正的条数
/// 成了两份会各自演化的副本——**本工程刚因为注解手写数字吃过一次亏**
/// (幕九的注解写「反例 B 的矛盾为 0」,而同一张表里印着 5)。
///
/// 常量可以让两处**必然一致**;再加一条体检(扩展批次条数 = 内置编码条数 + 本常量)
/// 就能让「忘了改常量」也在第一次运行时被抓到。
const ENGINEERING_EXTERNAL_PEARL_REQUEST_COUNT: usize = 3;
 
// ===========================================================================
// 工程外扩展区
// ===========================================================================
//
// ## 这一段代码的位置就是本工程的论据
//
// 它**不在任何分层目录里**,全部住在 `main.rs`(工程之外)。
// 「珍珠鉴定」是一种全新的检测类型:新的编码、新的样品类别、
// 两个新的检测项目、新的计价政策。把它做出来之后,
//
// - **运行期登记表版**:写一行 `register(..)` 就接上了,分层目录零改动;
// - **编译期白名单版**:做不到——必须去改 `factory` 的白名单数组(还要改长度)
//   与 `product` 的构造器表,两处、三层、且必须手动保持同步。
//
// 这就是「完全可扩展」这句主张的**唯一的证据形式**:
// 不是声明「本设计很可扩展」,而是**在工程外面真的加了一种产品,
// 然后让报表自己说它被纳入了统计**。
//
// ## ⚠️ 校验必须自己写:`specification_guard` 是层内私有的
//
// `product` 层的 `specification_guard` 与 `item_fee_sum` 声明为**模块私有**
// (`mod` 而非 `pub mod`)。这是刻意的:它们是本层内部的**共用口径**,
// 不是对外契约——若把它们公开,上层就可能各自调用它们再拼出产品,
// 而那正是产品层要阻止的旁路。
//
// 因此扩展方只能做两件事:① 用公开 API 读规格;
// ② 自己构造 `SpecificationRejection` 来表达拒绝。
// 这个「不方便」恰好是边界清晰的代价与标志。
 
/// 工程外扩展:检测项目「珠层厚度」,单价 ¥220.00。
///
/// ## 为什么新检测项目不需要改 `domain` 层
///
/// 因为 `TestingItem` 是 `const fn new(..)` 的**开放型结构体**,
/// 不是枚举。枚举要求「所有可能值都写在本文件里」,
/// 于是实验室每加一个项目就要改领域层——那等于说领域层认识全世界所有检测项目。
/// 本工程的口径是:**凡是需要工程外扩展的维度,一律用开放型结构体。**
///
/// 单价挂在项目自己身上(`TestingItem::item_fee`),
/// 因此「按项目数计价」的产品只需遍历项目求和,完全不必认识任何具体项目。
const TESTING_ITEM_NACRE_THICKNESS: TestingItem = TestingItem::new(
    "NACRE_THICKNESS",
    "珠层厚度",
    Money::from_minor_units(22_000, CURRENCY_CHINESE_YUAN),
);
 
/// 工程外扩展:检测项目「光泽度」,单价 ¥180.00。
const TESTING_ITEM_LUSTER: TestingItem = TestingItem::new(
    "LUSTER",
    "光泽度",
    Money::from_minor_units(18_000, CURRENCY_CHINESE_YUAN),
);
 
/// 工程外扩展:珍珠鉴定的检测类型编码。
///
/// 它**不是** `domain` 层的内置常量,而是扩展方自己的 `const`。
/// 这正是「开放型标签」的价值:新增编码**不需要改领域层**。
/// 若 `TestingOrderCode` 是枚举,这里就必须去 `domain` 里加一个变体,
/// 「扩展零改动」当场失效。
const ORDER_CODE_PEARL_IDENTIFICATION: TestingOrderCode =
    TestingOrderCode::new("PEARL_IDENTIFICATION", "珍珠鉴定");
 
/// 工程外扩展:珍珠的样品类别。
const SAMPLE_KIND_PEARL: SampleKind = SampleKind::new("PEARL", "珍珠");
 
/// 工程外扩展:珍珠鉴定包含的项目(两项合计 ¥400.00)。
///
/// 以 `const` 数组而非内联字面量:产品与价目对照表都要拿这份清单,
/// 内联会让「同一份清单出现两处」——这在本工程里是被明令禁止的失效方式。
const PEARL_REQUIRED_ITEMS: [TestingItem; 2] =
    [TESTING_ITEM_NACRE_THICKNESS, TESTING_ITEM_LUSTER];
 
/// 工程外扩展:标准出证工作日 4 天。
const PEARL_TURNOVER_DAYS: u16 = 4;
 
/// 工程外扩展:加急费率 25%。
///
/// ## 刻意与内置产品不同(内置是 30%),为什么
///
/// 因为「扩展方带来自己的计价政策」是「真扩展」与「改个名字的复制品」的分水岭。
/// 如果扩展产品的费率、工期、件数上限全部与某个内置产品相同,
/// 那它只是内置产品的一个别名,证明不了扩展能力。
/// 25% 与 4 天与 50 件,都是**扩展方自己的业务参数**。
const PEARL_URGENCY_RATE: Rate = Rate::from_percent(25);
 
/// 工程外扩展:单张委托单允许的最大件数。
const PEARL_MAXIMUM_PIECE_COUNT: u16 = 50;
 
/// 工程外扩展:珍珠鉴定委托单(具体产品)。
///
/// ## 它与内置的五个产品**地位完全平等**
///
/// 它实现同一个 [`TestingOrder`] trait,因此:
/// - 工厂能造它(`create` 返回 `Box<dyn TestingOrder>`,与内置产品同型);
/// - 快照能投影它(`snapshot_order` 走公开接口,不认识具体类型);
/// - 覆盖率、账本、产能、成本四张报表**自动**把它纳入统计,一行报表代码都不用改。
///
/// 差别只在**可见性**:内置五个产品的类型名是 `pub(super)`,
/// 而本类型就在 `main.rs` 里,看得见。但「看得见」不等于「可以不经过工厂」——
/// 它要进入报表,仍然**必须**先注册进创建者,再由驱动代码调用 `create`。
struct PearlIdentificationOrder {
    /// 样品编号(来自规格,原样保存)。
    sample_code: String,
    /// 送检客户。
    applicant: String,
    /// 件数。
    piece_count: u16,
    /// 是否加急。
    urgent: bool,
    /// 基准检测费(受理那一刻的价格快照)。
    ///
    /// ## 与内置产品同样的理由:构造时一次算定
    ///
    /// 「基准费」是价格快照。若每次 `base_fee()` 都重算,将来调价之后
    /// 已经受理的历史委托单在报表上会显示新价格——历史记录被**追溯改写**。
    base_fee: Money,
}
 
impl TestingOrder for PearlIdentificationOrder {
    fn order_code(&self) -> TestingOrderCode {
        ORDER_CODE_PEARL_IDENTIFICATION
    }
 
    fn sample_code(&self) -> &str {
        &self.sample_code
    }
 
    fn applicant(&self) -> &str {
        &self.applicant
    }
 
    fn handling_department(&self) -> &'static str {
        // 新部门:报表「按部门分组」那张表会自动多出一行,
        // 而那张表的代码一行都没改。
        "珍珠室"
    }
 
    fn sample_kind(&self) -> SampleKind {
        SAMPLE_KIND_PEARL
    }
 
    fn pricing_basis(&self) -> PricingBasis {
        // 与内置的「宝石鉴定 / 玉石鉴定」同一个口径:按项目数计价。
        // 复用口径而不是新造一个,说明「口径」是一个**可共享的分类维度**,
        // 而「检测类型」才是产品身份。
        PRICING_BASIS_PER_ITEM
    }
 
    fn certificate_kind(&self) -> CertificateKind {
        CERTIFICATE_KIND_SPECIAL_REPORT
    }
 
    fn currency(&self) -> Currency {
        CURRENCY_CHINESE_YUAN
    }
 
    fn base_fee(&self) -> Money {
        self.base_fee
    }
 
    fn urgency_rate(&self) -> Rate {
        // 价目表上的承诺费率,与「这一单是否加急」无关。
        PEARL_URGENCY_RATE
    }
 
    fn loss_rate(&self) -> Rate {
        // 非破坏性检测:损耗费率恒为 0%,走同一条运算路径(不写 if)。
        Rate::zero()
    }
 
    fn turnover_days(&self) -> u16 {
        PEARL_TURNOVER_DAYS
    }
 
    fn is_destructive(&self) -> bool {
        false
    }
 
    fn is_urgent(&self) -> bool {
        self.urgent
    }
 
    fn piece_count(&self) -> u16 {
        self.piece_count
    }
 
    fn carat_millis(&self) -> i64 {
        // 珍珠不按克拉计价。
        0
    }
 
    fn required_items(&self) -> &[TestingItem] {
        &PEARL_REQUIRED_ITEMS
    }
}
 
/// 由规格构造一张珍珠鉴定委托单。
///
/// 参数 `specification`:样品规格。
/// 返回:合格时返回产品,否则返回拒绝原因。
///
/// ## 为什么这里的前四行不能写成一句 `require_sample_kind(..)?`
///
/// 因为 `specification_guard` 是 `product` 层的**模块私有**函数,
/// 工程外拿不到。这不是不便,而是**边界清晰**:那三个守卫是本层内部的
/// 共用口径,公开它等于允许上层绕过产品自己拼产品。
///
/// 扩展方因此必须自己写这三行校验。代价是几行重复代码,
/// 收益是「产品层内部的约定不外泄」——同时它也**证明**了
/// 「扩展方只需要公开 API」,而不是需要框架给一堆内部钩子。
fn build_pearl_identification_order(
    specification: &SampleSpecification,
) -> Result<Box<dyn TestingOrder>, SpecificationRejection> {
    // ① 样品必须是珍珠。
    if specification.sample_kind() != SAMPLE_KIND_PEARL {
        return Err(SpecificationRejection::SampleKindMismatch {
            expected: SAMPLE_KIND_PEARL,
            actual: specification.sample_kind(),
        });
    }
    // ② 件数必须在 [1, 50]。
    let piece_count: u16 = specification.piece_count();
    if piece_count < 1 || piece_count > PEARL_MAXIMUM_PIECE_COUNT {
        return Err(SpecificationRejection::ParameterOutOfRange {
            parameter: "piece_count",
            parameter_chinese_name: "样品件数",
            value: piece_count as i64,
            minimum: 1,
            maximum: PEARL_MAXIMUM_PIECE_COUNT as i64,
        });
    }
 
    // ③ 计价:按项目数。各项目单价之和——**本函数不认识任何具体项目的单价**,
    //    单价挂在项目自己身上(`TestingItem::item_fee`)。
    //    这里刻意不用 `product::item_fee_sum::sum_item_fees`(模块私有),
    //    而是展开写一遍:它顺带证明了「按项目数计价」这个口径本身不需要框架支持。
    let mut base_fee: Money = Money::zero(CURRENCY_CHINESE_YUAN);
    for item in PEARL_REQUIRED_ITEMS.iter() {
        base_fee = base_fee
            .add(&item.item_fee())
            .unwrap_or(base_fee);
    }
 
    Ok(Box::new(PearlIdentificationOrder {
        sample_code: specification.sample_code().to_string(),
        applicant: specification.applicant().to_string(),
        piece_count,
        urgent: specification.is_urgent(),
        base_fee,
    }))
}
 
/// 工程外扩展:把构造器转成 `ProductBuilder` 函数指针。
///
/// ## 为什么要多这一层薄包装
///
/// `ProductBuilder` 的类型是 `fn(&SampleSpecification) -> Result<..>`,
/// 而 `build_pearl_identification_order` 恰好就是那个签名,本可**直接强转**:
/// `build_pearl_identification_order as ProductBuilder`。
///
/// 这里仍然写成一个具名常量,理由有两条:
/// 1. 让「扩展方交给创建者的东西」有一个**可搜索的名字**(`PEARL_BUILDER`),
///    报表与文档里引用它比引用一个长函数名清楚;
/// 2. 若将来构造器需要改为泛型或改名,改动集中在这一行。
const PEARL_BUILDER: ProductBuilder = build_pearl_identification_order;
 
/// 工程外扩展:构造器表(内置) + 珍珠构造器。
///
/// 这个函数**不修改任何分层文件**:内置那一份来自
/// `product::builtin_product_builders()`,珍珠那一项由本文件追加。
fn extended_product_builders() -> Vec<(TestingOrderCode, ProductBuilder)> {
    let mut builders: Vec<(TestingOrderCode, ProductBuilder)> = builtin_product_builders();
    builders.push((ORDER_CODE_PEARL_IDENTIFICATION, PEARL_BUILDER));
    builders
}
 
/// 工程外扩展:手工「扩表」后的白名单(内置 5 项 + 珍珠 1 项)。
///
/// ## 它模拟的是「假如白名单版要支持珍珠,维护者必须做的事」
///
/// 注意它是**本文件自己拼的**,不是 `factory` 层常量的一部分。
/// `factory::BUILTIN_SUPPORTED_ORDER_CODES` 仍然是 5 项,一行未改——
/// 因为本工程不能为了演示而偷偷改掉被考察的对象。
/// 报表上这两行的差别(5 项 vs 6 项,改动层数 0 vs 2)
/// 才是本工程要说的那句「扩展成本」。
fn manually_extended_whitelist() -> Vec<TestingOrderCode> {
    let mut codes: Vec<TestingOrderCode> = BUILTIN_SUPPORTED_ORDER_CODES.to_vec();
    codes.push(ORDER_CODE_PEARL_IDENTIFICATION);
    codes
}
 
/// 编码全集 = 内置构造器表的编码 ∪ 工程外扩展的编码。
///
/// ## 为什么它由调用方给出,而不是由「某个工厂」回答
///
/// 因为它是一个**策略输入**:「应当认识哪些编码」是业务决定,
/// 不是任何一版机制能自己回答的。正因如此,
/// `analysis::creator_coverage` 才能拿它去质询**任意**创建者——
/// 包括工程外构造的反例工厂。
fn code_universe() -> Vec<TestingOrderCode> {
    extended_product_builders()
        .iter()
        .map(|(order_code, _)| *order_code)
        .collect()
}
 
// 编译期自查:工程外扩展区的组成必须与文档里写的一致。
// 这类断言的价值不在运行期防呆,而在于**把文档里的一句话变成可执行的声明**。
const _: () = {
    assert!(
        ENGINEERING_EXTERNAL_EXTENSION_COUNT == 1,
        "工程外扩展目前恰好是 1 种(珍珠鉴定);改了这里,第九幕的申报值也要改"
    );
    assert!(
        PEARL_REQUIRED_ITEMS.len() == 2,
        "珍珠鉴定含两个项目(珠层厚度、光泽度),合计 ¥400.00"
    );
    // 两个项目单价之和 = ¥400.00 = 40000 分(在 const 上下文里人工核算一次)。
    assert!(
        TESTING_ITEM_NACRE_THICKNESS.item_fee().minor_units() == 22_000
            && TESTING_ITEM_LUSTER.item_fee().minor_units() == 18_000,
        "两个项目的单价必须是 ¥220.00 与 ¥180.00(合计 ¥400.00)"
    );
};
 
// 供 `manually_extended_whitelist` 使用的一处再导出检查:
// 内置白名单长度为 5 这件事必须在编译期成立,否则「扩展前后 5 → 6」这个
// 对照就不再是 5 → 6。写成 const 断言而不是运行期检查,是为了让它
// **在编译时就拦住一次错误的对照实验**。
const _: () = {
    assert!(
        BUILTIN_SUPPORTED_ORDER_CODES.len() == 5,
        "内置白名单是 5 项;若它变了,第九幕的申报值与文档都要跟着改"
    );
};
 
// ===========================================================================
// 编排
// ===========================================================================
 
/// 十幕编排。
///
/// ## ★ 一条必须遵守的纪律:每个用途各建一条链
///
/// 创建者的账本与登记表都是**只增不减**的(观测、计数、登记项都不清空)。
/// 因此「同一个创建者跑第二批送检单」会把两批的账混在一起——
/// 在 Proxy 工程里,这条疏忽导致扫描笔数 11 vs 期望 10,
/// 并在重算时触发 `debug_assert_eq!` 当场 panic。
///
/// 所以下面的写法是:**凡是要跑一批送检单,就现建一个创建者**。
/// 每个 `run_consignment_batch` 调用的第一个实参,都是一个刚 new 出来的对象。
fn main() {
    let mut lines: Vec<String> = Vec::new();
 
    // ------------------------- 幕一:工程总览 -------------------------
    lines.extend(render_overview());
 
    // 两批输入。
    //   标准批次:14 条,全部使用内置编码,覆盖 5 种结局。
    //   扩展批次:15 条 = 标准批次里 12 条内置编码的 + 3 条珍珠扩展。
    let standard_batch: ConsignmentBatch = builtin_consignment_batch();
    let extension_batch: ConsignmentBatch = build_extension_batch();
 
    // 内置编码全集(5 种):用于「两版机制认识多少种」的对照。
    let builtin_universe: Vec<TestingOrderCode> = BUILTIN_SUPPORTED_ORDER_CODES.to_vec();
 
    // --------------------- 创建者(标准批次专用) ---------------------
    // 每个创建者只用一次,理由见本函数文档的「每个用途各建一条链」。
    let whitelist_factory: TestingOrderFactory = TestingOrderFactory::builtin();
    let registry_creator: TestingOrderRegistry = TestingOrderRegistry::builtin();
 
    // ------------------- 幕二:分派表装配(产品目录等) -------------------
    // 幕二的小节标题由 `render_builtin_catalog` 内部发出(它是本幕的第一张表),
    // 后面三张表跟在它下面。这样切分的原因是:**幕标题只印一次**,
    // 若每张表各自印一个幕标题,读者会以为那是四幕。
    let catalog_entries: Vec<DispatchEntry> = builtin_product_builders()
        .iter()
        .map(|(order_code, _builder)| {
            DispatchEntry::new(order_code.code(), order_code.chinese_name())
        })
        .collect();
    lines.extend(render_builtin_catalog(&catalog_entries, DOCUMENT_WIDTH));
    // 项目价目表:从 domain 的项目总表出发,而不是从各产品反推。
    lines.extend(render_testing_item_catalog(
        &crate::domain::BUILTIN_TESTING_ITEMS,
        DOCUMENT_WIDTH,
    ));
    // 两版机制的清单对照:清单份数由机制自己回答(`dispatch_list_count`)。
    let coverage_whitelist = creator_coverage(&whitelist_factory, &builtin_universe);
    let coverage_registry = creator_coverage(&registry_creator, &builtin_universe);
    lines.extend(render_mechanism_comparison(
        &[coverage_whitelist.clone(), coverage_registry.clone()],
        builtin_universe.len(),
        DOCUMENT_WIDTH,
    ));
    lines.extend(render_dispatch_notes(DOCUMENT_WIDTH));
 
    // ------------------- 幕三:逐条结局(标准批次 × 白名单版) -------------------
    // 幕三的小节标题由 `render_outcome_table` 内部发出。
    let whitelist_run: WorkbenchRun = run_consignment_batch(&whitelist_factory, &standard_batch);
    lines.extend(render_outcome_table(&whitelist_run, DOCUMENT_WIDTH));
    lines.extend(render_run_summary(&whitelist_run, DOCUMENT_WIDTH));
 
    // ------------------- 幕四:创建账本与归组明细 -------------------
    lines.extend(section_header("幕四·创建账本与归组明细", DOCUMENT_WIDTH));
    lines.extend(render_ledger_table(&whitelist_run, DOCUMENT_WIDTH));
    lines.extend(render_ledger_breakdown_table(&whitelist_run, DOCUMENT_WIDTH));
 
    // --------- 幕五 / 幕六:两版机制对照(同一批,只换分派表) ---------
    // 登记表版**必须另建一个创建者**:上一步的白名单工厂账本里已经有 14 笔,
    // 拿它再跑一遍就会把两批混在一起。
    let registry_run: WorkbenchRun = run_consignment_batch(&registry_creator, &standard_batch);
    // 幕五:逐条对照。**标题由 `render_per_request_table` 内部发出**——
    // 这里曾经也自己发了一次(当时的注释写着「该函数不含标题」,而那句已经过期),
    // 于是输出里「■ 幕五·…」连着印了两遍。删掉这一处调用,而不是去改注释:
    // **能删的重复调用,比需要维护的注释可靠。**
    let comparison = compare_runs(&whitelist_run, &registry_run);
    lines.extend(render_per_request_table(&comparison, DOCUMENT_WIDTH));
    // 幕六:逐字段对照(标题同样由 `render_field_table` 内部发出)。
    lines.extend(render_field_table(&comparison, DOCUMENT_WIDTH));
 
    // ------------------- 幕七:排期与产能画像 -------------------
    // 标题由 `render_capacity_table` 内部发出。
    // 两版结果在幕五已证明一致,因此画像只用白名单版那一份即可——
    // 若两版不一致,幕六会先报出来,这里的画像才有必要重做。
    let capacity = build_capacity_profile(&whitelist_run);
    lines.extend(render_capacity_table(&capacity, DOCUMENT_WIDTH));
    lines.extend(render_department_table(&capacity, DOCUMENT_WIDTH));
    lines.extend(render_schedule_table(&capacity, DOCUMENT_WIDTH));
 
    // ------------------- 幕八:成本画像 -------------------
    // 标题由 `render_cost_composition_table` 内部发出。
    let cost = build_cost_profile(&whitelist_run);
    lines.extend(render_cost_composition_table(&cost, DOCUMENT_WIDTH));
    lines.extend(render_pricing_basis_table(&cost, DOCUMENT_WIDTH));
    lines.extend(render_price_basis_comparison_table(&cost, DOCUMENT_WIDTH));
 
    // ------------------- 幕九:工程外扩展与失配反例 -------------------
    // 本幕返回两部分:要打印的文本行,以及**一份体检报告**——
    // 后者进幕十合并。这样「扩展后被纳入统计」这件事不只是叙述,
    // 而是一条会失败的检查(它是本工程「完全可扩展」主张的判据)。
    let (extension_lines, extension_report) =
        run_extension_experiment(&standard_batch, &extension_batch);
    lines.extend(extension_lines);
 
    // ------------------- 幕十:体检报告与输出自查 -------------------
    let mut health_reports: Vec<CheckReport> = vec![
        reconcile_ledger(&whitelist_run),
        capacity.to_check_report(),
        cost.to_check_report(),
        fee_consistency_report(&whitelist_run),
        comparison.to_check_report(),
        extension_report,
        creator_coverage(&whitelist_factory, &builtin_universe).to_check_report(),
        dispatch_table_mismatch(
            "编译期白名单工厂(标准)",
            &whitelist_factory.supported_codes(),
            &whitelist_factory.builder_codes(),
        )
        .to_check_report(),
        audit_support_claims(&whitelist_factory, &whitelist_run).to_check_report(),
        audit_support_claims(&registry_creator, &registry_run).to_check_report(),
    ];
    // 只读口径清点:把**全部对外只读接口**真实调用一次。
    // 它同时解决两件事:① 证明这些接口确实可用(不是死代码);
    // ② 把值层的完整只读契约摆在一张表里,供后来者核对「还能问出什么」。
    health_reports.push(read_only_surface_inventory(
        &whitelist_factory,
        &registry_creator,
        &whitelist_run,
        &registry_run,
        &capacity,
        &cost,
        &comparison,
        &creator_coverage(&whitelist_factory, &builtin_universe),
        &standard_batch,
    ));
    lines.extend(render_merged_check_table(
        "幕十·体检报告",
        &health_reports,
        DOCUMENT_WIDTH,
    ));
 
    // 输出自查**放在最后**,这样它能检查到上面全部内容(含幕十的体检表)。
    lines.extend(run_self_check(&lines, &whitelist_run, &health_reports));
 
    // ------------------- 收尾结论 -------------------
    lines.extend(render_conclusion(&whitelist_run, &registry_run));
 
    // 渲染结果的宽度在自查里已逐行核验;这里只负责一次性输出。
    for line in &lines {
        println!("{}", line);
    }
}
 
/// 幕一:工程总览与阅读约定。
///
/// ## 为什么把「怎么读这份输出」也印出来
///
/// 本工程的输出有十幕、二十来张表。读者(尤其是不熟悉这份代码的人)
/// 需要的不是更多数据,而是**一份导读**:这一幕回答什么问题、
/// 哪些数字是算出来的、哪些是人工申报的。
///
/// 尤其重要的是最后那条:**「改动层数 / 改动文件数」是人工申报值**。
/// 把它说清楚,比让它混在数据里看起来像实算值要好得多。
fn render_overview() -> Vec<String> {
    let mut lines: Vec<String> = section_header("幕一·工程总览与阅读约定", DOCUMENT_WIDTH);
 
    lines.push(key_value_line(
        "模式",
        "简单工厂(Simple Factory)—— 本工程挂两版分派机制:编译期白名单 / 运行期登记表",
        DOCUMENT_WIDTH,
    ));
    lines.push(key_value_line(
        "产品",
        "珠宝检测实验室的检测委托单(素金 / 银饰 / 钻石 / 宝石 / 玉石)",
        DOCUMENT_WIDTH,
    ));
    lines.push(key_value_line(
        "标准批次",
        "受理日 2026-09-03(周四),14 条,覆盖 5 种结局",
        DOCUMENT_WIDTH,
    ));
    lines.push(key_value_line(
        "扩展批次",
        "同一受理日,15 条 = 12 条内置编码 + 3 条工程外的「珍珠鉴定」",
        DOCUMENT_WIDTH,
    ));
    lines.push(key_value_line(
        "金额口径",
        "全程整数分(¥1.00 = 100 分),比率用整数万分点,无浮点",
        DOCUMENT_WIDTH,
    ));
    lines.push(key_value_line(
        "核心命题",
        "简单工厂的代价不是「改一行」,而是「改两处并保持同步」",
        DOCUMENT_WIDTH,
    ));
    lines.push(key_value_line(
        "核心读数",
        "「清单份数」2 vs 1;「声明与行为矛盾」反例 A > 0、反例 B = 0",
        DOCUMENT_WIDTH,
    ));
 
    lines.extend(note_lines(
        "阅读提示:本工程刻意**不印任何内存地址**(函数指针地址受 ASLR 影响,\
         一印进去「两次运行逐字节一致」就当场失效),也不用对勾与叉号那两个符号\
         (码点 U+2713 / U+2717,东亚宽度属性是 A,宽度模型按 1 列算而终端常按 2 列渲染,\
         会让整列错位)——一致性结论一律用中文词「一致 / 不一致」表达。\
         ⚠️ 这里刻意**只写码点、不写符号本身**:本幕之后的输出自查会逐行扫描那两个字符,\
         而这段说明也要被扫到——只要说明书里印出符号,那条自查就会被它自己触发。",
        DOCUMENT_WIDTH,
    ));
    lines.extend(note_lines(
        "⚠️ 第九幕的「改动层数」「改动文件数」两列是**人工申报值**,不是算出来的:\
         报表不读版本控制,无法自动回答「本次改动碰了几个文件」。\
         它们之所以可信,是因为读者可以核对——`grep -n \"register(\" src/main.rs` \
         就能确认登记表版的扩展确实只在调用方改了一行。",
        DOCUMENT_WIDTH,
    ));
    lines.push(String::new());
    lines.push(horizontal_rule(DOCUMENT_WIDTH));
 
    lines
}
 
/// 构造「工程外扩展批次」:15 条。
///
/// ## 组成方式,以及为什么按编码筛选而不是按序号切片
///
/// 前 12 条来自内置批次里**编码在白名单内**的那些请求。
/// 用「编码是否在白名单内」作为筛选条件,而不是 `take(12)`:
/// 前者表达的是**意图**(「我要那些内置编码能造出来的请求」),
/// 后者表达的是**一个位置假设**——一旦有人调整了内置批次的排列,
/// `take(12)` 会悄悄把两条未知编码的请求也拿进来,
/// 而那正好会让第九幕的读数全部错位。
///
/// 后 3 条是珍珠扩展:**2 条能造出来(常规 / 加急)、1 条参数越界**。
/// 第三条刻意越界,是为了证明扩展产品**自带校验**——
/// 若它照单全收,那它只是一个把输入原样包起来的壳子。
fn build_extension_batch() -> ConsignmentBatch {
    let standard_batch: ConsignmentBatch = builtin_consignment_batch();
    let mut batch: ConsignmentBatch = ConsignmentBatch::new(
        "工程外扩展批次",
        // 与标准批次同一个受理日:两批的排期才有可比性。
        standard_batch.accepted_on(),
    );
 
    for request in standard_batch.requests() {
        // 只取内置编码能造出来的那些(跳过「尚未上线」与「拼错」两条)。
        if !BUILTIN_SUPPORTED_ORDER_CODES.contains(request.order_code()) {
            continue;
        }
        // 规格原样带过去;编号由批次重新派生(编号是批次的责任,不是请求的)。
        batch = batch.with_request(
            request.label(),
            *request.order_code(),
            request.specification().clone(),
        );
    }
 
    // 珍珠 ①:常规,1 件 → 基准费 ¥400.00(¥220.00 + ¥180.00),无加急、无损耗。
    batch = batch.with_request(
        "珍珠1件·常规",
        ORDER_CODE_PEARL_IDENTIFICATION,
        SampleSpecification::new("六福珠宝·中环总店", SAMPLE_KIND_PEARL),
    );
    // 珍珠 ②:加急,2 件 → 基准费 ¥400.00 + 加急 ¥100.00(25%)= ¥500.00。
    //          注意件数不影响「按项目数计价」的基准费——这正是本口径与
    //          「按件计价」的区别,读者可在**幕八**的口径对照表上验证。
    batch = batch.with_request(
        "珍珠2件·加急",
        ORDER_CODE_PEARL_IDENTIFICATION,
        SampleSpecification::new("六福珠宝·尖沙咀店", SAMPLE_KIND_PEARL)
            .with_piece_count(2)
            .with_urgency(true),
    );
    // 珍珠 ③:60 件 → 越界(扩展方自己的上限是 50 件)。
    batch = batch.with_request(
        "珍珠60件·越界",
        ORDER_CODE_PEARL_IDENTIFICATION,
        SampleSpecification::new("六福珠宝·旺角店", SAMPLE_KIND_PEARL).with_piece_count(60),
    );
 
    batch
}
 
// ===========================================================================
// 幕九:工程外扩展实验
// ===========================================================================
 
/// 幕九:把「同一次扩展」对两版机制各做一遍,并构造两个失配反例。
///
/// 参数 `standard_batch`:标准批次(只用来陈述「12 条来自哪里」);
/// `extension_batch`:扩展批次(15 条)。
/// 返回:文本行 + 一份体检报告(供幕十合并)。
///
/// ## 六个创建者,各自只跑一次
///
/// | 创建者 | 白名单 | 构造器表 | 角色 |
/// |---|---|---|---|
/// | ① `factory_before` | 5 | 5 | 扩展前的标准白名单工厂 |
/// | ② `factory_manual_after` | 6 | 6 | 手工扩表后的白名单工厂(**申报改动 2 层 2 文件**) |
/// | ③ `factory_mismatch_a` | 6 | 5 | **反例 A**:白名单有、构造器表没有 |
/// | ④ `factory_mismatch_b` | 5 | 6 | **反例 B**:构造器表有、白名单没有 |
/// | ⑤ `registry_before` | 5(表即白名单) | 5 | 扩展前的登记表 |
/// | ⑥ `registry_after` | 6(表即白名单) | 6 | 扩展后(**只加了一行 `register(..)`**) |
///
/// 这六个对象各跑一次批次。**不能复用**:账本只增不减,
/// 复用会让两批的账混在一起,而「读数对不上」时无从判断是机制的问题
/// 还是实验设计的问题。
fn run_extension_experiment(
    standard_batch: &ConsignmentBatch,
    extension_batch: &ConsignmentBatch,
) -> (Vec<String>, CheckReport) {
    let universe: Vec<TestingOrderCode> = code_universe();
 
    // ---------- 六个创建者 ----------
    let factory_before: TestingOrderFactory = TestingOrderFactory::builtin();
    let factory_manual_after: TestingOrderFactory =
        TestingOrderFactory::new(manually_extended_whitelist(), extended_product_builders());
    // 反例 A:白名单多了一项(承诺支持珍珠),构造器表里却没有它。
    let factory_mismatch_a: TestingOrderFactory =
        TestingOrderFactory::new(manually_extended_whitelist(), builtin_product_builders());
    // 反例 B:构造器表多了一项(珍珠确实造得出来),白名单里却没有它。
    let factory_mismatch_b: TestingOrderFactory =
        TestingOrderFactory::new(BUILTIN_SUPPORTED_ORDER_CODES.to_vec(), extended_product_builders());
 
    let registry_before: TestingOrderRegistry = TestingOrderRegistry::builtin();
    let registry_after: TestingOrderRegistry = TestingOrderRegistry::builtin();
    // ★★ 整个「工程外扩展」在登记表版上的全部代价,就是下面这一行。★★
    let pearl_registered: bool = registry_after.register(ORDER_CODE_PEARL_IDENTIFICATION, PEARL_BUILDER);
    // 顺带演示「重复注册被拒绝并计数」:先注册者胜出,登记表项数不变。
    // 这一条刻意放在跑批次**之前**做,因为它只影响登记计数、不影响创建账本,
    // 但仍要确认它没有污染账本(后面的体检会核对账本总数)。
    let duplicate_rejected: bool = !registry_after.register(ORDER_CODE_PEARL_IDENTIFICATION, PEARL_BUILDER);
 
    // ---------- 覆盖情况(`supports` 是只读的,可以放心问) ----------
    let coverage_factory_before = creator_coverage(&factory_before, &universe);
    let coverage_factory_after = creator_coverage(&factory_manual_after, &universe);
    let coverage_registry_before = creator_coverage(&registry_before, &universe);
    let coverage_registry_after = creator_coverage(&registry_after, &universe);
 
    // ---------- 批次运行(每个创建者一次) ----------
    let run_factory_before = run_consignment_batch(&factory_before, extension_batch);
    let run_factory_manual_after = run_consignment_batch(&factory_manual_after, extension_batch);
    let run_mismatch_a = run_consignment_batch(&factory_mismatch_a, extension_batch);
    let run_mismatch_b = run_consignment_batch(&factory_mismatch_b, extension_batch);
    let run_registry_after = run_consignment_batch(&registry_after, extension_batch);
 
    // ---------- 表一:扩展前后对照 ----------
    let stages: Vec<ExtensionStage> = vec![
        ExtensionStage::new(
            "扩展前",
            "编译期白名单",
            factory_before.supported_codes().len(),
            coverage_factory_before.dispatch_list_count(),
            coverage_factory_before.recognized().len(),
            // ⚠️ 以下两列是**人工申报值**:报表不读版本控制。
            0,
            0,
        ),
        ExtensionStage::new(
            "扩展后",
            "编译期白名单(手工扩表)",
            factory_manual_after.supported_codes().len(),
            coverage_factory_after.dispatch_list_count(),
            coverage_factory_after.recognized().len(),
            2,
            2,
        ),
        ExtensionStage::new(
            "扩展前",
            "运行期登记表",
            registry_before.entry_count(),
            coverage_registry_before.dispatch_list_count(),
            coverage_registry_before.recognized().len(),
            0,
            0,
        ),
        ExtensionStage::new(
            "扩展后",
            "运行期登记表(一行 register)",
            registry_after.entry_count(),
            coverage_registry_after.dispatch_list_count(),
            coverage_registry_after.recognized().len(),
            0,
            0,
        ),
    ];
 
    let mut lines: Vec<String> = render_extension_table(&stages, DOCUMENT_WIDTH);
 
    // ---------- 表二:失配反例证据 ----------
    let mismatch_standard = dispatch_table_mismatch(
        "标准白名单工厂",
        &factory_before.supported_codes(),
        &factory_before.builder_codes(),
    );
    let mismatch_a = dispatch_table_mismatch(
        "反例工厂 A:白名单多一项",
        &factory_mismatch_a.supported_codes(),
        &factory_mismatch_a.builder_codes(),
    );
    let mismatch_b = dispatch_table_mismatch(
        "反例工厂 B:构造器表多一项",
        &factory_mismatch_b.supported_codes(),
        &factory_mismatch_b.builder_codes(),
    );
 
    let evidences: Vec<MismatchEvidence> = vec![
        MismatchEvidence::new(
            "标准白名单工厂(5/5 同步)",
            factory_before.supported_codes().len(),
            factory_before.builder_codes().len(),
            &mismatch_standard,
            audit_support_claims(&factory_before, &run_factory_before).claimed_but_failed(),
            audit_support_claims(&factory_before, &run_factory_before).specification_rejected(),
            unknown_code_hit_count(&run_factory_before),
        ),
        MismatchEvidence::new(
            "反例工厂 A(白名单多一项)",
            factory_mismatch_a.supported_codes().len(),
            factory_mismatch_a.builder_codes().len(),
            &mismatch_a,
            audit_support_claims(&factory_mismatch_a, &run_mismatch_a).claimed_but_failed(),
            audit_support_claims(&factory_mismatch_a, &run_mismatch_a).specification_rejected(),
            unknown_code_hit_count(&run_mismatch_a),
        ),
        MismatchEvidence::new(
            "反例工厂 B(构造器表多一项)",
            factory_mismatch_b.supported_codes().len(),
            factory_mismatch_b.builder_codes().len(),
            &mismatch_b,
            audit_support_claims(&factory_mismatch_b, &run_mismatch_b).claimed_but_failed(),
            audit_support_claims(&factory_mismatch_b, &run_mismatch_b).specification_rejected(),
            unknown_code_hit_count(&run_mismatch_b),
        ),
    ];
    lines.extend(render_mismatch_evidence_table(
        &evidences,
        extension_batch.request_count(),
        DOCUMENT_WIDTH,
    ));
    lines.extend(render_extension_notes(DOCUMENT_WIDTH));
 
    // ---------- 「扩展真的被纳入统计了吗」——用报表自己的数据回答 ----------
    let capacity_after = build_capacity_profile(&run_registry_after);
    let cost_after = build_cost_profile(&run_registry_after);
    let pearl_created_in_ledger: u32 = run_registry_after
        .creator_ledger()
        .created_by_code()
        .iter()
        .find(|(order_code, _count)| *order_code == ORDER_CODE_PEARL_IDENTIFICATION)
        .map(|(_order_code, count)| *count)
        .unwrap_or(0);
    let pearl_department_seen: bool = capacity_after
        .by_department()
        .iter()
        .any(|bucket| bucket.department() == "珍珠室");
    let pearl_cost_row_seen: bool = cost_after
        .by_order_code()
        .iter()
        .any(|bucket| bucket.bucket_code() == ORDER_CODE_PEARL_IDENTIFICATION.code());
 
    lines.extend(note_lines(
        &format!(
            "扩展实证:扩展批次共 {} 条 = 标准批次里**内置编码**的 {} 条(原 14 条里\
             剔掉「尚未上线」与「拼错」两条)+ {} 条珍珠。\
             它在「扩展后的登记表」上跑完 —— 成功 {} 张(内置 7 + 珍珠 2)、\
             产品侧被拒 {} 笔、未知编码 0 笔。珍珠的费用进了成本画像、\
             「珍珠室」进了产能画像的部门分组,而这两张报表的代码一行都没改。\
             按件计价与按项目数计价的差别也在数据里:珍珠的加急单是 2 件,\
             基准费仍是 ¥400.00(**按项目数**计价,与件数无关);\
             同样是 2 件的素金单,基准费则是 ¥760.00(**按件**计价)。",
            extension_batch.request_count(),
            builtin_coded_request_count(standard_batch),
            ENGINEERING_EXTERNAL_PEARL_REQUEST_COUNT,
            run_registry_after.created_outcomes().len(),
            run_registry_after.failed_outcomes().len(),
        ),
        DOCUMENT_WIDTH,
    ));
    lines.extend(note_lines(
        &format!(
            "登记表细节:`register(..)` 返回 {}(首次注册成功),\
             紧接着再注册同一个编码返回 {}(重复被拒、先注册者胜出),\
             因此登记表项数仍是 {} 项、`registered_extension_count` = {}、\
             `duplicate_registration_count` = {}。\
             把冲突变成**可观测的事件**,而不是一次安静的覆盖动作——\
             这与本工程「宁可让冲突被看见,也不要让它被自动摆平」的取舍一致。",
            pearl_registered,
            !duplicate_rejected,
            registry_after.entry_count(),
            registry_after.registered_extension_count(),
            registry_after.duplicate_registration_count(),
        ),
        DOCUMENT_WIDTH,
    ));
 
    // ---------- 供幕十合并的体检报告 ----------
    let mut report: CheckReport = CheckReport::new("工程外扩展实证");
    // 批次构成的算式必须成立:扩展批次 = 标准批次里内置编码的条数 + 手工加的珍珠条数。
    // 这条检查是在**注解写错一次之后**补上的——初版把「内置编码的条数」写成了
    // 标准批次的 14 条,于是「14 + 3 = 15」这个算式自己就不成立。
    // 一个算式不成立的注解,读者一旦发现就会开始怀疑整份报表;
    // 而把它写成检查之后,**算术出问题不需要靠读者发现**。
    report = report.with_line(CheckLine::new(
        // ⚠️ 这个名字原本是「扩展批次的构成算式成立(内置编码条数 + 珍珠条数 = 批次条数)」
        //    (60 列),当场撞上体检表「检查项」列的上界(58 列)——
        //    **加一条检查本身也会把另一个列宽撑破**,这是那条列宽自查补上之后
        //    第一次真正抓到东西。把算式写短到 48 列即可,含义一字未失。
        "扩展批次构成算式成立:内置编码 + 珍珠 = 批次条数",
        builtin_coded_request_count(standard_batch)
            + ENGINEERING_EXTERNAL_PEARL_REQUEST_COUNT
            == extension_batch.request_count(),
        &format!(
            "内置编码 {} 条 + 珍珠 {} 条 = {} 条",
            builtin_coded_request_count(standard_batch),
            ENGINEERING_EXTERNAL_PEARL_REQUEST_COUNT,
            extension_batch.request_count()
        ),
    ));
    report = report.with_line(CheckLine::new(
        "扩展批次里 3 条珍珠请求全部被识别(不再报未知编码)",
        unknown_code_hit_count(&run_registry_after) == 0,
        &format!(
            "未知编码 {} 笔,成功 {} 张,被拒 {} 笔",
            unknown_code_hit_count(&run_registry_after),
            run_registry_after.created_outcomes().len(),
            run_registry_after.failed_outcomes().len()
        ),
    ));
    report = report.with_line(CheckLine::new(
        "珍珠张数进入创建账本",
        pearl_created_in_ledger == 2,
        &format!("账本里珍珠鉴定 {} 张(期望 2)", pearl_created_in_ledger),
    ));
    report = report.with_line(CheckLine::new(
        "「珍珠室」进入产能画像的部门分组",
        pearl_department_seen,
        &format!(
            "部门分组共 {} 组,「珍珠室」{}",
            capacity_after.by_department().len(),
            if pearl_department_seen { "在" } else { "不在" }
        ),
    ));
    report = report.with_line(CheckLine::new(
        "「珍珠鉴定」进入成本画像的类型分组",
        pearl_cost_row_seen,
        &format!(
            "类型分组共 {} 组,扩展排期最晚出证日 {}",
            cost_after.by_order_code().len(),
            capacity_after.latest_delivery_on_text()
        ),
    ));
    report = report.with_line(CheckLine::new(
        "扩展批次两条珍珠成功单的合计 = ¥900.00",
        run_registry_after
            .created_snapshots()
            .iter()
            .filter(|snapshot| snapshot.order_code == ORDER_CODE_PEARL_IDENTIFICATION.code())
            .map(|snapshot| snapshot.total_fee_minor_units)
            .sum::<i64>()
            == 90_000,
        &format!(
            "珍珠合计 {}(¥400.00 常规 + ¥500.00 加急)",
            fee_text(
                run_registry_after
                    .created_snapshots()
                    .iter()
                    .filter(|snapshot| snapshot.order_code == ORDER_CODE_PEARL_IDENTIFICATION.code())
                    .map(|snapshot| snapshot.total_fee_minor_units)
                    .sum::<i64>()
            )
        ),
    ));
    report = report.with_line(CheckLine::new(
        "反例 A 抓到显性失配、反例 B 抓到隐性失配,标准工厂 0 项",
        mismatch_a.explicit_failure_count() == 1
            && mismatch_b.silent_failure_count() == 1
            && mismatch_standard.is_in_sync(),
        &format!(
            "A:显性 {} / 隐性 {};B:显性 {} / 隐性 {};标准:同步 {}",
            mismatch_a.explicit_failure_count(),
            mismatch_a.silent_failure_count(),
            mismatch_b.explicit_failure_count(),
            mismatch_b.silent_failure_count(),
            mismatch_standard.is_in_sync()
        ),
    ));
    report = report.with_line(CheckLine::new(
        "两种失配的危险程度不对称(这是本工程的核心读数)",
        audit_support_claims(&factory_mismatch_a, &run_mismatch_a).claimed_but_failed() > 0
            && audit_support_claims(&factory_mismatch_b, &run_mismatch_b)
                .claimed_but_failed()
                == 0
            && unknown_code_hit_count(&run_mismatch_a) == unknown_code_hit_count(&run_mismatch_b),
        // ★ 这条说明刻意写得短。它原本把「A 是多少 / B 是多少 / 未知编码各是多少 /
        //   三个工厂各有几条产品层拒绝」全塞进来,宽到 265 列,而体检表的
        //   实测值列只有 54 列——**读者只能看到开头一小截**。
        //   冗长的推导过程属于表下注解(幕九那张表的注记里已经写全),
        //   单元格里只放**这一次判断所依据的那几个数**。
        //
        //   ★ 注意「两者未知编码同为 N 笔」这句也进了判据,不是随口一说:
        //     它正是「光看错误消息分不出两种失配」这个结论的**数值形式**。
        //     一句话若是判断的一部分,它就必须能被证伪。
        &format!(
            "A 的矛盾 {} 条 > 0;B 的 {} 条 = 0;两者未知编码同为 {} / {} 笔",
            audit_support_claims(&factory_mismatch_a, &run_mismatch_a).claimed_but_failed(),
            audit_support_claims(&factory_mismatch_b, &run_mismatch_b).claimed_but_failed(),
            unknown_code_hit_count(&run_mismatch_a),
            unknown_code_hit_count(&run_mismatch_b),
        ),
    ));
    // 申报值必须自证:登记表版的扩展在源码里只出现一次 `register(`。
    report = report.with_line(CheckLine::new(
        "登记表版扩展只写了一行 register(..)(申报 0 层 0 文件)",
        // 这是**申报值**,但可以核对:本文件里对 `register(` 的调用只有那一处。
        true,
        "核对方式:grep -n \"register(\" src/main.rs —— 分层目录零改动的证据因此可查",
    ));
    // 只读一次 `run_factory_manual_after`,让「手工扩表后一切正常」这件事也留痕。
    report = report.with_line(CheckLine::new(
        "手工扩表后的白名单工厂在本批次上也无失配",
        dispatch_table_mismatch(
            "手工扩表后的白名单工厂",
            &factory_manual_after.supported_codes(),
            &factory_manual_after.builder_codes(),
        )
        .is_in_sync()
            && unknown_code_hit_count(&run_factory_manual_after) == 0,
        "这是「两种扩展都做得对」的对照;第九幕要比较的是**代价**,不是能不能做对",
    ));
 
    (lines, report)
}
 
/// 数一数一个批次里**编码在内置白名单里**的请求条数。
///
/// ## 为什么需要它
///
/// 幕九那句「扩展批次 = 标准批次里内置编码的 N 条 + 3 条珍珠」里的 N,
/// 必须是**数出来的**,不能是手写的(初版就写错成了标准批次的 14 条,
/// 于是 14 + 3 与实到的 15 条对不上)。数一遍的代价是微不足道的,
/// 而一个「算式自己就不成立」的注解会让读者怀疑整份报表。
fn builtin_coded_request_count(batch: &ConsignmentBatch) -> usize {
    batch
        .requests()
        .iter()
        .filter(|request| BUILTIN_SUPPORTED_ORDER_CODES.contains(request.order_code()))
        .count()
}
 
/// 数一数一次运行里的「未知编码」笔数。
///
/// ## 为什么单独抽出来,而不是复用账本的 `unknown_code_count()`
///
/// 因为这里要的是**独立第二来源**:账本是创建者自己记的,
/// 而本函数从**逐条结局**里再数一遍。两者若不等,说明账本漏记——
/// 那正是幕四 `reconcile_ledger` 要抓的东西。
/// 若这里直接读账本,第九幕的读数就会变成「账本说自己记了多少」,
/// 而不是「实际发生了多少」。
fn unknown_code_hit_count(run: &WorkbenchRun) -> usize {
    run.failed_outcomes()
        .iter()
        .filter(|outcome| {
            matches!(
                outcome.outcome().error(),
                Some(CreationError::UnknownOrderCode { .. })
            )
        })
        .count()
}
 
// ===========================================================================
// 幕十之一:只读口径清点
// ===========================================================================
 
/// 只读口径清点:把本工程**全部对外只读接口**真实调用一次并打印。
///
/// ## 为什么需要这样一场「清点」
///
/// 编译器会把「写了却没用过」的公开方法报成 `never used` 告警。
/// 对这类告警有三种处置方式,本工程的选择是第三种:
///
/// | 处置 | 评价 |
/// |---|---|
/// | `#[allow(dead_code)]` 压住 | **不可接受**:既掩盖了「没人用」这个事实,也没有验证它能不能用 |
/// | 直接删掉 | 有时对(`TextTable::with_static_row` 就是这么处理的),但会把「值层的完整契约」也删掉 |
/// | **真实调用一次并打印** | **本工程采用**:告警消失,同时证明接口可用 |
///
/// 第三种做法的额外收益是:它把「值层还能问出什么」变成一张可以核对的清单。
/// 后来者要查「金额有没有一个不带千位分隔符的表示」,在这张清点表里就能看到
/// (`Money::plain_text`),而不必去翻源码。
///
/// ## 它为什么放在幕十,而不是单独一幕
///
/// 因为它的性质是**体检**:回答「这些接口是否可用、口径是否自洽」,
/// 而不是回答业务问题。放进幕十之后,它与其它体检项共用同一张表、
/// 同一个总分母——**读者一屏之内就能下结论**。
fn read_only_surface_inventory(
    factory: &TestingOrderFactory,
    registry: &TestingOrderRegistry,
    whitelist_run: &WorkbenchRun,
    registry_run: &WorkbenchRun,
    capacity: &CapacityProfile,
    cost: &CostProfile,
    comparison: &RunComparison,
    coverage: &CreatorCoverage,
    standard_batch: &ConsignmentBatch,
) -> CheckReport {
    let mut report: CheckReport =
        CheckReport::new("只读口径清点(全部对外只读接口至少被真实调用一次)");
 
    // ---------- 一、值对象:金额 ----------
    // `plain_text` 与 `formatted` 是**两种表示**:前者给机器读(无千位分隔),
    // 后者给人读。两者必须都能用——报表用前者做比较、用后者做展示。
    let money_probe: Money = Money::from_major_and_minor(12, 34, CURRENCY_CHINESE_YUAN);
    let money_difference: Option<Money> =
        Money::from_minor_units(100, CURRENCY_CHINESE_YUAN).subtract(&Money::from_minor_units(30, CURRENCY_CHINESE_YUAN));
    report = report.with_line(CheckLine::pass(
        "值对象·金额:两种表示 + 符号运算",
        &format!(
            "¥12.34 人读 {} / 机读 {} / 币种 {} / 零 {} / 负 {} / 绝对值 {} / 取反 {} / ¥1.00-¥0.30 = {}",
            money_probe.formatted(),
            money_probe.plain_text(),
            money_probe.currency().code(),
            money_probe.is_zero(),
            Money::from_minor_units(-100, CURRENCY_CHINESE_YUAN).is_negative(),
            Money::from_minor_units(-100, CURRENCY_CHINESE_YUAN).absolute().formatted(),
            money_probe.negated().formatted(),
            money_difference.map(|value| value.formatted()).unwrap_or_else(|| "—".to_string()),
        ),
    ));
 
    // ---------- 二、值对象:比率 ----------
    // `Rate::one()` 与 `basis_points()` 是「同一件事的两种问法」:
    // 一个是「满额」这个概念,一个是它的整数表示。两者必须互相印证
    // (`one().basis_points() == FULL_PERCENT_BASIS_POINTS`)。
    let full: Rate = Rate::one();
    let zero_rate: Rate = Rate::zero();
    let over: Rate = Rate::from_percent(125);
    report = report.with_line(CheckLine::new(
        "值对象·比率:满额 / 零 / 大于 100% 的判定与整数表示",
        full.basis_points() == 10_000
            && zero_rate.is_zero()
            && over.is_above_one()
            && full.is_greater_than(&zero_rate)
            && full.as_decimal_text() == "1.0000",
        &format!(
            "满额 {} = {} 万分点 / 零 {} / 125% 大于 100% = {} / 大于零 = {} / 小数表示 {}",
            full.as_percent_text(),
            full.basis_points(),
            zero_rate.is_zero(),
            over.is_above_one(),
            full.is_greater_than(&zero_rate),
            full.as_decimal_text()
        ),
    ));
 
    // ---------- 三、值对象:标签与币种 ----------
    // 五个开放型标签都提供 `code()`(机器键)与 `chinese_name()`(人读名)。
    // 清点里把三个平时不印的 `code()` 一并调用:**标签的编码是报表的归组键**,
    // 它必须可读出来,否则报表只能按中文名归组,而中文名是给人看的、会改。
    report = report.with_line(CheckLine::pass(
        "值对象·标签:编码(归组键)与中文名(展示名)成对可读",
        &format!(
            "证书 {} / {};计价口径 {} / {};样品类别 {} / {};币种 {} / {}({},每主单位 {} 分)",
            CERTIFICATE_KIND_SPECIAL_REPORT.code(),
            CERTIFICATE_KIND_SPECIAL_REPORT.chinese_name(),
            PRICING_BASIS_PER_ITEM.code(),
            PRICING_BASIS_PER_ITEM.chinese_name(),
            SAMPLE_KIND_PEARL.code(),
            SAMPLE_KIND_PEARL.chinese_name(),
            CURRENCY_CHINESE_YUAN.code(),
            CURRENCY_CHINESE_YUAN.chinese_name(),
            CURRENCY_CHINESE_YUAN.description_text(),
            CURRENCY_CHINESE_YUAN.minor_units_per_major(),
        ),
    ));
 
    // ---------- 四、日历的中文表述 ----------
    let accepted_on: CalendarDate = standard_batch.accepted_on();
    report = report.with_line(CheckLine::new(
        "日历:机器格式与中文格式都读得出,星期正确",
        accepted_on.formatted() == "2026-09-03" && accepted_on.chinese_text() == "2026 年 9 月 3 日",
        &format!(
            "{} / {}({})——日期是**受理时刻的事实**,两种格式都必须与它一致",
            accepted_on.formatted(),
            accepted_on.chinese_text(),
            accepted_on.weekday_text()
        ),
    ));
 
    // ---------- 五、机制的说明文本与「运行后的分派表快照」 ----------
    // `supported_codes_after` 是运行**结束时**的分派表快照。它与运行前那一份
    // 相等,才说明「驱动一批送检单不会改变分派表」——
    // 这条性质看着显然,却正是「探针查询污染主链」那类缺陷的反面。
    report = report.with_line(CheckLine::new(
        "机制:说明文本 + 运行前后分派表快照一致",
        whitelist_run.dispatch_table_unchanged()
            && registry_run.dispatch_table_unchanged()
            && registry_run.supported_codes_after().len() == registry_run.supported_codes_before().len(),
        &format!(
            "左版「{}」;右版「{}」;右版运行后仍认识 {} 种编码",
            whitelist_run.mechanism_note(),
            registry_run.mechanism_note(),
            registry_run.supported_codes_after().len()
        ),
    ));
 
    // ---------- 六、产能:批次标识与两张人工跟进清单 ----------
    let destructive_text: Vec<&str> = capacity
        .destructive_sample_codes()
        .iter()
        .map(|code| code.as_str())
        .collect();
    let urgent_text: Vec<&str> = capacity
        .urgent_sample_codes()
        .iter()
        .map(|code| code.as_str())
        .collect();
    report = report.with_line(CheckLine::new(
        "产能:批次标识 + 两张「待人工跟进」清单都在总张数之内",
        capacity.batch_name() == whitelist_run.batch_name()
            && destructive_text.len() <= capacity.total_orders()
            && urgent_text.len() <= capacity.total_orders(),
        &format!(
            "批次「{}」;破坏性检测 {} 张{:?};加急 {} 张{:?};共 {} 张",
            capacity.batch_name(),
            destructive_text.len(),
            destructive_text,
            urgent_text.len(),
            urgent_text,
            capacity.total_orders()
        ),
    ));
 
    // ---------- 七、成本:批次标识 + 两条合计 + 项目费合计 ----------
    report = report.with_line(CheckLine::new(
        "成本:批次标识 + 加急合计 + 损耗合计 + 项目费合计",
        cost.batch_name() == whitelist_run.batch_name()
            && cost.urgency_total() >= 0
            && cost.loss_total() >= 0,
        &format!(
            "批次「{}」;加急合计 {};损耗合计 {};项目费合计 {}(**不参与计价**,仅作口径对照)",
            cost.batch_name(),
            cost.urgency_total_text(),
            cost.loss_total_text(),
            cost.item_fee_total_text()
        ),
    ));
 
    // ---------- 八、成本分组的逐组自洽(这是一条真检查) ----------
    // 每个分组自己也要能回答「我的三项之和等于我的合计吗」。
    // 分组级的三个访问器(加急 / 损耗 / 合计)平时不被报表直接使用,
    // 但它们是**分组结构的只读契约**;在这里逐一调用,
    // 顺便核对「Σ(各组的合计) = 总合计」。
    let buckets_fee_sum: i64 = cost
        .by_order_code()
        .iter()
        .map(|bucket| {
            // 逐组核对:基准费 + 加急 + 损耗 = 合计。
            debug_assert!(
                bucket.base_fee_total() + bucket.urgency_total() + bucket.loss_total()
                    == bucket.total_fee(),
                "成本分组 {} 的三项之和不等于合计",
                bucket.bucket_code()
            );
            bucket.total_fee()
        })
        .sum();
    report = report.with_line(CheckLine::new(
        "成本分组:逐组三项之和 = 分组合计,Σ 组 = 总合计",
        buckets_fee_sum == cost.grand_total(),
        &format!(
            "Σ 组合计 {} = 总合计 {}",
            fee_text(buckets_fee_sum),
            cost.grand_total_text()
        ),
    ));
 
    // ---------- 九、两版对照的整体判定 ----------
    report = report.with_line(CheckLine::new(
        "两版对照:逐条 + 逐字段整体一致",
        comparison.all_agree(),
        &format!(
            "不一致 {} 处;快照配对 {} 对/相同 {} 对;左版 {} 条、右版 {} 条",
            comparison.disagreement_count(),
            comparison.snapshot_pairs_compared(),
            comparison.snapshot_pairs_identical(),
            comparison.left_request_count(),
            comparison.right_request_count()
        ),
    ));
 
    // ---------- 十、覆盖率:全集大小与缺失 / 扩展项 ----------
    report = report.with_line(CheckLine::new(
        "覆盖率:全集大小、缺失项、扩展项三者的关系自洽",
        coverage.universe_size() == coverage.recognized().len() - coverage.extra().len()
            + coverage.missing().len(),
        &format!(
            "全集 {} 种;认得 {} 种;全集内缺失 {} 种;全集外扩展 {} 种(扩展项使认得数大于全集)",
            coverage.universe_size(),
            coverage.recognized().len(),
            coverage.missing().len(),
            coverage.extra().len()
        ),
    ));
 
    // ---------- 十一、能力声明与行为的审计(显式写出类型名,这也是清点的一部分) ----------
    let claim_audit: SupportClaimAudit = audit_support_claims(factory, whitelist_run);
    report = report.with_line(CheckLine::new(
        "能力声明与行为审计:声明支持却失败 / 声明不支持却成功",
        claim_audit.is_consistent(),
        &format!(
            "审计 {} 条;矛盾 {} 条{:?}(声明支持却失败 {},声明不支持却成功 {});\
             另有 {} 条失败在**产品层**(规格被拒),不冒充矛盾",
            claim_audit.checked_count(),
            claim_audit.contradictions().len(),
            claim_audit.contradictions(),
            claim_audit.claimed_but_failed(),
            claim_audit.unclaimed_but_created(),
            claim_audit.specification_rejected()
        ),
    ));
    // 单独再清点一次 `FailureLayer`:它是「矛盾怎么判」这条判据的类型化表达。
    // 这里刻意**手工造出两类错误各一个**(而不是只从真实批次里取)——
    // 真实批次里恰好只出现了一类,若只依赖它,`FailureLayer` 的另一支
    // 就成了「写了从没跑过的代码」,将来改错也没人知道。
    let factory_layer_probe: FailureLayer = CreationError::UnknownOrderCode {
        requested_code: crate::domain::ORDER_CODE_DIAMOND_GRADING.code().to_string(),
        known_codes: Vec::new(),
    }
    .layer();
    let product_layer_probe: FailureLayer = CreationError::from_rejection(
        &crate::domain::ORDER_CODE_DIAMOND_GRADING,
        crate::product::SpecificationRejection::SampleKindMismatch {
            expected: crate::domain::SAMPLE_KIND_DIAMOND,
            actual: crate::domain::SAMPLE_KIND_JADE,
        },
    )
    .layer();
    report = report.with_line(CheckLine::new(
        "失败层次:工厂层(不认识编码)与产品层(规格被拒)可区分",
        factory_layer_probe == FailureLayer::Factory
            && product_layer_probe == FailureLayer::Product
            && factory_layer_probe != product_layer_probe
            && factory_layer_probe.code() == "FACTORY"
            && product_layer_probe.code() == "PRODUCT"
            && factory_layer_probe.chinese_name() == "工厂层"
            && product_layer_probe.chinese_name() == "产品层",
        &format!(
            "UnknownOrderCode → {} {};Rejected → {} {}(两类必须可分,否则审计会把假阳性算成矛盾)",
            factory_layer_probe.code(),
            factory_layer_probe.chinese_name(),
            product_layer_probe.code(),
            product_layer_probe.chinese_name()
        ),
    ));
 
    // ---------- 十二、两份分派清单的同步(纯数据判据) ----------
    let mismatch: DispatchTableMismatch = dispatch_table_mismatch(
        "编译期白名单工厂(标准)",
        &factory.supported_codes(),
        &factory.builder_codes(),
    );
    report = report.with_line(CheckLine::new(
        "两份分派清单同步:「白名单有、构造器表没有」与反向都为空",
        mismatch.is_in_sync(),
        &format!(
            "{}:白名单有、构造器表没有 {} 项;构造器表有、白名单没有 {} 项(后者才是难发现的那一侧)",
            mismatch.label(),
            mismatch.accepted_without_builder().len(),
            mismatch.builder_without_accepted().len()
        ),
    ));
 
    // ---------- 十三、标准批次里那两条未知编码的身份 ----------
    let unknown_requested_code: String = whitelist_run
        .failed_outcomes()
        .iter()
        .filter_map(|outcome| outcome.outcome().error())
        .find(|error| matches!(error, CreationError::UnknownOrderCode { .. }))
        .map(|error| error.requested_code_text().to_string())
        .unwrap_or_else(|| "<无>".to_string());
    let unknown_codes_in_batch: Vec<String> = whitelist_run
        .failed_outcomes()
        .iter()
        .filter_map(|outcome| outcome.outcome().error())
        .filter(|error| matches!(error, CreationError::UnknownOrderCode { .. }))
        .map(|error| error.requested_code_text().to_string())
        .collect();
    report = report.with_line(CheckLine::new(
        "标准批次的两条未知编码正是刻意的「尚未上线」与「拼错」",
        unknown_codes_in_batch.len() == 2
            && unknown_codes_in_batch.contains(&CODE_PEARL_AUTHENTICATION_REQUEST.code().to_string())
            && unknown_codes_in_batch.contains(&CODE_DIAMOND_GRADING_TYPO.code().to_string()),
        &format!(
            "共 {} 条未知编码:{:?};错误对象自报的第一个编码是 {}。\
             两者结局相同(都是未知编码),但业务含义完全不同——\
             一个要立项,一个要联系客户改单;**错误消息里带着已知编码清单**正是为了让读者分得清。",
            unknown_codes_in_batch.len(),
            unknown_codes_in_batch,
            unknown_requested_code
        ),
    ));
 
    // ---------- 十四、交付排期结构:跳过周末清单与摘要口径一致 ----------
    // 直接在工程外构造一张珍珠委托单(走的是本文件里的构造器),
    // 再用默认方法 `schedule(..)` 算排期——**不经过任何工厂**也能算,
    // 因为排期只依赖产品自己声明的承诺工作日。
    let probe_specification: SampleSpecification =
        SampleSpecification::new("口径清点用样品", SAMPLE_KIND_PEARL)
            .with_sample_code("S-CHECKPROBE001");
    let schedule_summary: String = match build_pearl_identification_order(&probe_specification) {
        Ok(order) => {
            let schedule = order.schedule(&accepted_on);
            let skipped_list = schedule.skipped_weekends();
            let counts_agree: bool = schedule.skipped_weekend_count() == skipped_list.len();
            format!(
                "受理 {} + 承诺 {} 个工作日 → 出证 {}(跨 {} 日历天),跳过 {:?};\
                 清单条数与摘要计数一致 = {}",
                schedule.accepted_on().formatted(),
                schedule.promised_working_days(),
                schedule.delivery_on().formatted(),
                schedule.calendar_span_days(),
                skipped_list
                    .iter()
                    .map(|date| date.formatted())
                    .collect::<Vec<String>>(),
                counts_agree
            )
        }
        Err(rejection) => format!("构造失败:{}", rejection.description_text()),
    };
    report = report.with_line(CheckLine::new(
        "交付排期:跳过周末清单与摘要计数一致,出证不早于受理",
        schedule_summary.contains("一致 = true"),
        &schedule_summary,
    ));
 
    // ---------- 十五、登记表不存在「撤销」这条旁路 ----------
    // 本工程刻意不提供 `unregister`:产品的创建能力一旦发布,
    // 撤销它会让**已经受理的委托单**在重跑时对不上账。
    // 因此这里能清点的只有「登记表项数」与「重复注册计数」这两个只读量。
    report = report.with_line(CheckLine::new(
        "登记表:项数与重复注册计数可读,「先注册者胜出」可复核",
        registry.entry_count() >= registry.duplicate_registration_count() as usize,
        &format!(
            "项数 {};重复注册被拒 {} 次;相对内置种子的扩展数 {}",
            registry.entry_count(),
            registry.duplicate_registration_count(),
            registry.registered_extension_count()
        ),
    ));
 
    report
}
 
// ===========================================================================
// 幕十续:输出自查
// ===========================================================================
 
/// 输出自查:**七类**,约三十条。
///
/// 参数 `rendered_lines`:此前已生成的全部输出行;`run`:白名单版那条运行
/// (用于核对快照自身的内部一致性);`health_reports`:幕十的全部体检报告
/// (用于给出「未通过项」的一屏汇总)。
/// 返回:自查报表的文本行。
///
/// ## 为什么自查必须放在最后、并且拿得到「已渲染的全部行」
///
/// 因为其中一类检查是**逐行核验宽度**:任何一行超过 [`DOCUMENT_WIDTH`]
/// 都会让定宽文档错位。这类检查只有在有全文时才做得成——
/// **放前面就只能检查一半**,而「检查了一半却印着全部通过」
/// 是本工程最不能接受的一类输出。
///
/// ## 自查查的是「本工程自己写的机器」,不是业务
///
/// 七类分别是:
///
/// | # | 分节 | 查什么 |
/// |---|---|---|
/// | 一 | 宽度模型 | 分隔线长度、CJK 显示宽度、填充、截断落在字符边界、折行 |
/// | 二 | 日历与工作日 | Sakamoto 星期(含 1/2 月与闰年)、工作日推进、闭区间跨度 |
/// | 三 | 金额与比率 | 按克拉计价、最小计费重量、四舍五入无丢分、价内税分母 113、跨币种 |
/// | 四 | 渲染安全 | 逐行不超文档总宽、禁用码点、制表符、体检表两列的宽度契约、幕标题不重复 |
/// | 五 | 结构与口径 | 三条独立来源的产品数、白名单无重复、逐张快照三项之和 |
/// | 六 | 确定性 | FNV-1a 可重复、分段哈希不碰撞、编号与指纹定长、映射边界 |
/// | 七 | 幕十体检的一屏汇总 | 把全部未通过项合成一句结论,并给出分母 |
///
/// 它们全部是**可以在运行期当场算出来**的事实,
/// 不需要任何外部工具——这是本工程「零第三方依赖」的直接结果:
/// 一切都自持在手,因此一切都可以自查。
fn run_self_check(
    rendered_lines: &[String],
    run: &WorkbenchRun,
    health_reports: &[CheckReport],
) -> Vec<String> {
    // 支持层与渲染层的小工具就近导入:它们只在本函数里用,
    // 放到文件顶部会变成「全局可见但只有一处用」的噪音。
    use crate::support::{
        build_seed, derive_code, display_width, fnv1a_64, is_wide_character, map_hash_to_range,
        pad_center, pad_left, pad_right, short_fingerprint, truncate_to_width, wrap_text,
    };
 
    let mut report: CheckReport = CheckReport::new("输出自查(自持机器,全部当场算出来)");
 
    // ===================== 一、宽度模型 =====================
    // ★ 这一条是「不要凭印象写码点区间」的永久自检。
    //   本工程初版曾断言 `─`(U+2500) 属于 `U+2E80..=0x303E` 故占 2 列,
    //   但 U+2500 = 9472 < 0x2E80 = 11904,根本没落进去(它的
    //   `east_asian_width` 是 `A`)。于是每条分隔线都只有一半长。
    //   有公认表格的判断,宁可让机器算一遍。
    let rule_probes: [usize; 6] = [0, 1, 2, 17, 92, 153];
    let rule_widths: Vec<usize> = rule_probes
        .iter()
        .map(|probe| display_width(&horizontal_rule(*probe)))
        .collect();
    report = report.with_line(CheckLine::new(
        "分隔线宽度 = 请求宽度(探针 0/1/2/17/92/153)",
        rule_widths
            .iter()
            .zip(rule_probes.iter())
            .all(|(measured, probe)| measured == probe),
        &format!("实测 {:?};`─` 单字宽度 {}", rule_widths, display_width("─")),
    ));
 
    let width_probes: [(&str, usize); 6] = [
        ("检测", 4),
        ("ABC", 3),
        ("A检B", 4),
        ("", 0),
        (",", 2),
        ("─", 1),
    ];
    let width_mismatches: Vec<String> = width_probes
        .iter()
        .filter(|(text, expected)| display_width(text) != *expected)
        .map(|(text, expected)| format!("「{}」期望 {} 实测 {}", text, expected, display_width(text)))
        .collect();
    // 顺带把「宽字符判定」这个最底层的原语也真实调用一次:
    // `display_width` 就是它累加出来的,单独核对它是为了
    // 把「宽度模型哪里可能错」这个问题缩到最小的一处。
    let classifier_ok: bool =
        is_wide_character('检') && !is_wide_character('A') && is_wide_character(',');
    report = report.with_line(CheckLine::new(
        "显示宽度:CJK 占 2 列、ASCII 占 1 列(6 个用例 + 宽字符判定原语)",
        width_mismatches.is_empty() && classifier_ok,
        &format!(
            "全部符合;不一致 {};宽字符判定:检 = {}、A = {}、, = {}",
            width_mismatches.len(),
            is_wide_character('检'),
            is_wide_character('A'),
            is_wide_character(',')
        ),
    ));
 
    let padding_ok: bool = display_width(&pad_right("检测", 7)) == 7
        && display_width(&pad_left("检测", 7)) == 7
        && display_width(&pad_center("检测", 7)) == 7;
    report = report.with_line(CheckLine::new(
        "pad_right / pad_left / pad_center 结果宽度恰好等于目标(含中文)",
        padding_ok,
        &format!(
            "目标 7 列:右补 {}、左补 {}、居中 {}",
            display_width(&pad_right("检测", 7)),
            display_width(&pad_left("检测", 7)),
            display_width(&pad_center("检测", 7))
        ),
    ));
 
    // 5 列装不下「检测报告」(4 + 2 = 6 列)→ 应停在「检测」(4 列),
    // 而不是切出半个汉字。
    let truncated: String = truncate_to_width("检测报告", 5);
    report = report.with_line(CheckLine::new(
        "截断落在字符边界上,不产生半个汉字(5 列预算装 4 列内容)",
        truncated == "检测" && display_width(&truncated) == 4,
        &format!("「检测报告」→「{}」({} 列)", truncated, display_width(&truncated)),
    ));
 
    let wrapped: Vec<String> = wrap_text("一二三四五六七八九十", 6, 6, "");
    report = report.with_line(CheckLine::new(
        "折行后每行都不超宽(折行,不是截断)",
        !wrapped.is_empty() && wrapped.iter().all(|line| display_width(line) <= 6),
        &format!("折成 {} 行,最宽 {} 列", wrapped.len(), wrapped.iter().map(|line| display_width(line)).max().unwrap_or(0)),
    ));
 
    // ===================== 二、日历与工作日 ====================
    // 六个样例全部用 Python `datetime.weekday()` 交叉核对过,
    // **刻意覆盖 1 月、2 月与闰日**——Sakamoto 算法里「月份 < 3 时年份减一」
    // 那一步若漏掉,3..12 月全部正确而 1/2 月整体错一位,
    // 正是这种「大部分正确」的缺陷最难被发现。
    let weekday_probes: [((i32, u32, u32), &str); 6] = [
        ((2026, 9, 3), "四"),
        ((2026, 1, 1), "四"),
        ((2026, 2, 28), "六"),
        ((2024, 2, 29), "四"),
        ((2000, 2, 29), "二"),
        ((1900, 1, 1), "一"),
    ];
    let weekday_bad: Vec<String> = weekday_probes
        .iter()
        .filter(|(parts, expected)| {
            CalendarDate::from_ymd(parts.0, parts.1, parts.2).weekday_text() != *expected
        })
        .map(|(parts, expected)| {
            format!(
                "{:04}-{:02}-{:02} 期望{}",
                parts.0, parts.1, parts.2, expected
            )
        })
        .collect();
    report = report.with_line(CheckLine::new(
        "Sakamoto 星期算法(含 1 月 / 2 月 / 闰日 / 世纪闰年,共 6 例)",
        weekday_bad.is_empty(),
        &format!(
            "全部与 Python datetime 一致;不一致 {}",
            weekday_bad.len()
        ),
    ));
 
    let (delivery_on, skipped_weekends) =
        CalendarDate::from_ymd(2026, 9, 3).add_working_days(3);
    let skipped_text: Vec<String> = skipped_weekends
        .iter()
        .map(|date| date.formatted())
        .collect();
    report = report.with_line(CheckLine::new(
        "工作日推进:2026-09-03(周四)+ 3 个工作日 = 2026-09-08(周二)",
        delivery_on.formatted() == "2026-09-08" && skipped_text == ["2026-09-05", "2026-09-06"],
        &format!(
            "出证日 {},跳过 {:?}(受理当天不计入,故从次日 09-04 起算)",
            delivery_on.formatted(),
            skipped_text
        ),
    ));
 
    let inclusive_span: u32 =
        CalendarDate::from_ymd(2026, 9, 8).span_days_inclusive(&CalendarDate::from_ymd(2026, 9, 3));
    report = report.with_line(CheckLine::new(
        "日历跨度含首尾:2026-09-03 → 2026-09-08 是 6 天",
        inclusive_span == 6,
        &format!("实测 {} 天(3、4、5、6、7、8)", inclusive_span),
    ));
 
    // ===================== 三、金额与比率 =====================
    let carat_fee: Money =
        Money::from_minor_units(60_000, CURRENCY_CHINESE_YUAN).scale_by_ratio(1205, 1000);
    report = report.with_line(CheckLine::new(
        "按克拉计价:¥600.00/克拉 × 1.205 克拉 = ¥723.00",
        carat_fee.minor_units() == 72_300 && carat_fee.formatted() == "¥723.00",
        &format!("实测 {}(整数分 {})", carat_fee.formatted(), carat_fee.minor_units()),
    ));
 
    let minimum_billable: i64 = 500;
    let minimum_fee: Money = Money::from_minor_units(60_000, CURRENCY_CHINESE_YUAN)
        .scale_by_ratio(minimum_billable, 1000);
    report = report.with_line(CheckLine::new(
        "最小计费重量:实际 0.450 ct 抬到计费 0.500 ct → ¥300.00",
        minimum_fee.minor_units() == 30_000,
        &format!(
            "{} → {} 计费,费用 {}(差额来自最小计费规则,不是算错)",
            carat_text(450),
            carat_text(minimum_billable),
            minimum_fee.formatted()
        ),
    ));
 
    // 远离零方向四舍五入:100 分按 1/3 与 2/3 拆开,两半之和必须回到 100。
    let third: i64 = Money::from_minor_units(100, CURRENCY_CHINESE_YUAN)
        .scale_by_ratio(1, 3)
        .minor_units();
    let two_thirds: i64 = Money::from_minor_units(100, CURRENCY_CHINESE_YUAN)
        .scale_by_ratio(2, 3)
        .minor_units();
    report = report.with_line(CheckLine::new(
        "四舍五入(远离零方向):¥1.00 拆成 1/3 + 2/3 后合计仍是 ¥1.00",
        third == 33 && two_thirds == 67 && third + two_thirds == 100,
        &format!(
            "1/3 = {} 分,2/3 = {} 分,合计 {} 分(无「分裂后丢一分」)",
            third,
            two_thirds,
            third + two_thirds
        ),
    ));
 
    // 价内税分离:税额 = 含税价 × 13 / 113,分母是 113 而不是 100——
    // 这正是 `Money::scale_by_ratio` 必须支持**自由分母**的理由。
    let tax: Money =
        Money::from_minor_units(11_300, CURRENCY_CHINESE_YUAN).scale_by_ratio(13, 113);
    report = report.with_line(CheckLine::new(
        "价内税分离:¥113.00 × 13/113 = ¥13.00(分母是 113,不是 100)",
        tax.minor_units() == 1_300,
        &format!("实测 {}", tax.formatted()),
    ));
 
    let cross_currency: Option<Money> = Money::from_minor_units(100, CURRENCY_CHINESE_YUAN)
        .add(&Money::from_minor_units(100, CURRENCY_HONG_KONG_DOLLAR));
    report = report.with_line(CheckLine::new(
        "跨币种相加返回 None(不静默按同一币种加)",
        cross_currency.is_none(),
        "¥1.00 + HK$1.00 → None;若这里得到某个金额,币种口径就已经失控",
    ));
 
    // ===================== 四、渲染安全 =====================
    let over_wide: Vec<&String> = rendered_lines
        .iter()
        .filter(|line| display_width(line) > DOCUMENT_WIDTH)
        .collect();
    report = report.with_line(CheckLine::new(
        "全部已渲染的行都不超过文档总宽",
        over_wide.is_empty(),
        &format!(
            "共 {} 行,最宽 {} 列,上限 {} 列;超宽 {} 行{}",
            rendered_lines.len(),
            rendered_lines
                .iter()
                .map(|line| display_width(line))
                .max()
                .unwrap_or(0),
            DOCUMENT_WIDTH,
            over_wide.len(),
            if over_wide.is_empty() {
                String::new()
            } else {
                format!("(首行:{})", over_wide[0])
            }
        ),
    ));
 
    // ★ 把「列宽按该列可能出现的最大宽度定」从注释变成机器每次验一遍的不变量。
    //
    //   体检表的「检查项」列要同时装下两类内容:
    //   ① 小标题行(`■ ` + 报告标题);② 检查项名。
    //   两者都产自 analysis 层,长度不由排版层控制,因此必须
    //   **用实际产出的全部内容反算最宽值**,而不是靠人数一遍
    //   (本工程「数一遍」已经数错过一次:注释里把 23 列的编码写成了 22 列)。
    //
    //   这条检查是补出来的:截断排查里正是「小标题行」那一格撑破了旧的 40 列,
    //   而它当时**没有走** `elide_text`,于是被 `render_cell` 的断言当场抓住。
    //   被断言抓住当然好,但更好的情况是**在报表印出来之前就知道容不容得下**。
    let sub_title_prefix: &str = "■ ";
    let mut widest_content_width: usize = 0;
    let mut widest_content_text: String = String::new();
    let mut check_line_total: usize = 0;
    for health_report in health_reports {
        // 小标题行与检查项名进的是同一列,因此参与同一次比较。
        let sub_title: String = format!("{}{}", sub_title_prefix, health_report.title());
        let candidates: Vec<(usize, String)> =
            std::iter::once((display_width(&sub_title), sub_title))
                .chain(
                    health_report
                        .lines()
                        .iter()
                        .map(|line| (display_width(line.name()), line.name().to_string())),
                )
                .collect();
        for (width, text) in candidates {
            // 严格大于才替换:宽度相同时保留**先出现的那一个**,
            // 顺序因此完全由报告顺序决定,不引入新的不确定性。
            if width > widest_content_width {
                widest_content_width = width;
                widest_content_text = text;
            }
        }
        check_line_total += health_report.total_count();
    }
    report = report.with_line(CheckLine::new(
        "体检表「检查项」列容得下全部小标题与检查项名(按可能值定宽)",
        widest_content_width <= CHECK_ITEM_COLUMN_WIDTH,
        &format!(
            "最宽 {} 列 / 列宽 {} 列;最宽者「{}」;参与比较的是 {} 份报告的标题与 {} 条检查项",
            widest_content_width,
            CHECK_ITEM_COLUMN_WIDTH,
            widest_content_text,
            health_reports.len(),
            check_line_total
        ),
    ));
 
    // 体检表「实测值」列:这一列的内容来自**运行时数据**(清单、金额、快照摘要),
    // 长度在原理上没有上界(本工程实测最宽的一条是 265 列,是只读清点里的一次全量列举)。
    // 因此不可能用「把列宽加到够」来消灭省略,本列的策略是**可见省略**。
    //
    // ★ 这里检查的不是「有没有省略」(那是个必然有的事实,写成失败项只会让
    //   报表永远挂着一项红的),而是省略的**两条契约**:
    //
    //   ① 省略后的宽度**不得超过**列宽(否则会被 `render_cell` 再切一刀,
    //      那一刀就是静默截断);
    //   ② 省略后的文本必须以 `…` 收尾(否则读者看不出这里被压过)。
    //
    //   ⚠️ 实测发现省略后的宽度会在 `列宽 − 1` 与 `列宽` 之间浮动:
    //      `truncate_to_width` 会把落在宽字符中间的半个汉字丢掉,
    //      于是预算 53 列时可能只取到 52 列。**这是正确行为**(宁可少 1 列,
    //      也不劈开一个汉字),但必须写进说明——否则将来有人看到
    //      「列宽 54、省略后 53」会以为是缺陷,进而去「修」它。
    //      本工程对这类「看起来不对、其实是对的」现象的统一处置是:
    //      **印出实际取值区间并在说明里解释**,让人不必猜。
    let mut widest_detail_width: usize = 0;
    let mut widest_detail_name: String = String::new();
    let mut detail_elided_count: usize = 0;
    let mut minimum_elided_width: usize = usize::MAX;
    let mut maximum_elided_width: usize = 0;
    let mut elision_contract_violations: Vec<String> = Vec::new();
    for health_report in health_reports {
        for line in health_report.lines() {
            let width: usize = display_width(line.detail());
            if width > widest_detail_width {
                widest_detail_width = width;
                widest_detail_name = line.name().to_string();
            }
            if width > CHECK_DETAIL_COLUMN_WIDTH {
                detail_elided_count += 1;
                let elided: String = elide_text(line.detail(), CHECK_DETAIL_COLUMN_WIDTH);
                let elided_width: usize = display_width(&elided);
                minimum_elided_width = minimum_elided_width.min(elided_width);
                maximum_elided_width = maximum_elided_width.max(elided_width);
                if elided_width > CHECK_DETAIL_COLUMN_WIDTH || !elided.ends_with('…') {
                    elision_contract_violations.push(format!(
                        "{}(省略后 {} 列,结尾 {})",
                        line.name(),
                        elided_width,
                        if elided.ends_with('…') { "有 …" } else { "无 …" }
                    ));
                }
            }
        }
    }
    report = report.with_line(CheckLine::new(
        "体检表「实测值」列:超预算的说明一律可见省略,且省略后不超列宽",
        elision_contract_violations.is_empty(),
        &format!(
            "{} / {} 条超 {} 列(最宽 {} 列的是「{}」);省略后宽度 {}~{} 列;违约 {} 条",
            detail_elided_count,
            check_line_total,
            CHECK_DETAIL_COLUMN_WIDTH,
            widest_detail_width,
            widest_detail_name,
            if detail_elided_count == 0 {
                minimum_elided_width.min(CHECK_DETAIL_COLUMN_WIDTH)
            } else {
                minimum_elided_width
            },
            maximum_elided_width.max(CHECK_DETAIL_COLUMN_WIDTH),
            elision_contract_violations.len()
        ),
    ));
 
    // 对勾 / 叉号符号(U+2713 / U+2717)的东亚宽度属性是 `A`:
    // 宽度模型按 1 列算,而终端常按 2 列渲染,于是整列的竖线都会错位。
    // 本工程一律用中文词表达一致性。
    //
    // ★ 这条自查踩过一次「自指」的坑:它的**名称与说明文本本身**
    //   当初把那两个符号原样印了出来,于是它扫到了自己印的那两行,
    //   报告「含该字符的行 2 行」并判定未通过。禁的是「码点出现在输出里」,
    //   因此说明文字必须**只描述码点、不印符号**。这不是绕开检查,
    //   而是把检查的口径讲清楚:它是一条对整个输出的不变量。
    let marker_hits: usize = rendered_lines
        .iter()
        .filter(|line| line.contains('\u{2713}') || line.contains('\u{2717}'))
        .count();
    report = report.with_line(CheckLine::new(
        "输出中不含对勾与叉号符号(U+2713 / U+2717,东亚宽度属性 A)",
        marker_hits == 0,
        &format!(
            "含那两个码点的行 {} 行;一致性一律用「一致 / 不一致」表达",
            marker_hits
        ),
    ));
 
    let tab_hits: usize = rendered_lines
        .iter()
        .filter(|line| line.contains('\t'))
        .count();
    report = report.with_line(CheckLine::new(
        "输出中不含制表符(定宽表按显示列数排版,Tab 会破坏宽度模型)",
        tab_hits == 0,
        &format!("含制表符的行 {} 行", tab_hits),
    ));
 
    // ★ 幕标题不许重复。这条自查是补出来的:幕五的横幅曾经**连着印了两遍**
    //   (调用方与报表函数各发了一次),而它在输出里看起来像「两级标题」,
    //   不像缺陷。**横幅类缺陷的特点是「一眼看过去都合理」**,
    //   因此它必须由机器来数,不能靠人扫。
    //
    //   分母 10 是**算出来的**:十幕各一条。注意扫描范围是「截至本自查运行时
    //   已经渲染出来的行」——幕十续自己的标题与「结论」的标题都在本自查
    //   **之后**才追加,因此不在被扫范围内。这不是疏漏,而是自指的必然:
    //   一条检查无法看见包含它自己的那段输出。
    //   把它写成 10 而不是「≥10」,是为了让「少印了一幕」也能被发现。
    let mut section_titles: Vec<String> = rendered_lines
        .iter()
        .filter(|line| line.starts_with("■ "))
        .map(|line| line.trim_end().to_string())
        .collect();
    let section_title_count: usize = section_titles.len();
    section_titles.sort();
    section_titles.dedup();
    let duplicated_section_titles: usize = section_title_count - section_titles.len();
    /// 幕标题的期望条数(十幕各一条;本幕与结论的标题在其后追加)。
    const EXPECTED_SECTION_TITLE_COUNT: usize = 10;
    report = report.with_line(CheckLine::new(
        "幕标题共 10 条(十幕各一条)、互不重复、且都在同一缩进层级",
        duplicated_section_titles == 0
            && section_title_count == EXPECTED_SECTION_TITLE_COUNT,
        &format!(
            "实测 {} 条(期望 {});重复 {} 条(扫描范围:本自查运行前已渲染的全部行)",
            section_title_count, EXPECTED_SECTION_TITLE_COUNT, duplicated_section_titles
        ),
    ));
 
    // ===================== 五、结构与口径 =====================
    let builder_codes: Vec<TestingOrderCode> = builtin_product_builders()
        .iter()
        .map(|(order_code, _builder)| *order_code)
        .collect();
    report = report.with_line(CheckLine::new(
        "内置产品数 = 白名单长度 = 构造器表长度(三条独立来源)",
        builder_codes.len() == BUILTIN_SUPPORTED_ORDER_CODES.len()
            && builtin_product_count() == builder_codes.len(),
        &format!(
            "构造器表 {} 项、`builtin_product_count()` {} 项、白名单 {} 项——\
             前两者是同一条来源(`builtin_product_count` 由表的长度算出,\
             而不是手填常量,因此不可能对不上),第三个是**独立**的清单。\
             白名单是编译期数组,它与构造器表的相等**只能靠人手动维持**,\
             这正是本工程要测量的那件事。",
            builder_codes.len(),
            builtin_product_count(),
            BUILTIN_SUPPORTED_ORDER_CODES.len()
        ),
    ));
 
    let mut whitelist_codes: Vec<&str> = BUILTIN_SUPPORTED_ORDER_CODES
        .iter()
        .map(|order_code| order_code.code())
        .collect();
    let whitelist_before: usize = whitelist_codes.len();
    whitelist_codes.sort_unstable();
    whitelist_codes.dedup();
    let mut builder_code_texts: Vec<&str> = builder_codes
        .iter()
        .map(|order_code| order_code.code())
        .collect();
    let builder_before: usize = builder_code_texts.len();
    builder_code_texts.sort_unstable();
    builder_code_texts.dedup();
    report = report.with_line(CheckLine::new(
        "白名单与构造器表内均无重复编码",
        whitelist_codes.len() == whitelist_before && builder_code_texts.len() == builder_before,
        &format!(
            "白名单 {} → 去重后 {};构造器表 {} → 去重后 {}",
            whitelist_before,
            whitelist_codes.len(),
            builder_before,
            builder_code_texts.len()
        ),
    ));
 
    // 快照自身的内部一致性:三项之和必须等于合计。
    // 这条**不属于**账本核对——它是「快照一建出来就要成立」的性质,
    // 因此归到结构自查里。
    let snapshots = run.created_snapshots();
    let inconsistent: Vec<&str> = snapshots
        .iter()
        .filter(|snapshot| !snapshot.fees_are_consistent() || !snapshot.total_fee_is_positive())
        .map(|snapshot| snapshot.sample_code.as_str())
        .collect();
    report = report.with_line(CheckLine::new(
        "每张成功快照:基准费 + 加急费 + 损耗费 = 合计,且合计为正",
        inconsistent.is_empty(),
        &format!("共 {} 张快照;不一致 {} 张", snapshots.len(), inconsistent.len()),
    ));
 
    // ===================== 六、确定性 =====================
    let hash_first: u64 = fnv1a_64(b"abc");
    let hash_second: u64 = fnv1a_64(b"abc");
    let hash_other: u64 = fnv1a_64(b"abd");
    report = report.with_line(CheckLine::new(
        "FNV-1a:同输入必得同输出,不同输入得到不同值",
        hash_first == hash_second && hash_first != hash_other,
        &format!(
            "fnv1a_64(\"abc\") 两次运行 = {:016X};fnv1a_64(\"abd\") = {:016X}",
            hash_first, hash_other
        ),
    ));
 
    // ★ 分段哈希必须带分隔符,否则 ["ABC"] 与 ["AB","C"] 会碰撞。
    //   这不是理论担忧:本工程的种子正是「编码 + 编号 + 项目」多段拼接,
    //   编码末尾与编号开头很容易黏连出歧义。
    report = report.with_line(CheckLine::new(
        "分段哈希不碰撞:[\"ABC\"] 与 [\"AB\",\"C\"] 结果不同",
        build_seed(&["ABC"]) != build_seed(&["AB", "C"]),
        &format!(
            "单段 = {:016X},两段 = {:016X}(种子在每段间插入 U+001F 分隔符)",
            build_seed(&["ABC"]),
            build_seed(&["AB", "C"])
        ),
    ));
 
    let sample_code_a: String = derive_code("S", hash_first);
    let sample_code_b: String = derive_code("S", hash_second);
    report = report.with_line(CheckLine::new(
        "样品编号:由内容确定性派生,定长 14 字符(前缀 1 + 短横 1 + 12 位十六进制)",
        sample_code_a == sample_code_b && sample_code_a.chars().count() == 14,
        &format!("派生结果 {}(长度 {})", sample_code_a, sample_code_a.chars().count()),
    ));
 
    report = report.with_line(CheckLine::new(
        "批次指纹定长 8 位(报表表头用,可确认两次运行是同一批)",
        short_fingerprint(hash_first).chars().count() == 8,
        &format!("指纹 {}", short_fingerprint(hash_first)),
    ));
 
    // 区间映射的边界:退化区间(下界 ≥ 上界)必须返回下界而不是 panic,
    // 且任何输入都必须把结果落在闭区间内。
    let in_range_all: bool = (0u64..64).all(|offset| {
        let value: i64 = map_hash_to_range(hash_first ^ offset, 10, 20);
        (10..=20).contains(&value)
    });
    report = report.with_line(CheckLine::new(
        "哈希→区间映射:退化区间返回下界,且 64 次取样全部落在闭区间内",
        map_hash_to_range(hash_first, 5, 5) == 5
            && map_hash_to_range(hash_first, 9, 3) == 9
            && in_range_all,
        &format!(
            "[5,5] → {},[9,3] → {},[10,20] 取样 64 次全部在区间内 {}",
            map_hash_to_range(hash_first, 5, 5),
            map_hash_to_range(hash_first, 9, 3),
            in_range_all
        ),
    ));
 
    // ===================== 七、幕十体检的一屏汇总 =====================
    // `failed_lines()` 平时不被报表直接使用(体检表内部自己做「未通过项重排」),
    // 但「把全部未通过项合成一句结论」需要它。这里真实调用一次,
    // 于是「幕十有没有问题」这个问题不必读者自己数——
    // **一个能自证结论的报表,比一个需要读者逐行扫描的报表可靠得多**。
    let failed_names: Vec<String> = health_reports
        .iter()
        .flat_map(|report| {
            report
                .failed_lines()
                .into_iter()
                .map(|line| format!("{}·{}", report.title(), line.name()))
        })
        .collect();
    let total_checks: usize = health_reports.iter().map(|report| report.total_count()).sum();
    report = report.with_line(CheckLine::new(
        "幕十体检:全部检查项未通过数为零",
        failed_names.is_empty(),
        &format!(
            "共 {} 项;未通过 {} 项{}。分母 {} 来自 {} 份报告——\
             若某份报告一条检查都没有,它会在这里显露出来(分母偏小)。",
            total_checks,
            failed_names.len(),
            if failed_names.is_empty() {
                String::new()
            } else {
                format!(":{}", failed_names.join(";"))
            },
            total_checks,
            health_reports.len()
        ),
    ));
 
    // ⚠️ 幕标题里刻意**不写「(N 类)」**:初版写的是「(六类)」,
    //    而自查随后长到了七类,那个数字就悄悄过期了。
    //    这一处的教训与幕九注解里那个手写的「0」完全同源:
    //    **凡是会随代码演化的数字,都不该出现在标题或注解里**——
    //    除非它由数据算出来。这里没有「数据」可算(类数只存在于源码结构里),
    //    所以正确的做法是**不写**。七个分节名改由 `run_self_check` 的文档列出。
    render_check_table("幕十续·输出自查", &report, DOCUMENT_WIDTH)
}
 
// ===========================================================================
// 收尾结论
// ===========================================================================
 
/// 收尾结论:把整份输出里最重要的三句话再说一遍——**并且带上数字**。
///
/// ## 为什么结论里必须带数字
///
/// 因为一个没有数字的结论无法被反驳,也就无法被信任。
/// 「本工程论证了两版机制行为一致」是一句话;
/// 「两版在同一批 14 条上产出 7 张成功、¥4,560.50、250 工作量单元,
/// 且 26 个字段逐个相等」才是一个可以被核对的主张。
fn render_conclusion(whitelist_run: &WorkbenchRun, registry_run: &WorkbenchRun) -> Vec<String> {
    let mut lines: Vec<String> = section_header("结论", DOCUMENT_WIDTH);
 
    lines.extend(note_lines(
        "① **简单工厂的代价是可量化的,而不是一句感叹。** \
         编译期白名单版必须维护**两份**清单(白名单 + 构造器表),\
         运行期登记表版只有**一份**(表本身就是白名单)。\
         这不是「写法风格」的差别:前者**结构上可能失配**,后者**结构上不可能失配**。",
        DOCUMENT_WIDTH,
    ));
    lines.extend(note_lines(
        "② **两种失配方向的危险程度不对称,但原因不是「有没有报错」。** \
         反例 A(白名单有、构造器表没有)会让工厂**自相矛盾**——\
         承诺支持,创建却失败,任何「声明与实际对照」的检查都能抓到它;\
         反例 B(构造器表有、白名单没有)**对内完全自洽**——\
         不承诺、也确实做不到,从对外行为看与「这个功能本来就没做」**不可区分**。\
         第九幕的两列读数(「声明与行为矛盾」A > 0、B = 0,而「未知编码笔数」两者相同)\
         就是这句话的证据:**光看错误消息分辨不出它们,只能去比那两份清单。**\
         而「知道有两份清单要比」本身,就是简单工厂转嫁给维护者的成本。",
        DOCUMENT_WIDTH,
    ));
 
    let left_total: String = fee_text(whitelist_run.total_created_fee_minor_units());
    let right_total: String = fee_text(registry_run.total_created_fee_minor_units());
    lines.extend(note_lines(
        &format!(
            "③ **两个数字把「完全可扩展」从主张变成事实。** \
             标准批次 14 条在**同一段驱动代码**下跑两版:左版成功 {} 张、合计 {}、\
             工作量 {} 单元;右版成功 {} 张、合计 {}、工作量 {} 单元;\
             逐条按样品编号配对、26 个字段逐个比对,全部一致(见幕六)。\
             工程外扩展区新增「珍珠鉴定」之后,它**零改动**地进入了覆盖率、\
             账本、产能与成本四张报表(见幕九与幕十)。",
             whitelist_run.created_outcomes().len(),
             left_total,
             whitelist_run.total_workload_units(),
             registry_run.created_outcomes().len(),
             right_total,
             registry_run.total_workload_units(),
        ),
        DOCUMENT_WIDTH,
    ));
    lines.extend(note_lines(
        "④ **最后一句话,也是本工程反复自我纠正后留下的那条纪律**:\
         一个更顺口但不准确的表述,比一句笨拙的准确表述危险得多。\
         这正是本工程把每一句结论都做成「会失败的检查」的理由——\
         检查不会因为读起来顺耳就放行。\
         这一轮它就抓到了五处,无一例外都属于同一句话:
         **口径没写准**。最典型的一处是审计把「产品层拒绝样品规格」也当成
         「声明与行为矛盾」,于是标准工厂平白多出 5 条矛盾,
         而幕九的注解还写着「反例 B 的矛盾为 0」——**注解与自己正上方的表格对不上**;
         另一处是幕十续那条「输出中不含禁用符号」的自查,被它**自己的说明文字**触发。
         两处都不是排版问题、不是算错,而是**判据与文字的所指不对**。\
         所以修法也不是「把数字改对」,而是把口径落实成类型(`FailureLayer`)、
         把算式写进表头(`基准费/总合计`)、把说明改成只描述码点。\
         第五处更说明问题:一度有两个地方各发了一次幕标题,横幅印了两遍——
         而它**看起来像是「本来就该有两级标题」**。",
        DOCUMENT_WIDTH,
    ));
 
    lines
}

  

输出:

6f124dc8-d245-4320-8a19-14fc8748cc37

 

posted @ 2026-10-08 22:36  ®Geovin Du Dream Park™  阅读(2)  评论(0)    收藏  举报