feat(cli): add order delegation + doctor self-check (phase 3)
docs-only + cli: extend scripts/gaokao-cli with two thin wrappers
without changing data/rules, data/majors_catalog, or rules/_truth.
- gaokao-cli order <subcommand> delegates argv to data.orders.cli.main
with the 'order' prefix stripped, so callers can use the unified
command face without changing the existing orders parser.
- gaokao-cli doctor <json?> returns a single self-check payload that
reuses RuleLoader + MajorsCatalogLoader (no new dependencies, no
gaokato/ scaffolding). Truth-root or catalog-root failures degrade
gracefully with structured {error, ok=false} sections instead of
raising.
- tests/test_cli_doctor_phase3.py covers: help text, order delegation
to list subcommand, doctor ok-path, doctor failure-path.
Also re-aligns docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md (new) +
docs/CURRENT_STATE.md (single execution口径) + rules/majors source-
of-truth index files so design-phase numbering no longer drifts from
execution-phase numbering. Pre-existing 2026-06-16 optimization board
and 2026-06-17 phase2 plan are demoted to historical snapshots.
P0_P1_P2 remediation plan §4 historical cards get a red warning
banner so they are not re-read as current pending.
Verification:
- focused: 4 passed
- dev-verify: 770 passed, 1 failed
(alipay_sim E2E failure pre-existed this commit; caused by an
uncommitted upstream edit in admin/routes/web_public.py that
rewires payment_return -> /portal/{token}/payment-success while
the E2E still asserts /portal/{token}/status; out of scope for
this commit and intentionally not touched.)
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from data.majors_catalog.cli import (
|
||||
@@ -87,6 +88,15 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
audit_run.add_argument("--catalog-root", default=str(DEFAULT_CATALOG_ROOT))
|
||||
audit_run.add_argument("--json", action="store_true")
|
||||
|
||||
subparsers.add_parser("order", help="delegate to data.orders.cli (orders commands)")
|
||||
|
||||
doctor_parser = subparsers.add_parser(
|
||||
"doctor", help="self-check rules/majors/audit/catalog wiring"
|
||||
)
|
||||
doctor_parser.add_argument("--truth-root", default=str(DEFAULT_RULES_TRUTH_ROOT))
|
||||
doctor_parser.add_argument("--catalog-root", default=str(DEFAULT_CATALOG_ROOT))
|
||||
doctor_parser.add_argument("--json", action="store_true")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
@@ -99,8 +109,19 @@ def _emit(payload: dict[str, object], json_output: bool) -> int:
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
# `order` is a pure delegation marker. Handle it before argparse so
|
||||
# the orders parser can see its native subcommand + flags directly
|
||||
# (including `--help`).
|
||||
tokens = list(sys.argv[1:]) if argv is None else list(argv)
|
||||
if tokens and tokens[0] == "order":
|
||||
from data.orders.cli import main as orders_main
|
||||
|
||||
# data.orders.cli.main expects subcommands without the `order`
|
||||
# prefix (its parser exposes `create` / `list` / `show` / ...).
|
||||
return orders_main(tokens[1:])
|
||||
|
||||
parser = _build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
args = parser.parse_args(tokens)
|
||||
|
||||
if args.command == "rules" and args.rules_command == "status":
|
||||
loader = RuleLoader.from_truth_root(Path(args.truth_root))
|
||||
@@ -167,6 +188,48 @@ def main(argv: list[str] | None = None) -> int:
|
||||
)
|
||||
return _emit(payload, args.json)
|
||||
|
||||
if args.command == "doctor":
|
||||
truth_root = Path(args.truth_root)
|
||||
catalog_root = Path(args.catalog_root)
|
||||
rules_section: dict[str, object]
|
||||
rules_ok: bool
|
||||
try:
|
||||
rule_status = RuleLoader.from_truth_root(truth_root).build_status()
|
||||
rules_section = {
|
||||
"province_count": rule_status.province_count,
|
||||
"national_rule_count": rule_status.national_rule_count,
|
||||
}
|
||||
rules_ok = rule_status.province_count > 0
|
||||
except (FileNotFoundError, KeyError, ValueError) as exc:
|
||||
rules_section = {"error": f"{type(exc).__name__}: {exc}"}
|
||||
rules_ok = False
|
||||
|
||||
def _safe_build(builder: object, root: Path) -> tuple[dict[str, object], bool]:
|
||||
try:
|
||||
payload = builder(root) # type: ignore[operator]
|
||||
return dict(payload), bool(payload.get("ok", True)) # type: ignore[union-attr]
|
||||
except (FileNotFoundError, KeyError, ValueError) as exc:
|
||||
return (
|
||||
{"ok": False, "error": f"{type(exc).__name__}: {exc}"},
|
||||
False,
|
||||
)
|
||||
|
||||
majors_status, majors_status_ok = _safe_build(
|
||||
build_majors_status_payload, catalog_root
|
||||
)
|
||||
majors_verify, majors_verify_ok = _safe_build(
|
||||
build_majors_verify_payload, catalog_root
|
||||
)
|
||||
majors_ok = majors_status_ok and majors_verify_ok
|
||||
overall_ok = rules_ok and majors_ok
|
||||
payload = {
|
||||
"ok": overall_ok,
|
||||
"rules": rules_section,
|
||||
"majors": majors_status,
|
||||
"majors_verify": majors_verify,
|
||||
}
|
||||
return _emit(payload, args.json)
|
||||
|
||||
parser.error("unsupported command")
|
||||
return 2
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# Batch 1 审计收敛执行板(2026-06-16)
|
||||
|
||||
> **状态**:**已收口(历史快照)**。当前执行真相请看 `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` + `docs/CURRENT_STATE.md` §0。
|
||||
> 本文件仅作历史审计记录保留,不再作为当前执行口径源。
|
||||
|
||||
真相源输入:
|
||||
|
||||
1. `docs/plans/2026-06-16-optimization-program.md`
|
||||
|
||||
97
docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md
Normal file
97
docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# ACTIVE_EXECUTION_BOARD_2026-06-17
|
||||
|
||||
> 真相源: `docs/CURRENT_STATE.md` §0
|
||||
> 设计快照: `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md`
|
||||
> 优化计划: `docs/plans/2026-06-16-optimization-program.md`
|
||||
> 状态: 已收口 / 未启动 两段式
|
||||
|
||||
---
|
||||
|
||||
## 1. 已收口(执行 Phase 1 / 1.5 / 2)
|
||||
|
||||
### 1.1 执行 Phase 1 + 1.5 — 规则真相源化
|
||||
|
||||
- 已落地:
|
||||
- `rules/_truth/national.yaml`(国家级规则)
|
||||
- `rules/_truth/province/*.yaml`(27 省,2026 版)
|
||||
- `data/rules/loader.py` `RuleLoader.from_truth_root`
|
||||
- `data/rules/audit_engine.py` `AuditEngine.audit_plan`
|
||||
- `data/rules/cli.py` `rules {status,verify}`
|
||||
- `scripts/migrate_province_rules_to_truth.py`(从 `PROVINCE_RULES` 一次性迁移)
|
||||
- `tests/test_rules_truth_phase1.py` + `tests/test_audit_cli_major_validation_phase2.py`
|
||||
- 提交链:
|
||||
- `ae4835e` rules truth source loader + rules CLI
|
||||
- `8a61b8f` phase1.5 verify 收口 + 旧 checker 迁移
|
||||
- 三仓: gitea / origin / tksea 同步至 `edc5b11`
|
||||
|
||||
### 1.2 执行 Phase 2 Batch 1 — 国家级专业目录 MVP
|
||||
|
||||
- 已落地:
|
||||
- `data/majors_catalog/models.py` `NationalMajor`
|
||||
- `data/majors_catalog/loader.py` `MajorsCatalogLoader`
|
||||
- `data/majors_catalog/national/2024.json` + `national/latest.json`(13 个 curated subset)
|
||||
- `data/majors_catalog/cli.py` `majors {status,lookup,verify,changes}`
|
||||
- `tests/test_majors_catalog_phase2.py` + `tests/test_majors_cli_phase2.py`
|
||||
- 提交: `36ad58a feat(majors): add phase2 national catalog mvp`
|
||||
|
||||
### 1.3 执行 Phase 2 Batch 2 — 校级招生目录骨架
|
||||
|
||||
- 已落地:
|
||||
- `data/majors_catalog/schools/2026/10001.json`(1 所校级骨架样本)
|
||||
- `data/majors_catalog/cli.py` `majors school-status / school-verify --year`
|
||||
- `tests/test_majors_school_catalog_phase2.py` + `tests/test_majors_school_cli_phase2.py`
|
||||
- 提交: `6b1157f feat(majors): add school catalog skeleton and verification`
|
||||
|
||||
### 1.4 执行 Phase 2 Batch 3 — audit_engine 接入 major_validation
|
||||
|
||||
- 已落地:
|
||||
- `data/rules/audit_engine.py` `_validate_majors` 接入 `audit_plan`
|
||||
- `data/rules/cli.py` `audit run --province --plan --truth-root --catalog-root --json`
|
||||
- 输出口径: `MAJORS.not_found` / `MAJORS.non_active`
|
||||
- `tests/test_audit_engine_major_validation_phase2.py` + `tests/test_audit_cli_major_validation_phase2.py`
|
||||
- 提交: `edc5b11 feat(audit): validate majors in structured audit flow`
|
||||
|
||||
---
|
||||
|
||||
## 2. 未启动(执行 Phase 3 = 统一 CLI 命令面)
|
||||
|
||||
> 这是当前唯一未启动且**不依赖外部前置**的 P1 主目标。
|
||||
> Lane D(设计)的 Phase 4 = Lane E(执行)的 Phase 3,请勿混淆。
|
||||
|
||||
### 2.1 范围
|
||||
|
||||
按 6/16 优化计划 Phase 4 / 设计层 Phase 4 收敛:
|
||||
|
||||
- `gaokao-cli {order,share,delivery,retention,payment,backup,channel,doctor}` 收纳
|
||||
- `gaokao-cli doctor` 最小自检(已有 loader / engine / CLI 调用面,不依赖 `gaokato/`)
|
||||
- 旧 `scripts/gaokao-*` wrapper 的 deprecation warning
|
||||
- `docs/CLI_API_MAPPING.md` 同步"已落地命令清单"节
|
||||
|
||||
### 2.2 暂不进入本轮
|
||||
|
||||
- `gaokato/services/*` 与 `gaokato/transport/*`(设计层 Phase 5 = 执行层 Phase 4)
|
||||
- `gaokato/capabilities/registry.py`(等 doctor 落地后再用其 JSON 输出作为初版源)
|
||||
- 退出码统一改写(沿用各子 CLI 现状,不与 Lane D 退出码收口任务冲突)
|
||||
|
||||
### 2.3 不可触发的真源改动
|
||||
|
||||
- 不改 `data/rules/loader.py` 字段
|
||||
- 不改 `data/majors_catalog/*` 数据契约
|
||||
- 不改 `rules/_truth/*` yaml
|
||||
- 不重写 `data/orders/cli.py` 状态机
|
||||
|
||||
---
|
||||
|
||||
## 3. 收口规则(防止 4 条线再漂)
|
||||
|
||||
1. 状态查询一律走本文件 + `docs/CURRENT_STATE.md` §0
|
||||
2. 设计层 Phase 编号不等于执行层 Phase 编号;引用必须带"执行/设计"前缀
|
||||
3. 新写"待做"必须指明属于哪个执行 Phase
|
||||
4. 历史评审/历史快照/历史整改计划三类文件不再进入本板追踪
|
||||
5. docs-only commit 必须在 commit message 标注"docs-only",避免与代码 commit 混轨
|
||||
|
||||
---
|
||||
|
||||
## 4. 与本板同步的当前任务文档
|
||||
|
||||
- `/home/long/.hermes/docs/tasks/current/TASK-20260617-gaokao-optimization-batch2.md`
|
||||
94
docs/CURRENT_RULES_STATE_2026-06-16.md
Normal file
94
docs/CURRENT_RULES_STATE_2026-06-16.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# CURRENT_RULES_STATE_2026-06-16
|
||||
|
||||
最后更新: 2026-06-16
|
||||
真相源: `docs/RULES_SOURCE_OF_TRUTH.md`
|
||||
执行上下文: `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.1
|
||||
|
||||
---
|
||||
|
||||
## 1. 国家级规则覆盖矩阵
|
||||
|
||||
| scope | rule_key | title | severity | status | source_evidence_id |
|
||||
| -------- | ------------- | ------------ | -------- | ------ | --------------------------- |
|
||||
| national | parallel_rule | 平行志愿规则 | info | active | national-2026-parallel-rule |
|
||||
|
||||
> 当前国家级仅 1 条(参考用);其余通用规则随各省差异实现。
|
||||
|
||||
---
|
||||
|
||||
## 2. 省级规则覆盖矩阵(2026 版)
|
||||
|
||||
| province | slug | rules_count | status | yaml |
|
||||
| -------- | ------------ | ----------- | ------ | ----------------------------------------- |
|
||||
| 北京 | beijing | TBD | active | `rules/_truth/province/beijing.yaml` |
|
||||
| 上海 | shanghai | TBD | active | `rules/_truth/province/shanghai.yaml` |
|
||||
| 天津 | tianjin | TBD | active | `rules/_truth/province/tianjin.yaml` |
|
||||
| 重庆 | chongqing | TBD | active | `rules/_truth/province/chongqing.yaml` |
|
||||
| 河北 | hebei | TBD | active | `rules/_truth/province/hebei.yaml` |
|
||||
| 山西 | shanxi | TBD | active | `rules/_truth/province/shanxi.yaml` |
|
||||
| 内蒙古 | (待补) | — | — | — |
|
||||
| 辽宁 | liaoning | TBD | active | `rules/_truth/province/liaoning.yaml` |
|
||||
| 吉林 | jilin | TBD | active | `rules/_truth/province/jilin.yaml` |
|
||||
| 黑龙江 | heilongjiang | TBD | active | `rules/_truth/province/heilongjiang.yaml` |
|
||||
| 江苏 | jiangsu | TBD | active | `rules/_truth/province/jiangsu.yaml` |
|
||||
| 浙江 | zhejiang | TBD | active | `rules/_truth/province/zhejiang.yaml` |
|
||||
| 安徽 | anhui | TBD | active | `rules/_truth/province/anhui.yaml` |
|
||||
| 福建 | fujian | TBD | active | `rules/_truth/province/fujian.yaml` |
|
||||
| 江西 | jiangxi | TBD | active | `rules/_truth/province/jiangxi.yaml` |
|
||||
| 山东 | shandong | TBD | active | `rules/_truth/province/shandong.yaml` |
|
||||
| 河南 | henan | TBD | active | `rules/_truth/province/henan.yaml` |
|
||||
| 湖北 | hubei | TBD | active | `rules/_truth/province/hubei.yaml` |
|
||||
| 湖南 | hunan | TBD | active | `rules/_truth/province/hunan.yaml` |
|
||||
| 广东 | guangdong | TBD | active | `rules/_truth/province/guangdong.yaml` |
|
||||
| 广西 | guangxi | TBD | active | `rules/_truth/province/guangxi.yaml` |
|
||||
| 海南 | hainan | TBD | active | `rules/_truth/province/hainan.yaml` |
|
||||
| 四川 | sichuan | TBD | active | `rules/_truth/province/sichuan.yaml` |
|
||||
| 贵州 | guizhou | TBD | active | `rules/_truth/province/guizhou.yaml` |
|
||||
| 云南 | yunnan | TBD | active | `rules/_truth/province/yunnan.yaml` |
|
||||
| 西藏 | xizang | TBD | active | `rules/_truth/province/xizang.yaml` |
|
||||
| 陕西 | (待补) | — | — | — |
|
||||
| 甘肃 | gansu | TBD | active | `rules/_truth/province/gansu.yaml` |
|
||||
| 青海 | qinghai | TBD | active | `rules/_truth/province/qinghai.yaml` |
|
||||
| 宁夏 | (待补) | — | — | — |
|
||||
| 新疆 | xinjiang | TBD | active | `rules/_truth/province/xinjiang.yaml` |
|
||||
|
||||
> 当前已落地 27 省 yaml(不含内蒙古/陕西/宁夏,3 个待补)。每省 `rules_count` 由本轮 Batch 4 候选任务回填。
|
||||
|
||||
---
|
||||
|
||||
## 3. 证据链覆盖矩阵
|
||||
|
||||
| 类别 | 已落地 | 备注 |
|
||||
| -------- | ------ | ------------------------------------------------------- |
|
||||
| 国家级 | ⚠ 占位 | `national-2026-parallel-rule` 是占位 ID,未指向真实文件 |
|
||||
| 27 省 | ❌ 0 | `rules/_evidence/` 目录尚未建 |
|
||||
| 全国通用 | ❌ 0 | 需 `rules/_evidence/national/` 子目录 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前能力面
|
||||
|
||||
- ✅ `gaokao-cli rules status --json` 报告 27 省在场
|
||||
- ✅ `gaokao-cli rules verify --json` 校验 `national.yaml` + `province/`
|
||||
- ✅ `gaokao-cli audit run --province <p> --plan <json> --json` 跑结构化审计
|
||||
- ❌ `gaokao-cli rules explain <rule_id>` 未实现
|
||||
- ❌ "最近验证时间 > 90 天"自动告警未实现
|
||||
|
||||
---
|
||||
|
||||
## 5. 与旧 `PROVINCE_RULES` 的迁移
|
||||
|
||||
- 迁移方式: 一次性脚本 `scripts/migrate_province_rules_to_truth.py`
|
||||
- 旧字典保留作为过渡期镜像,新代码只读 `_truth`
|
||||
- 旧 `gaokao-checker` 内部已委托给 `audit_engine`
|
||||
- 迁移完整性由 `tests/test_rules_truth_phase1.py` 锁定
|
||||
|
||||
---
|
||||
|
||||
## 6. 下一阶段(Batch 4 候选)
|
||||
|
||||
1. `rules/_evidence/<prov>/<year>-<topic>.md` 补建
|
||||
2. 逐省 `rules_count` 实际数回填
|
||||
3. `gaokao-cli rules explain <rule_id>` 命令
|
||||
4. "最近验证时间 > 90 天"自动告警
|
||||
5. 内蒙古/陕西/宁夏 3 省 yaml 补齐
|
||||
@@ -1,24 +1,41 @@
|
||||
# CURRENT_STATE
|
||||
|
||||
最后更新: 2026-06-17
|
||||
状态词: 本地验证完成(Phase2 major_validation 已接入 + 目录/审核闭环已收口)
|
||||
状态词: 本地验证完成(执行 Phase 1 规则真相源化 + Phase 1.5 verify 收口 + Phase 2 专业目录三 Batch 全部收口 + 下一阶段 = 执行 Phase 3 统一 CLI 命令面,未启动)
|
||||
|
||||
真相源优先级:
|
||||
|
||||
1. 本文件
|
||||
2. `docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md`(当前 Phase2 专业目录执行计划)
|
||||
3. `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md`(专业目录/CLI 总设计,当前被 Phase2 计划补充)
|
||||
4. `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md`(规划/实现漂移审计)
|
||||
5. `docs/RULES_SOURCE_OF_TRUTH.md`(规则真相源索引)
|
||||
6. `docs/MAJOR_DATA_SOURCE_OF_TRUTH.md`(专业目录真相源索引)
|
||||
7. `docs/CLI_API_MAPPING.md`(CLI/API 映射索引)
|
||||
2. `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md`(6/17 当前执行板,docs-only 收口任务之一)
|
||||
3. `docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md`(Phase 2 已收口快照)
|
||||
4. `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md`(设计快照;Phase 编号需与执行口径消歧)
|
||||
5. `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md`(历史规划/实现漂移审计)
|
||||
6. `docs/RULES_SOURCE_OF_TRUTH.md`(规则真相源索引;待补建)
|
||||
7. `docs/MAJOR_DATA_SOURCE_OF_TRUTH.md`(专业目录真相源索引)
|
||||
8. `docs/CLI_API_MAPPING.md`(CLI/API 映射索引)
|
||||
9. `docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md`(历史整改板;§4 卡片以 §2.1 状态归一为准)
|
||||
10. `docs/FRONTEND_UI_AUDIT_2026-06-16.md` / `docs/FRONTEND_UI_EXECUTION_BOARD_2026-06-16.md`
|
||||
11. `docs/ACTIVE_REMEDIATION_2026-06-13.md`
|
||||
12. `product/PRD.md` / `product/ROADMAP.md` / `docs/IMPLEMENTATION_PLAN_v2.md`
|
||||
13. `docs/PRODUCTION_DEPLOYMENT_CHECKLIST_2026-06-15.md`
|
||||
14. `reports/PRODUCT_PLANNING_TECH_ALIGNMENT_REVIEW_2026-06-13.md`(历史评审快照)
|
||||
|
||||
8. docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md(当前整改板;2026-06-15 已做状态归一)
|
||||
9. docs/FRONTEND_UI_AUDIT_2026-06-16.md
|
||||
10. docs/FRONTEND_UI_EXECUTION_BOARD_2026-06-16.md
|
||||
11. docs/ACTIVE_REMEDIATION_2026-06-13.md
|
||||
12. product/PRD.md / product/ROADMAP.md / docs/IMPLEMENTATION_PLAN_v2.md
|
||||
13. docs/PRODUCTION_DEPLOYMENT_CHECKLIST_2026-06-15.md
|
||||
14. reports/PRODUCT_PLANNING_TECH_ALIGNMENT_REVIEW_2026-06-13.md(历史评审快照)
|
||||
---
|
||||
|
||||
## 0. 单一执行口径(2026-06-17)
|
||||
|
||||
为消除设计 Phase 编号与执行 Phase 编号漂移,本节是当前唯一口径:
|
||||
|
||||
| 设计层 Phase(DESIGN_RULES_TRUSTED_CLI_2026-06-16.md) | 执行层 Phase(当前真相) | 状态 |
|
||||
| ------------------------------------------------------ | ------------------------------------------ | ---------------------------------------------- |
|
||||
| Phase 1 规则真相源化 | 执行 Phase 1 | ✅ 已收口(`ae4835e` + `8a61b8f`,三仓同步) |
|
||||
| Phase 2 统一审计引擎最小可用 | 执行 Phase 1.5(verify + 旧 checker 迁移) | ✅ 已收口(同上) |
|
||||
| Phase 3 专业目录接入 MVP | 执行 Phase 2 | ✅ 已收口(`36ad58a` + `6b1157f` + `edc5b11`) |
|
||||
| Phase 4 统一 CLI 命令面 | 执行 Phase 3 | ❌ 未启动 → 下一阶段 |
|
||||
| Phase 5 智能体调度 + admin 整合 | 执行 Phase 4 | ❌ 未启动 → 等 Phase 3 收口 |
|
||||
| Phase 6 文档/真相源索引收口 | 长期维护 | ⚠ 进行中(docs-only 收口本轮同步) |
|
||||
|
||||
> 设计层 Phase ≠ 执行层 Phase。状态查询一律以本表 + 6/17 执行板为准。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# MAJOR_DATA_SOURCE_OF_TRUTH
|
||||
|
||||
最后更新: 2026-06-16
|
||||
最后更新: 2026-06-17
|
||||
真相源: 本文件是"专业目录"维度的入口索引。
|
||||
审计上下文: `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md` §2.3-2.4
|
||||
设计上下文: `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §4
|
||||
执行上下文: `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.2-§1.4
|
||||
|
||||
---
|
||||
|
||||
@@ -28,20 +29,24 @@
|
||||
|
||||
### 2.1 当前已落地范围(2026-06-17)
|
||||
|
||||
- 已落地 `data/majors_catalog/` 包与 national loader/CLI
|
||||
- 已落地 `national/2024.json` + `national/latest.json`
|
||||
- 当前 `coverage_mode = mvp_subset`
|
||||
- 当前 curated subset = 13 个国家级本科专业样本
|
||||
- 已可通过:
|
||||
- `python scripts/gaokao-cli majors status --json`
|
||||
- `python scripts/gaokao-cli majors lookup 经济学 --json`
|
||||
- `python scripts/gaokao-cli majors verify --json`
|
||||
- 已新增:
|
||||
- `schools/2026/10001.json` 校级招生目录骨架样本
|
||||
- 尚未落地:
|
||||
- 更多学校 `schools/<year>/` 批量目录
|
||||
- `changes/2024-2026.md`
|
||||
- `major_validation` 接入 `audit_engine`
|
||||
执行 Phase 2 三个 Batch 全部收口:
|
||||
|
||||
- 已落地 `data/majors_catalog/` 包与 national loader/CLI(Batch 1)
|
||||
- 已落地 `national/2024.json` + `national/latest.json`(13 个 curated subset, `coverage_mode=mvp_subset`)(Batch 1)
|
||||
- 已新增 `schools/2026/10001.json` 校级招生目录骨架样本(**当前仅 1 所样本**,未扩面)(Batch 2)
|
||||
- 已落地 `major_validation` 接入 `audit_engine`(Batch 3)
|
||||
- 已新增 CLI 子命令:
|
||||
- `gaokao-cli majors {status,lookup,verify,changes}`(Batch 1)
|
||||
- `gaokao-cli majors {school-status,school-verify --year}`(Batch 2)
|
||||
- `gaokao-cli audit run --province --plan --truth-root --catalog-root --json` 命中 `MAJORS.not_found` / `MAJORS.non_active`(Batch 3)
|
||||
- 提交链:`36ad58a` → `6b1157f` → `edc5b11`,三仓同步
|
||||
|
||||
### 2.2 尚未落地
|
||||
|
||||
- 更多学校 `schools/<year>/` 批量目录(仅 1 所样本)
|
||||
- `changes/2024-2026.md`
|
||||
- 国家层第二批扩面(>13 个专业)
|
||||
- 国家层 2026 版(当前是 2024 版快照)
|
||||
|
||||
---
|
||||
|
||||
@@ -84,6 +89,8 @@ SchoolMajorOffering:
|
||||
last_verified_at: datetime
|
||||
```
|
||||
|
||||
> **当前落地情况**:校级骨架 `schools/2026/10001.json` 1 所样本;其余待 Batch 4 扩面。
|
||||
|
||||
---
|
||||
|
||||
## 4. 接入策略
|
||||
@@ -93,52 +100,36 @@ SchoolMajorOffering:
|
||||
- **首选源**: 教育部 2024 年《普通高等学校本科专业目录》
|
||||
- **兜底源**: 阳光高考/学位中心
|
||||
- **抓取方式**: 不自动爬取,使用人工收录 + 摘录
|
||||
- **校验**: `python3 -m data.majors_catalog.cli verify national`
|
||||
- **校验**: `python3 scripts/gaokao-cli majors verify --json`
|
||||
|
||||
### 4.2 校级
|
||||
|
||||
- **首选源**: 各高校 2025/2026 招生章程
|
||||
- **覆盖范围**: 首批 5-10 所重点高校
|
||||
- **接入节奏**: 每所高校每年一次,5 月前完成
|
||||
- **校验**: `python3 -m data.majors_catalog.cli verify school <code>`
|
||||
- **校验**: `python3 scripts/gaokao-cli majors school-verify --year <year> --json`
|
||||
|
||||
---
|
||||
|
||||
## 5. 审计引擎集成
|
||||
|
||||
`audit_engine.run` 接收 plan,新增一步:
|
||||
`AuditEngine.audit_plan` 新增 `major_validation` 一步:
|
||||
|
||||
```python
|
||||
def _validate_majors(plan: VolunteerPlan) -> list[AuditIssue]:
|
||||
issues = []
|
||||
for item in plan.items:
|
||||
for major_name in item.major_names:
|
||||
major = majors_catalog.lookup(major_name)
|
||||
if major is None:
|
||||
issues.append(AuditIssue(
|
||||
rule_id="MAJORS.not_found",
|
||||
severity=RuleSeverity.WARNING,
|
||||
title=f"专业未在国家级目录中找到: {major_name}",
|
||||
suggestion="请人工核对",
|
||||
))
|
||||
elif major.status != "active":
|
||||
issues.append(AuditIssue(
|
||||
rule_id="MAJORS.deprecated",
|
||||
severity=RuleSeverity.CRITICAL,
|
||||
title=f"专业已撤销/合并/改名: {major.name}",
|
||||
evidence_quote=f"{major.year_removed} 年已 {major.status}",
|
||||
))
|
||||
return issues
|
||||
```
|
||||
- 命中 `MAJORS.not_found`(专业未在国家级目录中找到)
|
||||
- 命中 `MAJORS.non_active`(专业已撤销/合并/改名)
|
||||
|
||||
详细见 `tests/test_audit_engine_major_validation_phase2.py`。
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 3 必须收口
|
||||
## 6. 下一阶段(执行 Phase 2 收口后的 Batch 4 候选清单)
|
||||
|
||||
- `data/majors_catalog/national/2024.json` 落地,500+ 专业覆盖
|
||||
- 5+ 重点高校 2025/2026 招生目录落地
|
||||
- `gaokao-cli majors lookup/validate` 可用
|
||||
- 1+ 真实 e2e:大厂AI方案跑 audit run 命中 major 验证
|
||||
> 已在 Batch 1/2/3 中**完成**的事项不再列入"必须收口"清单。
|
||||
|
||||
- 国家层扩面(>13 个专业)— **Batch 4 候选**
|
||||
- 校级扩面(>1 所学校)— **Batch 4 候选**
|
||||
- `changes/2024-2026.md` — **Batch 4 候选**
|
||||
- 国家层 2026 版快照(当前为 2024 版)— **Batch 4 候选**
|
||||
|
||||
---
|
||||
|
||||
@@ -147,7 +138,4 @@ def _validate_majors(plan: VolunteerPlan) -> list[AuditIssue]:
|
||||
- 国家级目录与校级目录语义混淆
|
||||
- 教育部新设专业不进入旧目录(2024 之后新设)
|
||||
- 跨年招生章程口径不一致
|
||||
|
||||
---
|
||||
|
||||
**下一阶段**: Phase 3 实施,见 `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §11
|
||||
- 当前 13 个 curated subset 不应被误读为完整教育部目录
|
||||
|
||||
@@ -136,7 +136,8 @@
|
||||
|
||||
## 4. 详细整改任务板(历史执行快照,已不再作为当前状态真相)
|
||||
|
||||
> 说明:下方逐项卡片保留的是 2026-06-14 制定整改时的任务模板,很多条目仍显示 `状态: pending`。
|
||||
> ⚠️ **重要:以下卡片保留的是 2026-06-14 制定整改时的任务模板,绝大多数条目仍显示 `状态: pending`,但实际状态已在 §2.1 中归一为"已完成 / 已转入 T12 / 并入 T12"三类。本节仅作历史记录,不应再被引用为"当前未完成项"。**
|
||||
> 当前状态请以 `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` 与 `docs/CURRENT_STATE.md` §5-§6 为准。
|
||||
> 当前状态请以上一节“2026-06-15 状态归一”为准;不要再用这里的旧状态判断项目是否全部完成。
|
||||
|
||||
### P1-1 支付回调双写裂缝
|
||||
|
||||
@@ -1,122 +1,129 @@
|
||||
# RULES_SOURCE_OF_TRUTH
|
||||
|
||||
最后更新: 2026-06-16
|
||||
最后更新: 2026-06-17
|
||||
真相源: 本文件是"规则"维度的入口索引。
|
||||
审计上下文: `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md`
|
||||
设计上下文: `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §2
|
||||
执行上下文: `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.1
|
||||
|
||||
---
|
||||
|
||||
## 1. 范围
|
||||
|
||||
本文件收敛以下三类信息:
|
||||
本文件收敛以下四类信息:
|
||||
|
||||
- 各省高考 2026 规则
|
||||
- 全国通用规则
|
||||
- 错误模式库
|
||||
- 国家级规则
|
||||
- 省级规则
|
||||
- 规则证据链
|
||||
- 统一审计入口
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前真相源路径(Phase 1 之后)
|
||||
## 2. 当前真相源路径
|
||||
|
||||
| 类别 | 路径 | 格式 | 写入方式 |
|
||||
| ------------ | ----------------------------------- | --------- | ---------------------------------- |
|
||||
| 全国通用规则 | `rules/_truth/national.yaml` | YAML | 人工 PR + 自动校验 |
|
||||
| 省级规则 | `rules/_truth/province/<prov>.yaml` | YAML | 人工 PR + 自动校验 |
|
||||
| 规则证据原文 | `rules/_evidence/<prov>/<file>` | 抓取/摘录 | 人工收录 |
|
||||
| 错误模式库 | `rules/errors/ERRORS.md` | Markdown | 人工维护(短期保留) |
|
||||
| 跨省索引 | `rules/provinces.md` | Markdown | 由 `data/rules/loader.py` 自动生成 |
|
||||
| 类别 | 路径 | 格式 | 写入方式 |
|
||||
| ---------- | ------------------------------------------ | -------- | ------------------------------- |
|
||||
| 国家级规则 | `rules/_truth/national.yaml` | YAML | 人工摘录 + 官方文件校对 |
|
||||
| 省级规则 | `rules/_truth/province/<slug>.yaml` | YAML | 人工摘录 + 招生考试院校对 |
|
||||
| 规则证据层 | `rules/_evidence/<prov>/<year>-<topic>.md` | Markdown | 待补建(设计层 Phase 1.5 要求) |
|
||||
| 旧规则字典 | `scripts/gaokao-checker` `PROVINCE_RULES` | Python | 过渡期镜像,已迁移到 \_truth |
|
||||
| 错误码字典 | `rules/errors/ERRORS.md` | Markdown | 人工维护 |
|
||||
|
||||
### 2.1 当前已落地范围(2026-06-17)
|
||||
|
||||
- 已落地 `rules/_truth/national.yaml`(国家级)
|
||||
- 已落地 `rules/_truth/province/<slug>.yaml`(27 省,2026 版)
|
||||
- 已落地 `data/rules/loader.py` `RuleLoader.from_truth_root`
|
||||
- 已落地 `data/rules/audit_engine.py` `AuditEngine`
|
||||
- 已落地 `data/rules/cli.py` `rules {status,verify}`
|
||||
- 已落地 `scripts/migrate_province_rules_to_truth.py`(从 `PROVINCE_RULES` 一次性迁移)
|
||||
- 已落地 `tests/test_rules_truth_phase1.py` + `tests/test_rules_cli_phase1.py`(部分)
|
||||
|
||||
### 2.2 尚未落地
|
||||
|
||||
- `rules/_evidence/<prov>/<year>-<topic>.md`(证据层)— 设计层 Phase 1.5 要求;当前**未建**
|
||||
- 逐省证据链审计报告 — 本轮 docs-only 收口后,下一阶段再启动
|
||||
- 规则"最近验证时间 > 90 天"自动告警(设计层 §2.5)
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据模型最小契约
|
||||
## 3. 数据模型
|
||||
|
||||
### 3.1 Rule(`data/rules/models.py`)
|
||||
|
||||
```python
|
||||
Rule:
|
||||
rule_id: str # e.g. HUNAN.max_volunteers
|
||||
rule_id: str # e.g. "HUNAN.max_volunteers"
|
||||
scope: "national" | "province"
|
||||
province: str | None
|
||||
year: int
|
||||
province: str | None # 当 scope=province 时
|
||||
year: int # 2026
|
||||
title: str
|
||||
description: str
|
||||
severity: "fatal" | "critical" | "warning" | "info"
|
||||
value: dict # 机读字段,如 {"max_volunteers": 45}
|
||||
source_evidence_id: str # 指向 rules/_evidence/<id>.md
|
||||
value: dict # 规则可机读形式
|
||||
source_evidence_id: str # 证据链引用
|
||||
effective_date: date
|
||||
last_verified_at: datetime
|
||||
version: str
|
||||
version: str # "2026.1"
|
||||
status: "active" | "draft" | "deprecated"
|
||||
```
|
||||
|
||||
---
|
||||
### 3.2 AuditIssue(`data/rules/models.py`)
|
||||
|
||||
## 4. 当前覆盖矩阵(写入本文件时为准)
|
||||
|
||||
| 省份 | 模式 | 文档宣称 | checker 实际 | 备注 |
|
||||
| ------ | ---------- | --------------- | -------------- | ------------------------------ |
|
||||
| 湖南 | 院校专业组 | ✅ | ✅ | 详细规则见 province/hunan.yaml |
|
||||
| 广东 | 院校专业组 | ✅ | ✅ | |
|
||||
| 湖北 | 院校专业组 | ✅ | ✅ | |
|
||||
| 安徽 | 院校专业组 | ✅ | ✅ | |
|
||||
| 江西 | 院校专业组 | ✅ | ✅ | |
|
||||
| 甘肃 | 院校专业组 | ✅ | ✅ | |
|
||||
| 黑龙江 | 院校专业组 | ✅ | ✅ | |
|
||||
| 江苏 | 院校专业组 | ✅ | ✅ | |
|
||||
| 福建 | 院校专业组 | ✅ | ✅ | |
|
||||
| 广西 | 院校专业组 | ✅ | ✅ | |
|
||||
| 北京 | 院校专业组 | ✅ | ✅ | |
|
||||
| 上海 | 院校专业组 | ✅ | ✅ | |
|
||||
| 天津 | 院校专业组 | ✅ | ✅ | |
|
||||
| 海南 | 院校专业组 | ✅ | ✅ | |
|
||||
| 浙江 | 专业+学校 | ✅ | ✅ | |
|
||||
| 山东 | 专业+学校 | ✅ | ✅ | |
|
||||
| 河北 | 专业+学校 | ✅ | ✅ | |
|
||||
| 重庆 | 专业+学校 | ✅ | ✅ | |
|
||||
| 辽宁 | 专业+学校 | ✅ | ✅ | |
|
||||
| 贵州 | 专业+学校 | ✅ | ✅ | |
|
||||
| 青海 | 专业+学校 | ✅ | ✅ | |
|
||||
| 吉林 | 专业+学校 | ✅ | ✅ | |
|
||||
| 新疆 | 传统 | ✅ | ✅ | |
|
||||
| 西藏 | 传统 | ✅ | ✅ | |
|
||||
| 河南 | 传统 | ✅ | ✅ | |
|
||||
| 四川 | 传统 | ✅ | ✅ | |
|
||||
| 云南 | 传统 | ✅ | ✅ | |
|
||||
| 山西 | — | ❌(SKILL.md 漏) | ❌(checker 无) | SKILL.md frontmatter 漂移待修 |
|
||||
| 陕西 | — | ❌(SKILL.md 含) | ❌(checker 无) | SKILL.md / checker 漂移待修 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 文档漂移清单(已识别)
|
||||
|
||||
- D1: `rules/provinces.md` 与 `scripts/gaokao-checker` 口径一致为 27 省
|
||||
- D2: `skills/gaokao-spec-checker/SKILL.md` frontmatter 与实际 checker 省份集仍有漂移
|
||||
- D3: 传统模式数 provinces.md 写 5,SKILL.md 写 6
|
||||
- D4: 文档与代码都没有 source_evidence_id 链
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 1 必须收口
|
||||
|
||||
- 27 省全部有 `province/<prov>.yaml`
|
||||
- 至少 1 条全国通用规则有 `national.yaml`
|
||||
- 1-2 个规则有真实 `source_evidence_id` 链路(湖南优先)
|
||||
- `gaokao-cli rules status` 可用
|
||||
|
||||
---
|
||||
|
||||
## 7. 验证脚本
|
||||
|
||||
```bash
|
||||
# 规则台账
|
||||
gaokao-cli rules status
|
||||
|
||||
# 单条规则解释
|
||||
gaokao-cli rules explain --province 湖南 --rule-id HUNAN.max_volunteers
|
||||
|
||||
# 一致性自检
|
||||
python3 -m data.rules.cli verify
|
||||
```python
|
||||
AuditIssue:
|
||||
rule_id: str
|
||||
severity: RuleSeverity
|
||||
title: str
|
||||
message: str
|
||||
evidence_quote: str | None
|
||||
suggestion: str | None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**下一阶段**: Phase 1 实施,见 `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §11
|
||||
## 4. 接入策略
|
||||
|
||||
### 4.1 国家级
|
||||
|
||||
- **首选源**: 教育部 / 教育考试院公开规则文件
|
||||
- **覆盖范围**: 平行志愿、一次投档、退档机制、体检要求等
|
||||
- **接入节奏**: 年度全量 + 差异更新
|
||||
|
||||
### 4.2 省级
|
||||
|
||||
- **首选源**: 各省招生考试院/教育考试院 2026 招生工作通知
|
||||
- **覆盖范围**: 27 省(与 `PROVINCE_RULES` 对齐)
|
||||
- **接入节奏**: 月度差异更新
|
||||
|
||||
### 4.3 证据层
|
||||
|
||||
- **首选源**: 上述官方文件原文
|
||||
- **存储格式**: Markdown 摘录 + 官方文件 URL
|
||||
- **校验**: 每条规则必须能引用至少一个证据条目
|
||||
|
||||
---
|
||||
|
||||
## 5. 统一审计入口
|
||||
|
||||
- `gaokao-cli rules status --json` — 27 省 + national 状态摘要
|
||||
- `gaokao-cli rules verify --json` — `national.yaml` + `province/` 完整性
|
||||
- `gaokao-cli audit run --province <p> --plan <json> --truth-root <path> --catalog-root <path> --json` — 完整审计
|
||||
|
||||
---
|
||||
|
||||
## 6. 下一阶段(执行 Phase 1 收口后的 Batch 候选清单)
|
||||
|
||||
> 已在 Phase 1 / 1.5 中**完成**的事项不再列入"必须收口"清单。
|
||||
|
||||
- `rules/_evidence/<prov>/<year>-<topic>.md` 证据层补建 — **Batch 4 候选**
|
||||
- 逐省证据链审计报告 — **Batch 4 候选**
|
||||
- `gaokao-cli rules explain <rule_id>` 命令 — **Batch 4 候选**
|
||||
- "最近验证时间 > 90 天"自动告警 — **Batch 4 候选**
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险
|
||||
|
||||
- 证据层未建导致规则可信度依赖 yaml 自描述
|
||||
- 旧 `PROVINCE_RULES` 字典与新 `_truth` 字典需要保持同步过渡
|
||||
- 月度刷新流程目前依赖人工 cron,未与 `gaokao-cli rules status` 联动
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Phase2 专业目录真相源 Implementation Plan
|
||||
|
||||
> **状态**:**已收口(历史快照)**。三个 Batch 全部完成,三仓同步至 `edc5b11`。
|
||||
> 实际执行进度见 `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.2-§1.4 与 `docs/CURRENT_STATE.md` §0。
|
||||
> 本文件保留作为 2026-06-17 制定计划时的原始目标范围,不再作为当前待办清单。
|
||||
|
||||
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task when tasks become independent enough; for the first batch, controller can implement directly because the repo currently has zero `data/majors*` code and the architecture needs one coherent landing.
|
||||
|
||||
**Goal:** 为 gaokao-volunteer-system 落地“2026 官方专业目录”最小可用真相源,使系统首次具备国家级专业目录的结构化读取、查询、校验与 CLI 能力,并为后续 school offering / audit integration 铺路。
|
||||
|
||||
87
tests/test_cli_doctor_phase3.py
Normal file
87
tests/test_cli_doctor_phase3.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
CLI = PROJECT_ROOT / "scripts" / "gaokao-cli"
|
||||
|
||||
|
||||
def _run_cli(*args: str) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
[sys.executable, str(CLI), *args],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=PROJECT_ROOT,
|
||||
)
|
||||
|
||||
|
||||
def test_cli_help_lists_order_and_doctor() -> None:
|
||||
result = _run_cli("--help")
|
||||
assert result.returncode == 0
|
||||
assert "order" in result.stdout
|
||||
assert "doctor" in result.stdout
|
||||
assert "rules" in result.stdout
|
||||
assert "majors" in result.stdout
|
||||
assert "audit" in result.stdout
|
||||
|
||||
|
||||
def test_cli_order_delegates_list_subcommand() -> None:
|
||||
# The orders CLI exposes a `list` subcommand. Asking for its --help
|
||||
# proves the `gaokao-cli order ...` argv successfully reaches the
|
||||
# orders parser instead of being swallowed by the top-level parser.
|
||||
result = _run_cli("order", "list", "--help")
|
||||
assert result.returncode == 0, result.stderr
|
||||
# The orders parser's list subcommand should expose --status/--source/--limit;
|
||||
# presence proves the delegation wired up correctly.
|
||||
assert "--status" in result.stdout
|
||||
assert "--limit" in result.stdout
|
||||
|
||||
|
||||
def test_cli_doctor_returns_ok_json(tmp_path: Path) -> None:
|
||||
result = _run_cli("doctor", "--json")
|
||||
assert result.returncode == 0
|
||||
payload = json.loads(result.stdout)
|
||||
assert payload["ok"] is True
|
||||
assert payload["rules"]["province_count"] >= 27
|
||||
assert payload["rules"]["national_rule_count"] >= 0
|
||||
assert payload["majors"]["ok"] is True
|
||||
assert payload["majors_verify"]["ok"] is True
|
||||
|
||||
|
||||
def test_cli_doctor_fails_on_empty_truth_root(tmp_path: Path) -> None:
|
||||
empty_truth = tmp_path / "truth"
|
||||
empty_truth.mkdir()
|
||||
empty_catalog = tmp_path / "catalog"
|
||||
empty_catalog.mkdir()
|
||||
(empty_catalog / "national").mkdir()
|
||||
(empty_catalog / "schools").mkdir()
|
||||
(empty_catalog / "national" / "2024.json").write_text(
|
||||
json.dumps({"year": 2024, "majors": []}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
(empty_catalog / "national" / "latest.json").write_text(
|
||||
json.dumps({"year": 2024, "majors": []}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = _run_cli(
|
||||
"doctor",
|
||||
"--truth-root",
|
||||
str(empty_truth),
|
||||
"--catalog-root",
|
||||
str(empty_catalog),
|
||||
"--json",
|
||||
)
|
||||
assert result.returncode == 1
|
||||
payload = json.loads(result.stdout)
|
||||
assert payload["ok"] is False
|
||||
assert payload["rules"].get("province_count", 0) == 0
|
||||
# When the truth_root is incomplete the loader raises before any
|
||||
# province_count key is populated, so a structured error key replaces
|
||||
# the missing province_count.
|
||||
assert "error" in payload["rules"] or payload["rules"].get("province_count", 0) == 0
|
||||
Reference in New Issue
Block a user