> **发布元数据**：ZCode · Kimi K3（评测者确认）· R01
> **来源**：`zcode/输出/zcode/T02/R01/BENCHMARK_RESULT.md`
> **原始文件 SHA-256**：`1efc086feba84e8c1b0373c7cf7059f45b64b3a5e88cb921e9c2836af8dad791`
> 本公开副本已将评测机绝对路径归一化；下文 PASS/FAIL/BLOCKED 是执行报告自述状态，不等同于独立验收结论。

---

PASS

# T02：配置优先级与错误路径（只读分析）

## 调用链概览

`python3 -m ledger list` 的入口与调用链（所有路径相对 workspace 根）：

1. `ledger/__main__.py`：`raise SystemExit(main())`，调用 `ledger/cli.py` 的 `main()`。
2. `ledger/cli.py` 的 `build_parser()`：定义全局参数 `--config` 和 `--data-file`（均为全局可选参数，位于子命令 `list` / `summary` 之前解析）。
3. `ledger/cli.py` 的 `main(argv=None)` 第 42 行：
   `path = resolve_data_file(cli_path=args.data_file, environ=os.environ, config_path=args.config)`
4. `ledger/settings.py` 的 `resolve_data_file(cli_path=None, environ=None, config_path=None)`：完成全部优先级与路径解析，返回一个 `pathlib.Path`。
5. `ledger/cli.py` 的 `main()` 第 43 行：`transactions = load_transactions(path)` —— **最终由 `ledger/storage.py` 的 `load_transactions(path)` 函数真正打开并读取 CSV**（第 13 行 `Path(path).open("r", encoding="utf-8", newline="")`，用 `csv.DictReader` 解析）。

## 优先级顺序（ledger/settings.py 的 resolve_data_file）

`resolve_data_file` 内部是一个 `if / elif / elif / else` 链，先到先得，优先级从高到低：

1. **全局 `--data-file` 参数**（`cli_path`，settings.py 第 13-14 行）。已实测：当 `LEDGER_DATA_FILE` 与 `--data-file` 同时给出时，报错路径指向 `--data-file` 的值，证明 CLI 参数优先。
2. **`LEDGER_DATA_FILE` 环境变量**（settings.py 第 15-16 行，`environ.get("LEDGER_DATA_FILE")`，空字符串视为未设置，因 `if` 条件为真值判断）。已实测：env 与 `--config` 同时给出时，使用的是 env 的值，报错为 `/nonexistent-env`，证明 env 优先于 config。
3. **JSON 配置文件 `--config`**（`config_path`，settings.py 第 17-28 行）。
4. **内置默认文件**（settings.py 第 29-30 行）：`DEFAULT_DATA_FILE = Path(__file__).resolve().parent / "data" / "transactions.csv"`（settings.py 第 5 行），即 workspace 内的 `ledger/data/transactions.csv`。默认路径在此模块内即被 resolve 成绝对路径，与工作目录无关。

## 相对路径解析规则（settings.py 第 31-34 行）

```
path = Path(selected).expanduser()
if not path.is_absolute() and selected_base is not None:
    path = selected_base / path
return path.resolve()
```

- 所有来源的值都会先 `expanduser()`（展开 `~`），最后统一 `path.resolve()`。
- **来自配置文件的 `data_file`**：若为相对路径，以**配置文件所在目录**为基准拼接（`selected_base = config_file.parent`，settings.py 第 28、32-33 行）。已实测：`/tmp/t02rel/sub/cfg.json` 中写 `{"data_file":"data/transactions.csv"}`，报错显示实际查找 `/private/tmp/t02rel/sub/data/transactions.csv`（macOS 上 `/tmp` 是 `/private/tmp` 的符号链接，`resolve()` 展开了它），与 CWD 无关。
- **来自 `--data-file` 或 `LEDGER_DATA_FILE` 的相对路径**：`selected_base` 为 `None`，不做拼接，`resolve()` 以**进程当前工作目录（CWD）**为基准解析为绝对路径。已实测：`LEDGER_DATA_FILE=ledger/data/transactions.csv` 在 workspace 根目录运行成功。

## 错误路径处理

错误分两层：配置选择阶段（`ledger/settings.py` 的 `resolve_data_file` 主动抛异常）与文件读取阶段（`ledger/storage.py` 的 `load_transactions` 打开文件时由 `Path.open` 抛出）。两类异常都被 `ledger/cli.py` 的 `main()` 第 54-56 行捕获：

```
except (FileNotFoundError, ValueError, OSError) as exc:
    print(f"ledger: error: {exc}", file=sys.stderr)
    return 2
```

即统一打印 `ledger: error: <消息>` 到 **stderr** 并**退出码 2**（经 `__main__.py` 的 `SystemExit`）。具体分支：

1. **配置文件不存在**：settings.py 第 21-22 行捕获 `FileNotFoundError` 并重新抛出 `FileNotFoundError(f"config file not found: {config_file}")`（`from None` 抑制链式 traceback）。
2. **配置格式错误**（JSON 语法错误或文件非 UTF-8）：settings.py 第 23-24 行捕获 `json.JSONDecodeError` / `UnicodeDecodeError`，抛 `ValueError(f"invalid config file {config_file}: {exc}")`。
3. **配置缺少有效 `data_file`**：settings.py 第 25-27 行——若 JSON 顶层不是 dict，取 `None`；若值不是非空字符串，抛 `ValueError("config must contain a non-empty string 'data_file'")`。
4. **目标 CSV 文件不存在**：`resolve_data_file` **不检查目标文件是否存在**（对默认文件和 CLI/env 来源均无存在性检查），直到 `ledger/storage.py` 的 `load_transactions` 第 13 行 `Path(path).open(...)` 才抛出原生 `FileNotFoundError`（形如 `[Errno 2] No such file or directory: '<绝对路径>'`），同样被 cli.py 捕获，退出码 2。注意此时报错中的路径已是 resolve 后的绝对路径。
5. **CSV 内容错误**（本题未要求，但属同一错误通道）：缺列时 `load_transactions` 抛 `ValueError("missing CSV columns: ...")`；行数据非法时抛 `ValueError(f"invalid transaction at line {n}: ...")`。

## 最终读取模块

**`ledger/storage.py` 的 `load_transactions(path)`** 是唯一实际打开并解析 CSV 的函数（`csv.DictReader`，要求列 `id, date, category, amount`，见 `_REQUIRED_COLUMNS`，storage.py 第 8 行）。`settings.py` 只负责选出路径，不读数据。

## 实测命令与原始输出

全部在 workspace 根目录执行，均为只读操作；临时文件仅写入 /tmp。

1. 默认路径：
```
$ python3 -m ledger list
A-02 | 2025-01-01 | Food | $12.00
A-01 | 2025-01-01 | Food | $-1.95
B-01 | 2025-01-02 | Travel | $100.10
C-01 | 2025-01-31 | food | $0.01
D-01 | 2025-02-01 | 家庭 | $3.50
E-01 | 2024-12-31 | Transit | $2.00
exit=0   (time: 0.066s total)
```

2. env 指向不存在文件：
```
$ LEDGER_DATA_FILE=/nonexistent python3 -m ledger list
ledger: error: [Errno 2] No such file or directory: '/nonexistent'
exit=2
```

3. CLI 优先于 env：
```
$ LEDGER_DATA_FILE=/nonexistent-env python3 -m ledger --data-file /nonexistent-cli list
ledger: error: [Errno 2] No such file or directory: '/nonexistent-cli'
exit=2
```

4. env 优先于 config（config 文件甚至未被读取——注意此处 /tmp/t02-empty.json 本身是会触发 ValueError 的无效配置，但报错来自 env 路径，证明走了 elif 分支）：
```
$ LEDGER_DATA_FILE=/nonexistent-env python3 -m ledger --config /tmp/t02-empty.json list
ledger: error: [Errno 2] No such file or directory: '/nonexistent-env'
exit=2
```

5. env 相对路径按 CWD 解析（在 workspace 根运行）：
```
$ LEDGER_DATA_FILE=ledger/data/transactions.csv python3 -m ledger list
A-02 | 2025-01-01 | Food | $12.00   （首行，其余略）
exit=0
```

6. 配置文件不存在：
```
$ python3 -m ledger --config /tmp/does-not-exist.json list
ledger: error: config file not found: /tmp/does-not-exist.json
exit=2
```

7. 配置 JSON 语法错误（/tmp/t02-bad.json 内容为 `{bad json`）：
```
$ python3 -m ledger --config /tmp/t02-bad.json list
ledger: error: invalid config file /tmp/t02-bad.json: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
exit=2
```

8. 配置缺少 data_file（/tmp/t02-empty.json 内容为 `{}`）：
```
$ python3 -m ledger --config /tmp/t02-empty.json list
ledger: error: config must contain a non-empty string 'data_file'
exit=2
```

9. 配置内相对 data_file 以配置文件目录为基准（/tmp/t02rel/sub/cfg.json 内容为 `{"data_file":"data/transactions.csv"}`，在 workspace 根运行）：
```
$ python3 -m ledger --config /tmp/t02rel/sub/cfg.json list
ledger: error: [Errno 2] No such file or directory: '/private/tmp/t02rel/sub/data/transactions.csv'
exit=2
```
（`/private/tmp` 是 macOS 上 `/tmp` 符号链接经 `resolve()` 展开后的结果，同时证明 resolve 被实际执行。）

## 耗时

源码阅读与实验合计约 3 分钟；单次 `python3 -m ledger list` 实测约 0.07s（time 输出 0.066s total）。

## 尚未完成的要求

无。题面所有项（优先级顺序、相对路径解析、配置格式错误处理、文件不存在处理、最终读取模块）均已分析并实测。

## 不确定 / 标注事项

- `--config` 与 `--data-file` 是 argparse 定义在根 parser 上的全局参数（cli.py `build_parser()` 第 28-29 行），必须放在子命令 `list` 之前才能被识别；这是 argparse 行为，代码中无特殊处理。
- `LEDGER_DATA_FILE` 为空字符串时视为未设置（真值判断），此点由代码逻辑推出（settings.py 第 15 行 `environ.get(...)` 的真值性），未单独实测。
- 默认文件存在性同样不在 `resolve_data_file` 中检查，若 `ledger/data/transactions.csv` 被删除，报错会来自 storage.py 的 open，行为与其他来源一致（由代码推定，未破坏 workspace 实测）。

## 会话隔离状态

- 仅读取了指定 workspace（`<评测根目录>/zcode/输出/zcode/T02/R01/workspace`）内的文件；未访问其他题目目录或基准仓库的其他路径。
- 未修改 workspace 内任何文件（本题只读，`git status` 层面无写入操作）。
- 未联网，未安装依赖。
- 实验用临时文件全部位于 /tmp（/tmp/t02-bad.json、/tmp/t02-empty.json、/tmp/t02rel/sub/cfg.json），未放入 workspace。
- 本文件（BENCHMARK_RESULT.md）写在运行目录根部，不在 workspace 内。
- 上述每条命令均为实际执行，输出为原始粘贴，未声称运行过未执行的检查。
