---
phase: 02-toml
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/cli.rs
  - src/config/mod.rs
  - src/config/validate.rs
  - src/install/mod.rs
  - tests/fixtures/semantic_invalid.toml
autonomous: true
requirements:
  - INST-02
  - QUAL-03

tags:
  - rust
  - toml
  - config-validation
  - cli
  - phase-02

must_haves:
  truths:
    - "DBA 运行 `dm-installer install --config dm.toml --package /path/to/dm.iso` 时，安装器读取 TOML 文件并按其参数执行（INST-02 SC1）"
    - "TOML 文件指定的 port/page_size/charset/extent_size/install_path/data_path/instance_name/case_sensitive 均生效（INST-02 SC2）"
    - "TOML 文件中 page_size=12（不在 {4,8,16,32}）触发 `配置验证失败: page_size 无效: 12；有效值为 4/8/16/32` 错误，不执行安装（INST-02 SC3）"
    - "TOML 文件中 charset=9（不在 {0,1,2}）触发 `配置验证失败: charset 无效: 9` 错误"
    - "TOML 文件中 extent_size=8（不在 {16,32}）触发 `配置验证失败: extent_size 无效: 8` 错误"
    - "TOML 文件中 port=0 触发 `配置验证失败: port 无效: 0；有效范围为 1-65535` 错误"
    - "运行 `dm-installer validate --config config.toml` 时，配置语义合法则输出 `配置文件合法: <path>`，不执行任何安装步骤（QUAL-03）"
    - "未提供 --config 时，install 继续使用 InstallConfig::default()，Phase 1 流程不变（D-02 回退路径）"
    - "提供 --config 时，参数确认 UI 仍展示配置文件中的 PAGE_SIZE/CHARSET/CASE_SENSITIVE/EXTENT_SIZE 值并等待 y/n，除非附加 --yes 或 --defaults（D-08, D-09）"
    - "install 和 validate 共用 `config::load_and_validate()` 函数，去除重复 TOML 解析代码（D-06, D-07）"
  artifacts:
    - path: "src/cli.rs"
      provides: "InstallArgs.config: Option<PathBuf> 字段"
      contains: "pub config: Option<PathBuf>"
    - path: "src/config/mod.rs"
      provides: "load_and_validate() 和 validate_install_config() 两个 pub 函数"
      contains: "pub fn load_and_validate"
    - path: "src/config/mod.rs"
      provides: "validate_install_config 语义验证逻辑"
      contains: "pub fn validate_install_config"
    - path: "src/config/validate.rs"
      provides: "重构后调用 load_and_validate() 的 thin wrapper"
      contains: "load_and_validate"
    - path: "src/install/mod.rs"
      provides: "run() 入口处根据 args.config 决定配置来源的条件分支"
      contains: "match &args.config"
    - path: "tests/fixtures/semantic_invalid.toml"
      provides: "语法合法但 page_size=12 语义非法的测试 fixture"
      contains: "page_size = 12"
  key_links:
    - from: "src/install/mod.rs::run()"
      to: "src/config/mod.rs::load_and_validate()"
      via: "match &args.config { Some(path) => config::load_and_validate(path)?, None => InstallConfig::default() }"
      pattern: "config::load_and_validate"
    - from: "src/config/validate.rs::run()"
      to: "src/config/mod.rs::load_and_validate()"
      via: "super::load_and_validate(&args.config)?"
      pattern: "load_and_validate"
    - from: "src/config/mod.rs::load_and_validate()"
      to: "src/config/mod.rs::validate_install_config()"
      via: "三步链末尾调用"
      pattern: "validate_install_config"
    - from: "src/cli.rs::InstallArgs"
      to: "src/install/mod.rs::run()"
      via: "args.config 字段读取"
      pattern: "args\\.config"
---

<objective>
为 dm-installer Rust 二进制添加 TOML 配置文件驱动单机安装能力，实现 INST-02（自定义所有 dminit 参数）和 QUAL-03（validate --config 仅验证）。

Purpose: DBA 用户可通过编写 TOML 配置文件精细控制端口、数据路径、页大小、字符集、大小写敏感、区段大小等所有 dminit 参数；同时 `validate --config` 子命令可在不执行安装的前提下确认配置语义合法性。

Output:
- `src/cli.rs::InstallArgs` 新增 `--config <PATH>` flag
- `src/config/mod.rs` 新增 `load_and_validate()` 和 `validate_install_config()` 两个 pub 函数
- `src/config/validate.rs::run()` 重构为调用共用函数
- `src/install/mod.rs::run()` 入口处条件分支选择配置来源
- `tests/fixtures/semantic_invalid.toml` 语义非法 fixture
- 所有改动配套单元测试，覆盖语义验证边界值、CLI 解析、install 条件分支三类场景
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/REQUIREMENTS.md
@.planning/phases/02-toml/02-CONTEXT.md
@.planning/phases/02-toml/02-RESEARCH.md
@.planning/phases/02-toml/02-PATTERNS.md
@.planning/phases/02-toml/02-VALIDATION.md

# Phase 1 既有源码（修改基线）
@.claude/worktrees/agent-a693079c0c4cadfbf/src/cli.rs
@.claude/worktrees/agent-a693079c0c4cadfbf/src/config/mod.rs
@.claude/worktrees/agent-a693079c0c4cadfbf/src/config/validate.rs
@.claude/worktrees/agent-a693079c0c4cadfbf/src/install/mod.rs
@.claude/worktrees/agent-a693079c0c4cadfbf/tests/fixtures/valid.toml
@.claude/worktrees/agent-a693079c0c4cadfbf/tests/fixtures/invalid.toml

<interfaces>
<!-- Phase 1 既有契约，executor 必须按此扩展而非重写 -->

From src/cli.rs（既有）:
```rust
#[derive(clap::Args)]
pub struct InstallArgs {
    #[arg(long)] pub package: Option<PathBuf>,
    #[arg(long)] pub checksum: Option<String>,
    #[arg(long)] pub defaults: bool,
    #[arg(long, short = 'y')] pub yes: bool,
    // Phase 2 在此追加: pub config: Option<PathBuf>
}

#[derive(clap::Args)]
pub struct ValidateArgs {
    #[arg(long)] pub config: PathBuf,  // 注意：validate 用必填 PathBuf；install 用 Option<PathBuf>
}
```

From src/config/mod.rs（既有，不改动结构）:
```rust
#[derive(Debug, Deserialize)]
pub struct InstallConfig {
    pub install_path: String,    // default "/opt/dmdbms"
    pub data_path: String,       // default "/opt/dmdbms/data"
    pub instance_name: String,   // default "DMSERVER"
    pub port: u16,               // default 5236
    pub page_size: u8,           // default 8
    pub charset: u8,             // default 0
    pub case_sensitive: bool,    // default true
    pub extent_size: u8,         // default 16
}
impl Default for InstallConfig { /* 既有 */ }
```

From src/install/mod.rs（既有 run() 入口）:
```rust
pub async fn run(args: &InstallArgs) -> Result<()> {
    tracing::info!("开始安装达梦数据库");
    let config = InstallConfig::default();  // Phase 2 替换为条件分支
    // ...7 步保持不变
}
```

From src/ui.rs（既有，不改动）:
```rust
pub fn confirm_immutable_params(config: &InstallConfig, skip: bool) -> Result<()>;
```
注意：`skip` 参数永远为 `args.defaults || args.yes`，不包含 `args.config.is_some()`（D-09）。
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: 编写 Wave 0 失败测试 + 创建语义非法 fixture（RED）</name>
  <files>
    src/config/mod.rs,
    src/config/validate.rs,
    src/cli.rs,
    src/install/mod.rs,
    tests/fixtures/semantic_invalid.toml
  </files>
  <read_first>
    .claude/worktrees/agent-a693079c0c4cadfbf/src/config/mod.rs,
    .claude/worktrees/agent-a693079c0c4cadfbf/src/config/validate.rs,
    .claude/worktrees/agent-a693079c0c4cadfbf/src/cli.rs,
    .claude/worktrees/agent-a693079c0c4cadfbf/src/install/mod.rs,
    .claude/worktrees/agent-a693079c0c4cadfbf/tests/fixtures/valid.toml,
    .claude/worktrees/agent-a693079c0c4cadfbf/tests/fixtures/invalid.toml,
    .planning/phases/02-toml/02-CONTEXT.md,
    .planning/phases/02-toml/02-RESEARCH.md,
    .planning/phases/02-toml/02-VALIDATION.md
  </read_first>
  <behavior>
    在 src/config/mod.rs 的 #[cfg(test)] mod tests 中新增 5 个失败测试:
    - test_validate_install_config_rejects_invalid_page_size: 用 InstallConfig { page_size: 12, ..Default::default() } 调用 validate_install_config()，断言 Err，错误消息含 "page_size 无效: 12" 和 "有效值为 4/8/16/32"
    - test_validate_install_config_rejects_invalid_charset: page_size=8 charset=9，错误消息含 "charset 无效: 9"
    - test_validate_install_config_rejects_invalid_extent_size: extent_size=8（非 16/32），错误消息含 "extent_size 无效: 8"
    - test_validate_install_config_rejects_port_zero: port=0，错误消息含 "port 无效: 0" 和 "1-65535"
    - test_validate_install_config_accepts_all_valid_combinations: 对 page_size ∈ {4,8,16,32}、charset ∈ {0,1,2}、extent_size ∈ {16,32} 全部 4*3*2=24 种组合断言 is_ok()
    - test_load_and_validate_reads_tempfile_returns_config: 用 NamedTempFile 写入 "port = 5237\npage_size = 16\n"，调用 load_and_validate(file.path())，断言返回 Ok(cfg) 且 cfg.port == 5237、cfg.page_size == 16
    - test_load_and_validate_rejects_semantic_invalid_toml: 用 tempfile 写入 "page_size = 12\n"，断言 Err，{:#} 格式消息含 "page_size 无效: 12"
    - test_load_and_validate_missing_file_fails: 路径 "/nonexistent/path/dm.toml"，断言 Err，消息含 "无法读取配置文件"
    - test_load_and_validate_syntax_error_fails: tempfile 写入 'port = "not_a_number"\n'，断言 Err，消息含 "配置文件解析失败"

    在 src/config/validate.rs 的 tests mod 新增:
    - test_semantic_invalid_fixture_rejected: 用 ValidateArgs { config: "tests/fixtures/semantic_invalid.toml".into() }，调用 run()，断言 Err，{:#} 消息含 "page_size 无效: 12"

    在 src/cli.rs 的 tests mod 新增:
    - test_install_args_with_config: 解析 ["dm-installer", "install", "--config", "/etc/dm.toml"]，断言 args.config == Some(PathBuf::from("/etc/dm.toml"))
    - test_install_args_config_default_none: 解析 ["dm-installer", "install", "--defaults"]，断言 args.config.is_none()
    - test_install_args_config_and_defaults_combined: 解析 ["dm-installer", "install", "--config", "/etc/dm.toml", "--defaults"]，断言 args.config == Some(...) 且 args.defaults == true（D-03 正交性）

    在 src/install/mod.rs 的 #[cfg(test)] mod 中新增（若 mod 不存在则创建）:
    - test_load_config_from_args_uses_default_when_none: 引入 helper fn resolve_config(args: &InstallArgs) -> Result<InstallConfig>（提取自 run() 入口的 match 分支，纯逻辑无 I/O 副作用），断言 None config 返回与 InstallConfig::default() 等价的对象（比较 port/page_size/charset/extent_size/install_path/data_path/instance_name/case_sensitive 全字段相等）
    - test_load_config_from_args_uses_file_when_some: 用 tempfile 写入完整有效 TOML（参考 02-CONTEXT.md Specific Ideas 段落，port=5237 page_size=16 charset=1 extent_size=32 install_path="/opt/dmdbms" data_path="/opt/dmdbms/data" instance_name="DMSERVER" case_sensitive=true），断言 resolve_config 返回 cfg 中 cfg.port == 5237、cfg.page_size == 16、cfg.charset == 1、cfg.extent_size == 32

    创建 tests/fixtures/semantic_invalid.toml，单行内容: `page_size = 12`（无前导/尾随空格，UTF-8 无 BOM，最后保留换行）

    本任务 RED 阶段：测试编写后运行 `cargo test`，必须看到上述新增测试编译失败或断言失败（因为 load_and_validate、validate_install_config、InstallArgs.config 字段、resolve_config helper 都还不存在）。Task 2/3 实施时通过这些测试转为 GREEN。

    本任务不修改任何生产代码——只写测试和 fixture 文件。允许在 src/config/mod.rs 末尾添加 stub `pub fn load_and_validate(_path: &std::path::Path) -> anyhow::Result<InstallConfig> { unimplemented!() }` 和 `pub fn validate_install_config(_cfg: &InstallConfig) -> anyhow::Result<()> { unimplemented!() }` 仅为让测试文件编译通过；stub 调用时 panic，测试结果为 FAIL（而非编译错误）。InstallArgs.config 字段同理可先添加 `pub config: Option<std::path::PathBuf>` 但 install::run() 不消费它。
  </behavior>
  <action>
    在 src/config/mod.rs 添加 stub 函数签名（per D-06/D-04）：`pub fn load_and_validate(_path: &std::path::Path) -> anyhow::Result<InstallConfig> { unimplemented!("Task 2") }` 和 `pub fn validate_install_config(_cfg: &InstallConfig) -> anyhow::Result<()> { unimplemented!("Task 2") }`。在 src/cli.rs 的 InstallArgs 末尾追加字段 `#[arg(long)] pub config: Option<std::path::PathBuf>`，中文 doc comment：`/// TOML 配置文件路径（可选；未提供时使用内置默认参数）`。在 src/install/mod.rs 添加私有 helper `fn resolve_config(args: &crate::cli::InstallArgs) -> anyhow::Result<crate::config::InstallConfig> { match &args.config { Some(path) => crate::config::load_and_validate(path), None => Ok(crate::config::InstallConfig::default()) } }`，但 run() 暂不调用它（保留 `let config = InstallConfig::default();` 不动）。创建 tests/fixtures/semantic_invalid.toml 内容为 `page_size = 12\n`。编写 behavior 字段列出的所有测试到对应文件的 #[cfg(test)] mod tests 块中。运行 cargo test 后预期：新增测试出现 FAIL（unimplemented panic 或断言失败），既有 Phase 1 测试仍为 PASS。RED 状态确认后提交 commit `test(02-01): add failing tests for load_and_validate, validate_install_config, --config flag, install resolve_config (RED)`。
  </action>
  <verify>
    <automated>cd .claude/worktrees/agent-a693079c0c4cadfbf &amp;&amp; cargo test 2>&amp;1 | tee /tmp/02-01-task1.log; grep -E 'test result:' /tmp/02-01-task1.log; grep -cE 'FAILED|panicked' /tmp/02-01-task1.log; echo "NOTE: RED phase — FAILED output is expected and correct"</automated>
  </verify>
  <acceptance_criteria>
    - tests/fixtures/semantic_invalid.toml 存在，cat 输出严格匹配 `page_size = 12` 加换行（无其他字符）：`diff <(cat .claude/worktrees/agent-a693079c0c4cadfbf/tests/fixtures/semantic_invalid.toml) <(printf 'page_size = 12\n')` 退出码为 0
    - src/config/mod.rs 含 `pub fn load_and_validate` 和 `pub fn validate_install_config` 两个签名（grep `grep -c '^pub fn load_and_validate\|^pub fn validate_install_config' src/config/mod.rs` 输出 >= 2）
    - src/cli.rs InstallArgs 含 `pub config: Option<` 字段（grep `grep -c 'pub config: Option<' src/cli.rs` 输出 >= 1）
    - src/install/mod.rs 含 `fn resolve_config` helper（`grep -c 'fn resolve_config' src/install/mod.rs` >= 1）
    - cargo test 输出含至少 5 个新增 FAILED 测试名称（test_validate_install_config_rejects_invalid_page_size、test_load_and_validate_reads_tempfile_returns_config、test_install_args_with_config、test_load_config_from_args_uses_file_when_some 等）
    - Phase 1 既有测试（test_install_args_defaults、test_validate_args_config、test_install_config_defaults 等）仍为 PASS
    - 编译成功（无 compilation error，仅测试断言失败/panic）
  </acceptance_criteria>
  <done>
    Wave 0 测试基线建立：每个待实现行为均有失败测试覆盖；fixture 文件就位；CLI flag 已声明但未消费。后续 Task 2/3 的 GREEN 阶段通过让这些测试转 PASS 完成实现，无需再回头补测试。
  </done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: 实现 load_and_validate + validate_install_config（GREEN — config 层）</name>
  <files>
    src/config/mod.rs,
    src/config/validate.rs
  </files>
  <read_first>
    src/config/mod.rs,
    src/config/validate.rs,
    .planning/phases/02-toml/02-CONTEXT.md,
    .planning/phases/02-toml/02-PATTERNS.md,
    .planning/phases/02-toml/02-RESEARCH.md
  </read_first>
  <behavior>
    用最小实现让 Task 1 的 config 层测试全部 PASS（10 个测试）:
    - test_validate_install_config_rejects_invalid_page_size: PASS
    - test_validate_install_config_rejects_invalid_charset: PASS
    - test_validate_install_config_rejects_invalid_extent_size: PASS
    - test_validate_install_config_rejects_port_zero: PASS
    - test_validate_install_config_accepts_all_valid_combinations: PASS（24 种组合全部 is_ok）
    - test_load_and_validate_reads_tempfile_returns_config: PASS
    - test_load_and_validate_rejects_semantic_invalid_toml: PASS
    - test_load_and_validate_missing_file_fails: PASS
    - test_load_and_validate_syntax_error_fails: PASS
    - test_semantic_invalid_fixture_rejected: PASS（在 validate.rs tests）

    Phase 1 既有测试全部保持 PASS（test_valid_toml_passes、test_invalid_toml_fails、test_missing_file_fails、test_install_config_defaults、test_install_config_partial_toml 不变）。

    错误消息必须严格匹配以下字面量（用于错误链断言）:
    - `配置验证失败: page_size 无效: {v}；有效值为 4/8/16/32`
    - `配置验证失败: charset 无效: {v}；有效值 0=GB18030 1=UTF-8 2=EUC-KR`
    - `配置验证失败: extent_size 无效: {v}；有效值为 16/32`
    - `配置验证失败: port 无效: 0；有效范围为 1-65535`
    - `无法读取配置文件: {path}`（读文件失败时由 with_context 提供）
    - `配置文件解析失败`（TOML 反序列化失败时由 with_context 提供）
  </behavior>
  <action>
    在 src/config/mod.rs 实现两个 pub 函数，替换 Task 1 的 stub。函数体遵循以下约束（per D-04/D-06、02-PATTERNS.md "load_and_validate 完整实现模式" / "validate_install_config 完整实现模式"）:

    `load_and_validate(path: &Path) -> Result<InstallConfig>` 三步链：
    1. `std::fs::read_to_string(path).with_context(|| format!("无法读取配置文件: {}", path.display()))?`
    2. `toml::from_str::<InstallConfig>(&content).with_context(|| "配置文件解析失败")?`
    3. `validate_install_config(&cfg)?` 后 `Ok(cfg)`

    `validate_install_config(cfg: &InstallConfig) -> Result<()>` 用 anyhow::bail!（不引入 thiserror）按以下顺序检查并返回精确错误消息：
    - page_size: 若不在 `[4u8, 8, 16, 32]`，bail 上述 page_size 消息
    - charset: 若不在 `[0u8, 1, 2]`，bail 上述 charset 消息
    - extent_size: 若不在 `[16u8, 32]`，bail 上述 extent_size 消息
    - port: 若 == 0，bail 上述 port 消息（per Open Question 解答 + D-04 [1, 65535] 下界）
    - 全部通过返回 Ok(())

    导入：在 mod.rs 顶部添加 `use std::path::Path;` 和 `use anyhow::{Context, Result};`（若未导入）。

    src/config/validate.rs::run() 重构（per D-07）：删除原有的 read_to_string + toml::from_str 内联代码，替换为 `super::load_and_validate(&args.config)?;`，保留 println!("配置文件合法: {}", args.config.display()) 和 Ok(())。保留 use anyhow::Result（若 Context 不再用可删除）。

    路径字段（install_path、data_path）不做存在性检查（D-05）；instance_name/case_sensitive 不做额外验证（serde 类型已约束）。

    运行 cargo test 后所有 Task 1 config 层测试 PASS。GREEN 状态确认后提交 commit `feat(02-01): implement load_and_validate and validate_install_config (GREEN)`。
  </action>
  <verify>
    <automated>cd .claude/worktrees/agent-a693079c0c4cadfbf &amp;&amp; cargo test config:: 2>&amp;1 | tee /tmp/02-01-task2.log; grep -E 'test result: ok' /tmp/02-01-task2.log</automated>
  </verify>
  <acceptance_criteria>
    - cargo test config:: 输出 `test result: ok` 且 0 failed
    - 以下测试名在输出中均显示 PASS（grep `grep -cE 'test config::.*test_validate_install_config|test config::.*test_load_and_validate|test config::validate::tests::test_semantic_invalid' /tmp/02-01-task2.log` >= 6）
    - src/config/mod.rs 中 `grep -c '^pub fn load_and_validate\|^pub fn validate_install_config' src/config/mod.rs` 输出 >= 2 且不再含 `unimplemented!`（`grep -c unimplemented src/config/mod.rs` == 0）
    - src/config/validate.rs::run() 函数体不含 `toml::from_str`（已重构）：`grep -v '^#' src/config/validate.rs | grep -cE 'fn run.*\{[^}]*toml::from_str'` == 0（验证 run 函数体内无 toml::from_str；mod tests 内的允许）
    - src/config/validate.rs::run() 含 `super::load_and_validate` 调用（`grep -c 'super::load_and_validate' src/config/validate.rs` >= 1 或 `grep -c 'load_and_validate' src/config/validate.rs` >= 1）
    - 错误消息字面量验证：`cargo test config::tests::test_validate_install_config_rejects_invalid_page_size -- --nocapture 2>&1 | grep -c '有效值为 4/8/16/32'` >= 1
    - Phase 1 既有测试 PASS：`cargo test test_install_config_defaults test_install_config_partial_toml test_valid_toml_passes test_invalid_toml_fails test_missing_file_fails 2>&1 | grep -E 'test result: ok'` 非空
  </acceptance_criteria>
  <done>
    config 模块语义验证就绪：load_and_validate 三步链统一入口、validate_install_config 覆盖 D-04 全部四个枚举/范围规则、错误消息中文 + 字段名 + 有效值范围满足 INST-02 SC3、validate.rs 重构去重满足 D-07、QUAL-03 在 validate.rs 层端到端可用。
  </done>
</task>

<task type="auto" tdd="true">
  <name>Task 3: install::run() 条件分支接入 + CLI 集成（GREEN — install 层）</name>
  <files>
    src/install/mod.rs,
    src/cli.rs
  </files>
  <read_first>
    src/install/mod.rs,
    src/cli.rs,
    src/config/mod.rs,
    src/ui.rs,
    .planning/phases/02-toml/02-CONTEXT.md,
    .planning/phases/02-toml/02-PATTERNS.md
  </read_first>
  <behavior>
    用最小实现让 Task 1 的 install/cli 层测试全部 PASS（5 个测试）：
    - test_install_args_with_config: PASS（args.config == Some(PathBuf::from("/etc/dm.toml"))）
    - test_install_args_config_default_none: PASS
    - test_install_args_config_and_defaults_combined: PASS（D-03 正交性）
    - test_load_config_from_args_uses_default_when_none: PASS
    - test_load_config_from_args_uses_file_when_some: PASS（cfg.port == 5237、cfg.page_size == 16、cfg.charset == 1、cfg.extent_size == 32）

    install::run() 行为变化：
    - 第 2 行从 `let config = InstallConfig::default();` 改为调用 `let config = resolve_config(args)?;`
    - 当 args.config = Some(path)：调用 config::load_and_validate(path)，错误传播至顶层（含 with_context 链）
    - 当 args.config = None：返回 InstallConfig::default()，行为与 Phase 1 完全一致
    - 后续 7 步（check_idempotent_early_exit、fetch_package、verify_checksum、step_extract、step_confirm_params、step_silent_install、step_dminit）签名和调用顺序完全不变
    - step_confirm_params 的 skip 参数保持 `args.defaults || args.yes`，绝不包含 `args.config.is_some()`（D-08, D-09）

    Phase 1 既有 install 测试（若有）保持 PASS；端到端集成测试（带真实 ISO 的）保持 #[ignore] 状态。
  </behavior>
  <action>
    src/install/mod.rs::run() 修改第 2 行（per D-02、02-PATTERNS.md "install::run() 条件分支"）：从 `let config = InstallConfig::default();` 改为 `let config = resolve_config(args)?;`。保留 Task 1 已添加的 helper `fn resolve_config(args: &InstallArgs) -> Result<InstallConfig> { match &args.config { Some(path) => crate::config::load_and_validate(path), None => Ok(InstallConfig::default()) } }`。`tracing::info!("开始安装达梦数据库")` 第 1 行保留；不为条件分支新增日志（per 02-PATTERNS.md "tracing 日志模式"）。其余 7 步函数（check_idempotent_early_exit/fetch_package/verify_checksum/step_extract/step_confirm_params/step_silent_install/step_dminit）零修改。step_confirm_params 的 skip 表达式仍为 `args.defaults || args.yes`（不接受任何形如 `|| args.config.is_some()` 的改动，per D-09 防 Pitfall 2）。

    src/cli.rs::InstallArgs 的 `pub config: Option<PathBuf>` 字段（Task 1 已添加）保持不变，确认 doc comment 为 `/// TOML 配置文件路径（可选；未提供时使用内置默认参数）`。导入 std::path::PathBuf 已就绪（Phase 1 已有）。

    运行 cargo test 后 Task 1 的所有 install/cli 层测试 PASS，config 层测试继续 PASS。GREEN 状态确认后提交 commit `feat(02-01): wire --config flag through install::run conditional branch (GREEN)`。
  </action>
  <verify>
    <automated>cd .claude/worktrees/agent-a693079c0c4cadfbf &amp;&amp; cargo test 2>&amp;1 | tee /tmp/02-01-task3.log; grep -E 'test result:' /tmp/02-01-task3.log; cargo build --release 2>&amp;1 | tail -5</automated>
  </verify>
  <acceptance_criteria>
    - cargo test 全套输出 `test result: ok` 且 0 failed
    - cargo build --release 成功（无 compilation error / warning 阻塞）
    - src/install/mod.rs::run() 函数体含 `resolve_config(args)` 调用（`grep -c 'resolve_config(args)' src/install/mod.rs` >= 1）
    - src/install/mod.rs::run() 不再直接构造 `let config = InstallConfig::default();`（`grep -v '^#' src/install/mod.rs | grep -E 'pub async fn run' -A 5 | grep -c 'InstallConfig::default()'` == 0，仅允许在 resolve_config 内部或 mod tests 出现）
    - src/install/mod.rs::step_confirm_params 的 skip 表达式保持 `args.defaults || args.yes`，不含 args.config：`grep -A 3 'fn step_confirm_params' src/install/mod.rs | grep 'args.config'` 返回空
    - src/cli.rs 含 `pub config: Option<PathBuf>` 字段在 InstallArgs 内（`awk '/pub struct InstallArgs/,/^\}/' src/cli.rs | grep -c 'pub config: Option<PathBuf>'` >= 1）
    - CLI smoke test：`cargo run --release -- install --help 2>&1 | grep -c '\-\-config'` >= 1 （--config flag 出现在 install --help 输出中）
    - CLI smoke test：`cargo run --release -- validate --config .claude/worktrees/agent-a693079c0c4cadfbf/tests/fixtures/semantic_invalid.toml 2>&1 | grep -c 'page_size 无效: 12'` >= 1 （端到端语义验证错误传播）
    - CLI smoke test：`cargo run --release -- validate --config .claude/worktrees/agent-a693079c0c4cadfbf/tests/fixtures/valid.toml 2>&1 | grep -c '配置文件合法'` >= 1 （端到端 valid path）
    - 所有 Phase 1 cli/config/install 测试仍 PASS（`cargo test -- --skip ignored 2>&1 | grep -cE 'FAILED'` == 0）
  </acceptance_criteria>
  <done>
    Phase 2 端到端垂直切片就绪：用户运行 `dm-installer install --config dm.toml --package /path/to/dm.iso` 时 CLI 解析 → load_and_validate → 三步链验证 → InstallConfig 注入到 7 步安装流程；`dm-installer validate --config dm.toml` 共用同一验证函数，语义非法输入被精确字段名 + 有效值范围拒绝；--config 与 --defaults/--yes 正交，参数确认 UI 在无 --yes/--defaults 时仍展示配置值（D-08）；INST-02 SC1/SC2/SC4 全部满足，QUAL-03 端到端可用。
  </done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| 用户提供的 TOML 文件 → InstallConfig 反序列化 | 用户控制 TOML 字段值，可注入恶意路径或非法枚举值 |
| InstallConfig 字段 → DMInstall.bin XML 响应文件 | install_path/data_path 等字段最终拼入 XML 命令行 |
| InstallConfig 字段 → dminit 进程命令行 | 字段被传入 std::process::Command 参数 |
| 调用进程权限边界（root 要求） | Phase 1 已有 sudo re-exec 逻辑，Phase 2 沿用 |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-02-01 | Tampering | TOML 路径字段（install_path/data_path）注入 XML 元字符 | mitigate | Phase 1 已实现 `xml_escape()` 在 src/install/silent_install.rs；Phase 2 在 Task 3 端到端 smoke test 中验证 silent_install 既有路径继续生效（不修改 silent_install.rs，复用 Phase 1 既有转义） |
| T-02-02 | Tampering | TOML 枚举字段（page_size/charset/extent_size）非法值绕过验证 | mitigate | Task 2 在 `validate_install_config()` 实现严格枚举值域检查（`[4u8,8,16,32].contains(&cfg.page_size)` 等），任何不在白名单内的值用 anyhow::bail! 拒绝，错误消息含字段名和有效值范围（INST-02 SC3） |
| T-02-03 | Elevation of Privilege | install_path 指向系统目录（如 /etc、/usr/bin）导致任意路径写入 | accept | 安装器需 root 权限运行（Phase 1 已有 sudo re-exec）；路径合法性由 OS 文件系统权限和 dminit 自身检查决定；Phase 2 不在配置验证阶段拦截（per D-05 路径字段不做存在性校验，遵循"安装器负责创建"原则）；若用户提供恶意路径，需 root 同意，已是用户主动决策 |
| T-02-04 | Information Disclosure | TOML 文件读取错误暴露文件系统路径 | accept | with_context 错误链包含完整路径（如 "无法读取配置文件: /etc/secret/dm.toml"）；这是 CLI 工具的预期行为（错误诊断需要），不构成新增信息泄露面 |
| T-02-05 | Denial of Service | 超大 TOML 文件耗尽内存 | accept | std::fs::read_to_string 将整个文件载入内存；TOML 配置文件预期 < 10KB；恶意大文件场景在 root + 本机 CLI 环境下风险可接受 |
| T-02-SC | Tampering | npm/pip/cargo 包安装（供应链） | mitigate | Phase 2 不引入任何新外部依赖（per 02-RESEARCH.md "Package Legitimacy Audit"），全部使用 Phase 1 已锁定的 Cargo.lock 版本（clap/serde/toml/anyhow/tempfile），无需 slopcheck 与人类 checkpoint |

## ASVS L1 Coverage

| ASVS Category | Coverage |
|---------------|----------|
| V5.1.3 Input validation (deserialization) | validate_install_config 枚举值域检查（T-02-02） |
| V5.3.3 Output encoding (command injection) | Phase 1 既有 xml_escape 仍生效（T-02-01） |
| V5.5.1 Memory leak prevention | Rust 借用检查器 + tempfile RAII（不适用 manual mitigation） |
</threat_model>

<verification>
## Overall Phase Checks

运行 cargo test 全套：
```bash
cd .claude/worktrees/agent-a693079c0c4cadfbf && cargo test
```
预期：`test result: ok. N passed; 0 failed; ... ignored`（N >= Phase 1 测试数 + 14 新增测试）

运行端到端 CLI smoke test：
```bash
cd .claude/worktrees/agent-a693079c0c4cadfbf

# QUAL-03: validate 合法配置
cargo run --release -- validate --config tests/fixtures/valid.toml
# 预期: 配置文件合法: tests/fixtures/valid.toml

# QUAL-03 + INST-02 SC3: validate 语义非法配置
cargo run --release -- validate --config tests/fixtures/semantic_invalid.toml
# 预期: 错误退出码 != 0，stderr 含 "page_size 无效: 12；有效值为 4/8/16/32"

# INST-02 SC1（不带 --package 会在 fetch_package 阶段失败，但 config 加载成功证明流程开启）
cargo run --release -- install --config tests/fixtures/valid.toml --defaults 2>&1 | head -20
# 预期: tracing 日志显示 "开始安装达梦数据库" + "[1/7] 幂等性检测"（说明 config 已成功加载）
```

源码不变量检查（Phase 1 行为保护）：
```bash
# step_confirm_params 的 skip 仍为 args.defaults || args.yes（D-09 防 Pitfall 2）
grep -A 3 'fn step_confirm_params' src/install/mod.rs | grep -v '^#' | grep 'args.config' && echo "FAIL: D-09 违反" || echo "PASS: D-09 保护生效"

# install::run 不再硬编码 InstallConfig::default() 而通过 resolve_config
awk '/pub async fn run/,/^}/' src/install/mod.rs | grep -c 'resolve_config'
# 预期 >= 1
```

错误消息字面量验证（INST-02 SC3）：
```bash
cargo test config::tests::test_validate_install_config_rejects_invalid_page_size -- --nocapture 2>&1 | grep '有效值为 4/8/16/32'
cargo test config::tests::test_validate_install_config_rejects_port_zero -- --nocapture 2>&1 | grep '1-65535'
```
</verification>

<success_criteria>
## Measurable Completion

- [ ] cargo test 全套绿（0 failed），含 Phase 1 既有测试 + Phase 2 新增 14 个测试
- [ ] cargo build --release 无 warning 阻塞
- [ ] `dm-installer install --help` 输出含 `--config <PATH>` 行
- [ ] `dm-installer validate --config tests/fixtures/valid.toml` 输出 `配置文件合法: tests/fixtures/valid.toml`，退出码 0
- [ ] `dm-installer validate --config tests/fixtures/semantic_invalid.toml` 错误退出，stderr 含 `page_size 无效: 12` 和 `有效值为 4/8/16/32`
- [ ] `dm-installer install --config tests/fixtures/valid.toml --defaults` 日志显示安装流程启动（fetch_package 阶段失败可接受——证明 config 加载成功路径打通）
- [ ] src/cli.rs::InstallArgs 含 `pub config: Option<PathBuf>` 字段
- [ ] src/config/mod.rs 含 `pub fn load_and_validate` 和 `pub fn validate_install_config` 实现（非 unimplemented stub）
- [ ] src/config/validate.rs::run() 重构为调用 `super::load_and_validate`（不再含内联 toml::from_str）
- [ ] src/install/mod.rs::run() 改为通过 `resolve_config(args)` 决定 config 来源
- [ ] src/install/mod.rs::step_confirm_params 的 skip 仍为 `args.defaults || args.yes`（D-09）
- [ ] tests/fixtures/semantic_invalid.toml 存在且内容为单行 `page_size = 12`
- [ ] 三次 commit 形成 RED→GREEN→GREEN 历史（test → feat config → feat install）
</success_criteria>

<output>
执行完成时创建 SUMMARY：
`.planning/phases/02-toml/02-01-SUMMARY.md`

SUMMARY 须包含：
- 实现摘要（4 文件 + 1 fixture）
- 14 个新增测试名称与覆盖映射（INST-02 SC1-SC4、QUAL-03）
- D-01 至 D-09 决策追踪表（每条决策对应实现位置）
- STRIDE 缓解状态（T-02-01 至 T-02-SC）
- 端到端 CLI smoke test 实际输出
- Phase 1 行为保护检查结果（D-09 不变量）
- 已知遗留（manual-only verification: 真实 ISO + root 环境的 INST-02 SC1 完整流程；INST-02 SC2 的参数确认 UI 显示——见 02-VALIDATION.md "Manual-Only Verifications"）
</output>
