From eac956a896d8558359779bc7a6236bdee745a1ba Mon Sep 17 00:00:00 2001 From: Hermes Date: Sat, 20 Jun 2026 00:27:14 +0800 Subject: [PATCH] docs(review): 6/19 production strict review baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reports/PRODUCTION_STRICT_REVIEW_2026-06-19.md: 当前真相版 review - reports/STRICT_COMPREHENSIVE_REVIEW_2026-06-18.md: 昨日 review (历史快照) - docs/plans/2026-06-19-production-readiness-remediation-plan.md: 6/19 整改计划 - docs/plans/2026-06-18-strict-review-remediation-plan.md: 昨日整改计划 --- ...26-06-18-strict-review-remediation-plan.md | 1178 +++++++++++++++++ ...9-production-readiness-remediation-plan.md | 205 +++ .../PRODUCTION_STRICT_REVIEW_2026-06-19.md | 269 ++++ .../STRICT_COMPREHENSIVE_REVIEW_2026-06-18.md | 962 ++++++++++++++ 4 files changed, 2614 insertions(+) create mode 100644 docs/plans/2026-06-18-strict-review-remediation-plan.md create mode 100644 docs/plans/2026-06-19-production-readiness-remediation-plan.md create mode 100644 reports/PRODUCTION_STRICT_REVIEW_2026-06-19.md create mode 100644 reports/STRICT_COMPREHENSIVE_REVIEW_2026-06-18.md diff --git a/docs/plans/2026-06-18-strict-review-remediation-plan.md b/docs/plans/2026-06-18-strict-review-remediation-plan.md new file mode 100644 index 0000000..a2f6f2e --- /dev/null +++ b/docs/plans/2026-06-18-strict-review-remediation-plan.md @@ -0,0 +1,1178 @@ +# Strict Review Remediation Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** 按 2026-06-18 严格 review 报告收口 P0/P1/P2 整改项,先修会造成错误对外承诺、错误安全边界、错误恢复结论和错误验证结论的问题,再补运行时契约、数据治理和文档收口。 + +**Architecture:** 继续沿用现有 Python/FastAPI/SQLite 单仓架构,不引入新基础设施。先修底层不变量:恢复链真实性、权限边界、路径信任边界、token 传播边界、验证链真实性;再修跨表 retention、运行时契约、同意记录与数据治理;最后补文档、前端 sink 和部署证明。所有任务按 TDD 和最小改动推进,每个任务必须有独立回归测试与独立提交。 + +**Tech Stack:** Python 3.11/.venv, FastAPI, sqlite3, pytest, bash scripts, Markdown docs, GitHub Actions + +--- + +## Phase 0: Documentation Discovery and Contract Freeze + +### Task 0: 固化整改真相源与禁止回归的契约 + +**Files:** +- Read: `reports/STRICT_COMPREHENSIVE_REVIEW_2026-06-18.md` +- Read: `docs/CURRENT_STATE.md` +- Read: `docs/TECH_ARCHITECTURE.md` +- Read: `docs/DATA_RETENTION_AND_DELETION.md` +- Read: `docs/LEGAL_PRIVACY_BASELINE.md` +- Read: `docs/DELIVERY_RETENTION_OPS_RUNBOOK.md` +- Read: `docs/BACKUP_AND_RECOVERY_PLAN.md` +- Read: `docs/plans/2026-06-17-comprehensive-review-remediation.md` +- Create: `docs/plans/2026-06-18-strict-review-remediation-plan.md` + +**Step 1: 提炼唯一真相源** + +把本次整改的输入固定为: + +- 状态真相:`docs/CURRENT_STATE.md` +- 问题真相:`reports/STRICT_COMPREHENSIVE_REVIEW_2026-06-18.md` +- 运行时门禁:`admin/config.py`, `scripts/dev-verify.sh`, `.github/workflows/ci.yml` +- 数据治理基线:`docs/DATA_RETENTION_AND_DELETION.md`, `docs/LEGAL_PRIVACY_BASELINE.md` + +**Step 2: 写计划前的禁止事项** + +实施阶段必须遵守: + +- 不新增第二套 auth/RBAC 机制;沿用 `admin/auth.py` 与路由依赖模式。 +- 不把 `audit_report` / `pdf_path` 继续当任意自由文本路径。 +- 不再增加新的 query-token 能力 URL。 +- 不用“补文档解释”替代代码修复。 +- 不用“跳过 manifest”或“复制 live db”伪造恢复成功。 + +**Step 3: 计划作者自检** + +确认每个后续任务都满足: + +- 精确文件路径 +- 先写失败测试 +- 明确验证命令 +- 明确不该做什么 + +**Verification checklist:** +- 重新打开 `reports/STRICT_COMPREHENSIVE_REVIEW_2026-06-18.md`,确认后续任务覆盖 P0 和前六个 P1。 + +**Anti-pattern guards:** +- 不发散到“重写系统” +- 不把 P2 任务插队到 P0/P1 前面 + +--- + +## Phase 1: P0 Blocking Fixes + +### Task 1: 修复 `backup_verify.sh` 的 live SQLite staging,不再对 WAL 数据库做裸复制 + +**Files:** +- Modify: `scripts/backup_verify.sh` +- Modify: `docs/BACKUP_AND_RECOVERY_PLAN.md` +- Modify: `docs/DELIVERY_RETENTION_OPS_RUNBOOK.md` +- Test: `tests/test_backup_workflow.py` +- Test: `tests/test_backup_restore_service_level.py` + +**Step 1: 先写失败测试** + +在 `tests/test_backup_workflow.py` 增加一个针对 live WAL 库的回归测试。测试形状: + +```python +def test_backup_verify_live_copy_handles_wal_sqlite(tmp_path): + # 1. 建 WAL sqlite + # 2. 写入数据但不手动 checkpoint + # 3. 调 backup_verify.sh 的 live staging 路径 + # 4. 断言 staged 库仍能读到原表和原数据 +``` + +在 `tests/test_backup_restore_service_level.py` 增加一个约束: + +```python +def test_backup_verify_live_mode_uses_sqlite_backup_for_db_files(...): + # 断言 live verify 后的恢复副本不是空库/缺表 +``` + +**Step 2: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_backup_workflow.py tests/test_backup_restore_service_level.py +``` + +Expected: + +- FAIL,原因是当前 `backup_verify.sh` live 路径使用 `cp`,WAL 数据无法可靠复制。 + +**Step 3: 最小实现** + +在 `scripts/backup_verify.sh`: + +- 复用 `backup_snapshot.sh` 的 Python `sqlite3.backup()` 方案,不再裸复制 `.db` +- 明确处理 `-wal` / `-shm` 场景 +- 把 live staging 与 `--from-backup` staging 逻辑拆开命名,避免混淆 + +可接受的 shell/Python 片段方向: + +```bash +python3 - <<'PY' +import sqlite3 +src = sqlite3.connect(source) +dst = sqlite3.connect(target) +src.backup(dst) +dst.close() +src.close() +PY +``` + +**Step 4: 收口文档** + +更新 `docs/BACKUP_AND_RECOVERY_PLAN.md` 和 `docs/DELIVERY_RETENTION_OPS_RUNBOOK.md`: + +- 明确 live verify 现在如何做 SQLite staging +- 明确 `live-smoke` 与 `snapshot-verify` 的差异 + +**Step 5: 重新运行测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_backup_workflow.py tests/test_backup_restore_service_level.py +``` + +Expected: + +- PASS + +**Step 6: Commit** + +```bash +git add scripts/backup_verify.sh docs/BACKUP_AND_RECOVERY_PLAN.md docs/DELIVERY_RETENTION_OPS_RUNBOOK.md tests/test_backup_workflow.py tests/test_backup_restore_service_level.py +git commit -m "fix: make backup verify safe for wal sqlite" +``` + +**Anti-pattern guards:** +- 不允许继续用 `cp` 复制 live sqlite +- 不允许只改文档不改脚本 + +--- + +### Task 2: 重做 coverage gate,让 CI 指标只代表应用代码 + +**Files:** +- Modify: `scripts/dev-verify.sh` +- Modify: `scripts/check_coverage_gate.py` +- Modify: `codecov.yml` +- Test: `tests/test_dev_verify_entrypoint.py` +- Create or Modify: `tests/test_coverage_gate_rules.py` + +**Step 1: 写失败测试** + +在 `tests/test_dev_verify_entrypoint.py` 或新建 `tests/test_coverage_gate_rules.py` 增加两个断言: + +```python +def test_dev_verify_excludes_test_packages_from_coverage_args(): + script = Path("scripts/dev-verify.sh").read_text(encoding="utf-8") + assert "--cov=tests" not in script + assert "--cov=admin/tests" not in script + + +def test_coverage_gate_ignores_test_files_in_ratio(): + # 给 check_coverage_gate.py 喂一个包含 tests 和非 tests 的 coverage xml fixture + # 断言只按非测试代码统计 gate +``` + +**Step 2: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_dev_verify_entrypoint.py tests/test_coverage_gate_rules.py +``` + +Expected: + +- FAIL,当前脚本与 gate 逻辑仍把测试代码计入 overall。 + +**Step 3: 最小实现** + +- 在 `scripts/dev-verify.sh` 中把覆盖目标改成只针对应用代码目录,例如: + - `--cov=admin` + - `--cov=data` + - 如有脚本要纳管,再单独显式列出 +- 在 `scripts/check_coverage_gate.py` 中忽略: + - `tests/` + - `admin/tests/` + - 文档型 fixture / 计划文件 +- 在 `codecov.yml` 中同步排除同类路径 + +**Step 4: 重新跑测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_dev_verify_entrypoint.py tests/test_coverage_gate_rules.py +``` + +Expected: + +- PASS + +**Step 5: 追加一次真实门禁 smoke** + +Run: + +```bash +GAOKAO_SKIP_INSTALL=1 bash scripts/dev-verify.sh +``` + +Expected: + +- PASS +- coverage 计算口径不再把测试代码当主体 + +**Step 6: Commit** + +```bash +git add scripts/dev-verify.sh scripts/check_coverage_gate.py codecov.yml tests/test_dev_verify_entrypoint.py tests/test_coverage_gate_rules.py +git commit -m "fix: make coverage gate reflect application code only" +``` + +**Anti-pattern guards:** +- 不允许靠降低阈值“修复”问题 +- 不允许直接移除 coverage gate + +--- + +### Task 3: 收口产品/架构文档,把 Current 与 Target 明确拆开 + +**Files:** +- Modify: `docs/CURRENT_STATE.md` +- Modify: `README.md` +- Modify: `product/PRD.md` +- Modify: `product/ROADMAP.md` +- Modify: `product/MARKET_RESEARCH.md` +- Modify: `docs/TECH_ARCHITECTURE.md` +- Modify: `docs/API.md` +- Test: `tests/test_design_docs_substantive.py` + +**Step 1: 写失败测试** + +在 `tests/test_design_docs_substantive.py` 增加: + +```python +def test_prd_and_architecture_do_not_present_web_saas_as_current_state(): + prd = (REPO_ROOT / "product" / "PRD.md").read_text(encoding="utf-8") + arch = (DOCS_DIR / "TECH_ARCHITECTURE.md").read_text(encoding="utf-8") + assert "当前完整 Web 自助 SaaS" not in prd + assert "不存在的路径" not in arch # 用更精确的字符串断言真实条目 +``` + +再补一个路径存在性测试: + +```python +def test_tech_architecture_only_references_existing_current_paths(): + # 从文档中抽取被标为 current 的关键路径并断言仓库存在 +``` + +**Step 2: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_design_docs_substantive.py +``` + +Expected: + +- FAIL,原因是 PRD / TECH_ARCHITECTURE 仍混写目标态或引用不存在对象。 + +**Step 3: 最小实现** + +按以下原则改文档: + +- `README.md` 与 `CURRENT_STATE.md` 保持同一定位用语 +- `product/PRD.md` 中 Web 自助流程统一改成“目标态 / 试点态 / 本地 MVP” +- `product/ROADMAP.md` 把“已本地验证 / 待线上验收 / 目标态里程碑”拆层 +- `docs/TECH_ARCHITECTURE.md` 改成: + - Current + - In Progress + - Target +- 删除 `API.md` / `TECH_ARCHITECTURE.md` 中不存在的路径、接口、CLI、测试名 + +**Step 4: 重新运行测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_design_docs_substantive.py +``` + +Expected: + +- PASS + +**Step 5: Commit** + +```bash +git add docs/CURRENT_STATE.md README.md product/PRD.md product/ROADMAP.md product/MARKET_RESEARCH.md docs/TECH_ARCHITECTURE.md docs/API.md tests/test_design_docs_substantive.py +git commit -m "docs: align current state and target architecture claims" +``` + +**Anti-pattern guards:** +- 不允许保留“假 current”路径 +- 不允许仅加免责声明但正文继续写成已上线能力 + +--- + +### Task 4: 阻断 `audit_report/pdf_path/plan_file` 任意路径信任链 + +**Files:** +- Modify: `admin/routes/orders.py` +- Modify: `admin/routes/web_public.py` +- Modify: `data/orders/dao.py` or `data/orders/models.py`(仅在现有模型需要约束时) +- Test: `admin/tests/test_routes_orders.py` +- Test: `admin/tests/test_web_public.py` + +**Step 1: 写失败测试** + +在 `admin/tests/test_routes_orders.py` 增加: + +```python +def test_patch_order_rejects_audit_report_outside_allowed_report_dir(...): + resp = client.patch( + "/api/orders/GKO-TEST-1", + headers=auth_headers, + json={"updates": {"audit_report": "/etc/hosts"}}, + ) + assert resp.status_code == 422 +``` + +再加一条: + +```python +def test_patch_order_rejects_pdf_path_outside_allowed_report_dir(...): + ... +``` + +在 `admin/tests/test_web_public.py` 增加: + +```python +def test_portal_report_rejects_untrusted_report_path(...): + # 即使订单里已有脏路径,也不返回本地文件内容 +``` + +**Step 2: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_routes_orders.py admin/tests/test_web_public.py +``` + +Expected: + +- FAIL,当前 `/etc/hosts` 仍可被接受或读出。 + +**Step 3: 最小实现** + +实现原则: + +- `admin/routes/orders.py` 中 `_normalize_updates()` 或后续更新流程对 `audit_report` / `pdf_path` / `plan_file` 做受控校验 +- 只允许这些字段指向: + - `settings.share_report_dir` + - 或现有明确的报告输出目录 +- 对路径做: + - `resolve()` + - 基目录包含校验 + - 合法后缀校验(如 `.html`, `.json`, `.pdf`, `.md`,按当前真实报告产物收紧) +- `admin/routes/web_public.py` 中 portal 报告读取前再做一次防御性校验,不能只依赖后台写入端 + +**Step 4: 重新运行测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_routes_orders.py admin/tests/test_web_public.py +``` + +Expected: + +- PASS + +**Step 5: Commit** + +```bash +git add admin/routes/orders.py admin/routes/web_public.py admin/tests/test_routes_orders.py admin/tests/test_web_public.py +git commit -m "fix: restrict portal report paths to trusted directories" +``` + +**Anti-pattern guards:** +- 不允许用“只隐藏按钮”代替服务端校验 +- 不允许继续让 portal 直接信任任意订单路径字段 + +--- + +## Phase 2: P1 Security and Data-Governance Fixes + +### Task 5: 为后台订单能力补真正的角色授权 + +**Files:** +- Modify: `admin/auth.py` +- Modify: `admin/routes/orders.py` +- Modify: `admin/routes/notifications.py` +- Modify: `admin/routes/users.py`(如果列表/详情也需同策略) +- Test: `admin/tests/test_auth.py` +- Test: `admin/tests/test_routes_orders.py` +- Test: `admin/tests/test_notification_audit_page.py` + +**Step 1: 写失败测试** + +在 `admin/tests/test_routes_orders.py` 增加: + +```python +def test_viewer_cannot_list_orders(...): + resp = client.get("/api/orders", headers=viewer_headers) + assert resp.status_code == 403 + + +def test_viewer_cannot_patch_order(...): + resp = client.patch(...) + assert resp.status_code == 403 +``` + +在 `admin/tests/test_auth.py` 增加: + +```python +def test_require_role_rejects_non_admin_user(...): + ... +``` + +**Step 2: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_auth.py admin/tests/test_routes_orders.py admin/tests/test_notification_audit_page.py +``` + +Expected: + +- FAIL,当前 viewer 账号仍能访问这些接口。 + +**Step 3: 最小实现** + +在 `admin/auth.py` 增加统一依赖: + +```python +def require_role(*allowed_roles: str): + def _dep(user: AdminUser = Depends(get_current_user)) -> AdminUser: + if user.role not in allowed_roles: + raise BusinessError(...) + return user + return _dep +``` + +然后把高权限路由从: + +```python +_: AdminUser = Depends(get_current_user) +``` + +改成: + +```python +_: AdminUser = Depends(require_role("admin")) +``` + +至少覆盖: + +- `/api/orders*` +- `/api/admin/orders*` +- 通知审计 / ops alert 审计 +- 任何会暴露内部路径或跨用户数据的后台接口 + +**Step 4: 重新跑测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_auth.py admin/tests/test_routes_orders.py admin/tests/test_notification_audit_page.py +``` + +Expected: + +- PASS + +**Step 5: Commit** + +```bash +git add admin/auth.py admin/routes/orders.py admin/routes/notifications.py admin/routes/users.py admin/tests/test_auth.py admin/tests/test_routes_orders.py admin/tests/test_notification_audit_page.py +git commit -m "fix: enforce admin role on backend order and audit routes" +``` + +**Anti-pattern guards:** +- 不允许只在前端隐藏入口 +- 不允许继续让 `role` 字段存在但不参与授权 + +--- + +### Task 6: 收紧 Portal token 传播边界,不再用 query token 贯穿支付链 + +**Files:** +- Modify: `data/customer_portal/token.py` +- Modify: `data/payments/service.py` +- Modify: `data/payments/dao.py` +- Modify: `data/payments/models.py` +- Modify: `data/payments/providers/mock_gateway.py` +- Modify: `data/payments/providers/alipay_sim.py` +- Modify: `data/payments/providers/alipay.py` +- Modify: `admin/routes/web_public.py` +- Test: `admin/tests/test_web_public.py` +- Test: `data/payments/tests/test_provider_alipay.py` +- Test: `data/payments/tests/test_service.py` + +**Step 1: 先决定 contract** + +最保守方案: + +- Portal token 继续存在,但不再放进第三方回跳 URL 和公开 checkout query +- 支付回跳改成一次性 payment-bound token 或 server-side lookup key +- `payments.checkout_token` 改为: + - 不持久化,或 + - 持久化哈希值而非明文 + +**Step 2: 写失败测试** + +```python +def test_checkout_url_does_not_expose_portal_token_in_query(...): + assert "token=" not in checkout.checkout_url + + +def test_alipay_return_url_does_not_embed_portal_token(...): + assert "token=" not in checkout_url +``` + +如保留数据库字段但改哈希: + +```python +def test_payment_does_not_persist_plain_portal_token(...): + assert payment.checkout_token != raw_portal_token +``` + +**Step 3: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_web_public.py data/payments/tests/test_provider_alipay.py data/payments/tests/test_service.py +``` + +Expected: + +- FAIL,当前 provider / service 仍在 URL 和表中传播明文 token。 + +**Step 4: 最小实现** + +优先选 boring 方案: + +- 支付回跳带 `payment_id` + provider state,不带 portal token +- 服务端根据 `payment_id -> order_id` 找回订单,再单独发起安全跳转 +- 若必须有回跳能力标识,则使用短期一次性 nonce,校验后立即失效 + +**Step 5: 重新跑测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_web_public.py data/payments/tests/test_provider_alipay.py data/payments/tests/test_service.py +``` + +Expected: + +- PASS + +**Step 6: Commit** + +```bash +git add data/customer_portal/token.py data/payments/service.py data/payments/dao.py data/payments/models.py data/payments/providers/mock_gateway.py data/payments/providers/alipay_sim.py data/payments/providers/alipay.py admin/routes/web_public.py admin/tests/test_web_public.py data/payments/tests/test_provider_alipay.py data/payments/tests/test_service.py +git commit -m "fix: stop exposing portal tokens across payment URLs and storage" +``` + +**Anti-pattern guards:** +- 不允许把旧 portal token 换个 query 参数名继续用 +- 不允许“只是缩短 token”但继续明文传播 + +--- + +### Task 7: 补齐 retention / anonymize 的跨表治理 + +**Files:** +- Modify: `data/orders/deletion_service.py` +- Modify: `data/orders/retention_cleanup.py` +- Modify: `admin/routes/web_public.py` +- Modify: `admin/routes/notifications.py` +- Modify: `data/share/short_link.py` +- Modify: `docs/DATA_RETENTION_AND_DELETION.md` +- Modify: `docs/DELIVERY_RETENTION_OPS_RUNBOOK.md` +- Test: `admin/tests/test_order_deletion.py` +- Test: `tests/test_delivery_dispatcher.py` +- Test: `admin/tests/test_order_info_form.py` + +**Step 1: 写失败测试** + +补三类测试: + +```python +def test_anonymize_order_scrubs_delivery_notifications_payload(...): + ... + + +def test_retention_cleanup_scrubs_notification_payload_for_expired_orders(...): + ... + + +def test_deletion_request_log_is_purged_or_rotated_by_retention_policy(...): + ... +``` + +如果分享遥测要纳管,再补: + +```python +def test_share_access_events_have_retention_cleanup(...): + ... +``` + +**Step 2: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_order_deletion.py tests/test_delivery_dispatcher.py admin/tests/test_order_info_form.py +``` + +Expected: + +- FAIL,当前通知 payload / 删除申请日志 / 分享遥测未被完整纳管。 + +**Step 3: 最小实现** + +- `OrderDeletionService.anonymize_order()` 同步清理或脱敏: + - `delivery_notifications.payload_json` +- 给 `deletion-requests.jsonl` 制定 retention: + - 要么纳入清理脚本 + - 要么改成入库并统一治理 +- 给分享访问遥测增加最小 retention 清理入口 + +**Step 4: 更新文档** + +在 `docs/DATA_RETENTION_AND_DELETION.md` 和 runbook 中明确: + +- 哪些旁路数据集在 retention 范围内 +- 匿名化 vs 物理删除分别影响哪些表/文件 + +**Step 5: 重新跑测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_order_deletion.py tests/test_delivery_dispatcher.py admin/tests/test_order_info_form.py +``` + +Expected: + +- PASS + +**Step 6: Commit** + +```bash +git add data/orders/deletion_service.py data/orders/retention_cleanup.py admin/routes/web_public.py admin/routes/notifications.py data/share/short_link.py docs/DATA_RETENTION_AND_DELETION.md docs/DELIVERY_RETENTION_OPS_RUNBOOK.md admin/tests/test_order_deletion.py tests/test_delivery_dispatcher.py admin/tests/test_order_info_form.py +git commit -m "fix: align retention cleanup across notifications logs and share telemetry" +``` + +**Anti-pattern guards:** +- 不允许只清订单主表 +- 不允许继续让旁路 JSONL/遥测游离在治理范围外 + +--- + +### Task 8: 收紧后台审计暴露、同意记录和邮箱保护策略 + +**Files:** +- Modify: `admin/routes/notifications.py` +- Modify: `admin/routes/web_public.py` +- Modify: `data/orders/intake_store.py` +- Modify: `data/orders/models.py` +- Modify: `data/orders/schema.py`(如需迁移字段) +- Modify: `docs/LEGAL_PRIVACY_BASELINE.md` +- Test: `admin/tests/test_notification_audit_page.py` +- Test: `admin/tests/test_order_info_form.py` +- Test: `data/orders/tests/test_models.py` + +**Step 1: 拆成两个最小目标** + +A. 后台审计接口不再透传全量 `payload_json` / `details` +B. 同意记录与邮箱保护策略收口 + +**Step 2: 先写失败测试** + +```python +def test_notification_audit_api_masks_internal_paths_and_customer_email(...): + ... + + +def test_portal_intake_persists_consent_audit_fields(...): + assert payload["privacy_accepted_at"] + assert payload["service_terms_accepted_at"] + assert payload["consent_channel"] == "portal" + + +def test_customer_email_is_not_stored_as_plaintext(...): + assert row["customer_email"] != "parent@example.com" +``` + +如果暂时不准备对邮箱做加密,至少先落计划中的显式例外说明,并改测试锁死“必须有策略注释 / 明确豁免”。但更推荐直接与手机号策略统一。 + +**Step 3: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_notification_audit_page.py admin/tests/test_order_info_form.py data/orders/tests/test_models.py +``` + +Expected: + +- FAIL + +**Step 4: 最小实现** + +- `admin/routes/notifications.py` 返回 allowlist 字段,不直接回传完整 payload/details +- `web_public.py` 在 intake payload 中补: + - `privacy_accepted_at` + - `service_terms_accepted_at` + - `consent_channel` + - `consent_given_at` + - 如有后台代录,再补 `consent_operator` +- `customer_email`: + - 优先按手机号同类策略加密落盘 + - 如查询需要,增加 hash/索引辅助字段 + +**Step 5: 迁移与兼容** + +如果 schema 变更,补一个最小安全迁移: + +- 旧数据可读 +- 新写入按新策略走 +- 测试覆盖旧行缺字段场景 + +**Step 6: 重新跑测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_notification_audit_page.py admin/tests/test_order_info_form.py data/orders/tests/test_models.py +``` + +Expected: + +- PASS + +**Step 7: Commit** + +```bash +git add admin/routes/notifications.py admin/routes/web_public.py data/orders/intake_store.py data/orders/models.py data/orders/schema.py docs/LEGAL_PRIVACY_BASELINE.md admin/tests/test_notification_audit_page.py admin/tests/test_order_info_form.py data/orders/tests/test_models.py +git commit -m "fix: tighten audit exposure and consent data handling" +``` + +**Anti-pattern guards:** +- 不允许只在前端隐藏敏感字段 +- 不允许继续把“同意过”只记录成布尔值 + +--- + +### Task 9: 收口运行时契约、依赖锁定与 `dev-verify` 漂移检查 + +**Files:** +- Modify: `scripts/dev-verify.sh` +- Modify: `.github/workflows/ci.yml` +- Modify: `docker-compose.yml` +- Modify: `Dockerfile` +- Create: `requirements.lock.txt` or `constraints.txt`(按项目决定一种) +- Modify: `README.md` +- Modify: `INSTALL.md` +- Test: `tests/test_dev_verify_entrypoint.py` +- Test: `tests/test_docker_compose_env_contract.py` +- Create or Modify: `tests/test_runtime_contracts.py` + +**Step 1: 先定方案** + +只选一种,不要同时搞多套: + +- **锁定机制**:`constraints.txt` 或 `requirements.lock.txt` +- **compose 契约**: + - 要么真正支持 `GAOKAO_ADMIN_BIND/PORT` + - 要么删掉这些 env,避免假接线 +- **dev-verify 漂移检查**: + - 比较 `.venv/bin/python --version` 与 `PYTHON_BIN --version` + - 不一致则报错或要求显式重建 + +**Step 2: 写失败测试** + +```python +def test_dev_verify_detects_python_bin_drift(...): + ... + + +def test_compose_does_not_expose_unused_admin_bind_port_env(...): + ... + + +def test_ci_cache_key_tracks_all_runtime_requirement_inputs(...): + ... +``` + +**Step 3: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_dev_verify_entrypoint.py tests/test_docker_compose_env_contract.py tests/test_runtime_contracts.py +``` + +Expected: + +- FAIL + +**Step 4: 最小实现** + +- `scripts/dev-verify.sh` + - 加解释器漂移检测 + - 明确 `recreate_venv` 入口或错误提示 +- `.github/workflows/ci.yml` + - cache key 同时 hash `requirements-admin.txt` + `requirements-dev.txt` + lock/constraints +- `docker-compose.yml` / `Dockerfile` + - 修正假接线:要么真接,要么删掉 +- `README.md` / `INSTALL.md` + - 所有 `python3 ...` 改成先建/激活 `.venv` 的前置说明 + +**Step 5: 重新跑测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_dev_verify_entrypoint.py tests/test_docker_compose_env_contract.py tests/test_runtime_contracts.py +``` + +Expected: + +- PASS + +**Step 6: Commit** + +```bash +git add scripts/dev-verify.sh .github/workflows/ci.yml docker-compose.yml Dockerfile requirements.lock.txt constraints.txt README.md INSTALL.md tests/test_dev_verify_entrypoint.py tests/test_docker_compose_env_contract.py tests/test_runtime_contracts.py +git commit -m "build: tighten runtime contracts and dependency reproducibility" +``` + +**Anti-pattern guards:** +- 不允许新增第二套安装说明而保留旧模糊说法 +- 不允许继续让 compose env “看着可配实际上没用” + +--- + +### Task 10: 决定 `audit run` 与 `payment_failed` 的真实 contract + +**Files:** +- Modify: `data/rules/audit_engine.py` +- Modify: `data/rules/cli.py` +- Modify: `data/payments/service.py` +- Modify: `admin/routes/web_public.py` +- Modify: `docs/TECH_ARCHITECTURE.md` +- Modify: `docs/CURRENT_STATE.md` +- Test: `tests/test_audit_engine_major_validation_phase2.py` +- Create or Modify: `tests/test_audit_engine_contract.py` +- Create or Modify: `data/payments/tests/test_service.py` + +**Step 1: 选一个真实方向** + +- `audit run`: + - 方案 A:收窄文档与 CLI 文案,只承认当前检查面 + - 方案 B:补至少 1-2 个最关键缺失检查 +- `payment_failed`: + - 方案 A:真实落库 + portal 呈现 + - 方案 B:删掉 UI 分支,避免假状态机 + +优先推荐:**文案先收窄,状态机先收实或删死分支**。 + +**Step 2: 写失败测试** + +```python +def test_audit_run_output_declares_actual_checks_only(...): + ... + + +def test_failed_payment_state_is_either_persisted_or_not_rendered(...): + ... +``` + +**Step 3: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_audit_engine_major_validation_phase2.py tests/test_audit_engine_contract.py data/payments/tests/test_service.py +``` + +Expected: + +- FAIL + +**Step 4: 最小实现** + +- `audit run` 的输出与文档只描述真实执行内容 +- `payment_failed` 要么真实落状态,要么删掉 portal 分支和文案 + +**Step 5: 重新跑测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_audit_engine_major_validation_phase2.py tests/test_audit_engine_contract.py data/payments/tests/test_service.py +``` + +Expected: + +- PASS + +**Step 6: Commit** + +```bash +git add data/rules/audit_engine.py data/rules/cli.py data/payments/service.py admin/routes/web_public.py docs/TECH_ARCHITECTURE.md docs/CURRENT_STATE.md tests/test_audit_engine_major_validation_phase2.py tests/test_audit_engine_contract.py data/payments/tests/test_service.py +git commit -m "fix: align audit and payment failure contracts with real behavior" +``` + +**Anti-pattern guards:** +- 不允许继续让 CLI / UI 说一套、代码做一套 + +--- + +## Phase 3: P2 Cleanup, Deployment Proof, and Final Verification + +### Task 11: 收口 P2 前端/分享/健康检查边界 + +**Files:** +- Modify: `admin/routes/web_public.py` +- Modify: `admin/routes/health.py` +- Modify: `data/share/permission.py` +- Test: `admin/tests/test_web_public.py` +- Test: `data/share/tests/test_permission.py` +- Create or Modify: `admin/tests/test_health.py` + +**Step 1: 写失败测试** + +```python +def test_confirm_summary_does_not_use_innerhtml_for_user_fields(...): + ... + + +def test_health_endpoint_returns_minimal_readiness_only(...): + ... + + +def test_share_edit_scope_excludes_id_card_and_internal_paths(...): + ... +``` + +**Step 2: 跑测试确认失败** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_web_public.py data/share/tests/test_permission.py admin/tests/test_health.py +``` + +Expected: + +- FAIL + +**Step 3: 最小实现** + +- 去掉确认页 `innerHTML` 拼接用户输入 +- `/health` 只返回最小 readiness +- 收紧 `data/share/permission.py` 中 `edit/admin` allowlist,排除身份证号、联系方式、内部路径 + +**Step 4: 重新跑测试** + +Run: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_web_public.py data/share/tests/test_permission.py admin/tests/test_health.py +``` + +Expected: + +- PASS + +**Step 5: Commit** + +```bash +git add admin/routes/web_public.py admin/routes/health.py data/share/permission.py admin/tests/test_web_public.py data/share/tests/test_permission.py admin/tests/test_health.py +git commit -m "fix: reduce portal and share exposure surfaces" +``` + +--- + +### Task 12: 为容器内 PDF 生成和统一门禁补最终证明 + +**Files:** +- Modify: `Dockerfile` +- Modify: `.github/workflows/ci.yml` +- Modify: `README.md` +- Modify: `INSTALL.md` +- Create or Modify: `tests/test_pdf_runtime_smoke.py` + +**Step 1: 补容器或 CI 级 smoke** + +至少选一种: + +- Docker build 后在容器内运行最小 WeasyPrint PDF smoke +- 或 CI job 直接执行最小 PDF 生成 smoke,并断言输出文件存在 + +测试形状: + +```python +def test_weasyprint_pdf_smoke(tmp_path): + from weasyprint import HTML + out = tmp_path / "smoke.pdf" + HTML(string="

smoke

").write_pdf(out) + assert out.exists() + assert out.stat().st_size > 0 +``` + +**Step 2: 跑测试确认当前门禁不足** + +Run: + +```bash +./.venv/bin/python -m pytest -q tests/test_pdf_runtime_smoke.py +``` + +Expected: + +- 本地可能 PASS,但 CI / Dockerfile 尚未把这条变成交付证明 + +**Step 3: 最小实现** + +- 如 Dockerfile 缺系统库,则补安装 +- 在 CI 中增加 PDF smoke +- 在 README / INSTALL 中明确“哪些环境已验证可生成 PDF” + +**Step 4: 运行最终门禁** + +Run: + +```bash +./.venv/bin/python -m pytest -q \ + tests/test_backup_workflow.py \ + tests/test_backup_restore_service_level.py \ + tests/test_dev_verify_entrypoint.py \ + tests/test_design_docs_substantive.py \ + tests/test_audit_engine_major_validation_phase2.py \ + tests/test_audit_engine_contract.py \ + admin/tests/test_auth.py \ + admin/tests/test_routes_orders.py \ + admin/tests/test_notification_audit_page.py \ + admin/tests/test_web_public.py \ + admin/tests/test_order_deletion.py \ + admin/tests/test_order_info_form.py \ + data/orders/tests/test_models.py \ + data/payments/tests/test_service.py \ + data/payments/tests/test_provider_alipay.py \ + data/share/tests/test_permission.py \ + tests/test_runtime_contracts.py \ + tests/test_pdf_runtime_smoke.py -q +``` + +然后跑: + +```bash +GAOKAO_SKIP_INSTALL=1 bash scripts/dev-verify.sh +./.venv/bin/python -m data.rules.cli doctor --json +``` + +Expected: + +- 全部 PASS +- doctor 返回 `ok: true` + +**Step 5: Commit** + +```bash +git add Dockerfile .github/workflows/ci.yml README.md INSTALL.md tests/test_pdf_runtime_smoke.py +git commit -m "build: prove pdf runtime and strict remediation gates" +``` + +**Anti-pattern guards:** +- 不允许用“本机能跑”替代 Docker/CI 交付证明 + +--- + +## Final Verification Checklist + +1. `backup_verify.sh` live 路径不再裸复制 SQLite。 +2. coverage gate 不再把测试代码算进 overall。 +3. `PRD` / `ROADMAP` / `TECH_ARCHITECTURE` 不再把目标态写成现状。 +4. `audit_report/pdf_path/plan_file` 不可指向任意本地路径。 +5. 非 admin 后台账号无法访问订单后台写能力。 +6. Portal token 不再通过 query 参数贯穿支付链。 +7. retention 覆盖通知 payload、删除申请日志、分享遥测。 +8. 后台审计接口做字段级最小暴露。 +9. consent 记录含时间/渠道/操作者语义。 +10. `customer_email` 策略与 PII 保护策略一致。 +11. `dev-verify` 能发现 `.venv` 解释器漂移。 +12. compose 契约不存在假接线。 +13. `/health` 只返回最小 readiness。 +14. 分享 allowlist 不再包含高敏字段和内部路径。 +15. Docker/CI 对 PDF 生成能力有真实证明。 + +## Grep / Anti-pattern Sweep + +Run: + +```bash +python3 - <<'PY' +from pathlib import Path +bad = [ + "?token=", + "innerHTML =", + "--cov=.", + "Depends(get_current_user)", +] +for needle in bad: + print(f"== {needle} ==") + for path in Path('.').rglob('*.py'): + try: + text = path.read_text(encoding='utf-8') + except Exception: + continue + if needle in text: + print(path) +PY +``` + +人工确认: + +- `?token=` 不再出现在支付 URL 构造链 +- 高权限后台路由不再只挂 `Depends(get_current_user)` +- `innerHTML =` 不再用于拼接用户输入 +- `--cov=.` 不再作为统一 coverage 入口 + +--- + +Plan complete and saved to `docs/plans/2026-06-18-strict-review-remediation-plan.md`. Two execution options: + +**1. Subagent-Driven (this session)** - 我在当前会话按任务逐个执行、每步复核后再推进。 +**2. Parallel Session (separate)** - 开一个新会话,按这个计划批量执行并在阶段点回报。 + +**Which approach?** diff --git a/docs/plans/2026-06-19-production-readiness-remediation-plan.md b/docs/plans/2026-06-19-production-readiness-remediation-plan.md new file mode 100644 index 0000000..6ee2a7d --- /dev/null +++ b/docs/plans/2026-06-19-production-readiness-remediation-plan.md @@ -0,0 +1,205 @@ +# 2026-06-19 生产上线整改计划(基于当前真相) + +目标:在不扩范围、不重写系统的前提下,把当前仍有效的上线阻塞项收口到“可受控上线评审”的状态。 + +原则: + +1. 只修当前仍有效问题,不重开已收口旧问题。 +2. 先修会影响真相、合规、支付状态判断的项,再修文档与运行说明。 +3. 每项必须带测试或可重复验证证据。 + +--- + +## 批次 A:P0 真相与合规阻塞(必须优先) + +### A1. 为删除/匿名化补保留期门禁 + +**优先级**:P0 + +**目标**:保证处于支付审计期 / 争议期 / 法定保留期的订单不能被直接删除或匿名化。 + +**涉及文件**: + +- `data/orders/schema.py` +- `data/orders/models.py` +- `data/orders/deletion_service.py` +- `admin/routes/orders.py` +- `admin/tests/test_order_deletion.py` + +**实现要求**: + +1. 为订单增加保留期字段(如 `retention_until`)或等价策略字段。 +2. `delete_order()` / `anonymize_order()` 执行前检查: + - 未过保留期 → 明确拒绝 + - 已过保留期 → 允许执行 +3. 管理后台接口必须返回明确的业务错误,而不是静默失败。 +4. 增加至少两类回归测试: + - 保留期内删除被拒绝 + - 过保留期删除成功 + +**验收命令**: + +```bash +./.venv/bin/python -m pytest -q admin/tests/test_order_deletion.py +GAOKAO_SKIP_INSTALL=1 bash scripts/dev-verify.sh +``` + +--- + +### A2. 重写产品/架构文档中的 Current / In Progress / Target 边界 + +**优先级**:P0 + +**目标**:消除“已是完整 Web 自助产品 / audit run 已包含全部能力”的误导。 + +**涉及文件**: + +- `README.md` +- `product/PRD.md` +- `product/ROADMAP.md` +- `docs/TECH_ARCHITECTURE.md` +- `docs/API.md` + +**实现要求**: + +1. `README.md`:保留顶部真实口径,并让正文不再盖过该限制。 +2. `product/PRD.md`: + - 将 `Web系统` 明确标注为 `目标态/本地 MVP`,不得作为当前售卖渠道直接陈述 + - 定价矩阵中的 `闲鱼/Web`、`微信/Web` 改成分层表达 +3. `product/ROADMAP.md`: + - 把“AI方案审核 / 反扎堆 / 数据溯源”的完成状态拆成:已落地能力 vs 未接入 `audit run` 的能力 +4. `docs/TECH_ARCHITECTURE.md`: + - 保持 Current / In Progress / Target 明确分段 + - 删除或降级仍会被误读成“当前上线能力”的表述 +5. `docs/API.md`:如果仍有“综合评分/完整审核闭环”式表述,应改成当前真实能力范围。 + +**验收标准**: + +- 任意只读这几份文档的评审者,不应再得出“系统已是完整 Web 自助 SaaS”或“audit run 已做完反扎堆/综合评分”的结论。 + +--- + +## 批次 B:P1 业务状态与真相源同步 + +### B1. 为支付失败 webhook 持久化失败状态 + +**优先级**:P1 + +**目标**:让支付失败不只是一次 HTTP 错误,而是可审计的业务状态。 + +**涉及文件**: + +- `data/payments/service.py` +- `data/payments/dao.py`(如需要) +- 相关 payment tests + +**实现要求**: + +1. 当 provider webhook 返回非 success 状态时: + - 将 payment 记录持久化为 `failed`(或等价失败态) + - 保留 callback payload / failure reason +2. 增加失败 webhook 回归测试。 +3. 校准 portal / admin 对失败态的展示或处理语义。 + +**验收命令**: + +```bash +./.venv/bin/python -m pytest -q data/payments/tests admin/tests/test_payment_alipay_notify.py +GAOKAO_SKIP_INSTALL=1 bash scripts/dev-verify.sh +``` + +--- + +### B2. 更新唯一真相源文档 + +**优先级**:P1 + +**目标**:把 6/18-6/19 的真实变化纳入当前真相链,停止继续依赖旧快照。 + +**涉及文件**: + +- `docs/CURRENT_STATE.md` +- `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md`(或新建 2026-06-19 板) +- `docs/ACTIVE_REMEDIATION_2026-06-19.md`(建议新建) +- `docs/NAVIGATION.md` + +**实现要求**: + +1. `CURRENT_STATE.md` 更新为最新日期,吸收: + - 已收口项 + - 当前仍有效项 + - 当前上线判断边界 +2. 旧执行板若继续保留,必须标成历史快照;建议新增 6/19 当前执行板。 +3. 新 remediation 只保留当前有效问题: + - 文档真相漂移 + - 删除保留期门禁 + - 失败支付状态机 + - 运行说明残留 +4. `reports/PRODUCTION_STRICT_REVIEW_2026-06-19.md` 与本计划文档进入导航索引。 + +**验收标准**: + +- 新会话从 `CURRENT_STATE.md` 出发时,不会再把 6/18 前已修复问题当作当前阻塞。 + +--- + +## 批次 C:P2 收尾与降低复发率 + +### C1. 收紧 README 的运行前提说明 + +**优先级**:P2 + +**目标**:避免继续把系统 `python3` 直跑误读为正式支持路径。 + +**涉及文件**: + +- `README.md` +- 如有必要:`INSTALL.md` + +**实现要求**: + +1. 所有关键命令示例统一成 `.venv/bin/python` 或明确“需先激活 venv”。 +2. 若保留 `python3` 示例,必须标明这是“已装依赖前提下”的简写,而不是裸系统 Python 保证可跑。 + +--- + +### C2. 可选增强:checkout token 落库最小化 + +**优先级**:P2 + +**目标**:进一步降低支付关联 token 泄露面。 + +**涉及文件**: + +- `data/payments/service.py` +- `data/payments/dao.py` +- provider / payment tests + +**实现要求**: + +- 如成本可控,将 `checkout_token` 改成只存哈希,或明确该 token 的最小权限边界。 + +--- + +## 推荐执行顺序 + +1. **A1 删除保留期门禁** +2. **A2 文档 Current/Target 收口** +3. **B1 失败支付状态持久化** +4. **B2 真相源同步** +5. **C1 README/INSTALL 运行说明收口** +6. **C2 checkout token 最小化(可选)** + +--- + +## 最终验收门槛 + +完成本计划后,至少应满足: + +1. `GAOKAO_SKIP_INSTALL=1 bash scripts/dev-verify.sh` 持续通过 +2. 删除/匿名化存在明确保留期拒绝测试 +3. 失败支付存在明确持久化失败态测试 +4. `CURRENT_STATE.md` / 当前执行板 / remediation 已同步到 6/19 真相 +5. PRD / ROADMAP / README / TECH_ARCHITECTURE 不再把 Target 写成 Current + +在此之前,不得对外表述为“完整生产上线完成”。 diff --git a/reports/PRODUCTION_STRICT_REVIEW_2026-06-19.md b/reports/PRODUCTION_STRICT_REVIEW_2026-06-19.md new file mode 100644 index 0000000..dbc9c14 --- /dev/null +++ b/reports/PRODUCTION_STRICT_REVIEW_2026-06-19.md @@ -0,0 +1,269 @@ +# 生产上线严格复审报告(当前真相版) + +- 项目:`/home/long/project/gaokao-volunteer-system` +- 复审日期:2026-06-19 +- 复审目标:按生产上线要求复核昨天 strict review 之后的当前真实状态,剔除已修复项,只保留当前仍有效的上线问题。 +- 复审依据: + - `docs/CURRENT_STATE.md` + - `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` + - `scripts/dev-verify.sh` + - `.github/workflows/ci.yml` + - 当前主干代码与本轮实跑结果 + +--- + +## 1. 结论 + +**状态**:本地验证完成,未达到生产上线质量要求。 + +**最短结论**: + +1. 代码门禁已显著提升,当前 `dev-verify` 可真实通过:`1172 passed`,`coverage gate summary: overall=85.10%, core=100.00%`。 +2. 昨日 strict review 中的大部分高危问题已在后续 commit 收口: + - WAL SQLite 恢复校验 + - coverage 口径失真 + - 后台角色授权缺失 + - 订单报告路径任意文件读取 + - portal token 直接透传支付 URL + - retention 未清理 `delivery_notifications.payload_json` +3. 但**当前仍有 4 类问题阻止按“生产可上线”对外表述**: + - **P0 文档真相漂移**:PRD / ROADMAP 仍把 Web 自助售卖链与 AI 审核覆盖面写得过满,容易误导为“已是完整 Web 自助产品” + - **P0 合规删除门禁缺失**:删除/匿名化没有保留期/审计期代码门禁 + - **P1 支付失败状态机未闭环**:失败 webhook 没有持久化 `failed` 状态 + - **P1 当前真相源未更新**:`CURRENT_STATE` / 执行板仍停留在 6/17 口径,没有吸收 6/18-6/19 的真实修复与剩余问题 + +因此,**当前真实结论是:可继续作为本地/受控试运行版本,不可宣称生产上线完成。** + +--- + +## 2. 本轮直接验证证据 + +### 2.1 统一门禁 + +执行: + +```bash +GAOKAO_SKIP_INSTALL=1 bash scripts/dev-verify.sh +``` + +本轮结果(摘录): + +- `1172 passed, 6 warnings in 87.54s` +- `coverage gate summary: overall=85.10%, core=100.00%` +- `ruff: All checks passed!` +- `mypy: Success: no issues found in 225 source files` + +### 2.2 当前代码直接核对 + +已直接核对关键实现: + +- `admin/auth.py`:存在 `require_role(*allowed_roles)` +- `admin/routes/orders.py`:订单列表/导出/详情/创建等高权限接口已挂 `Depends(require_role("admin"))` +- `admin/routes/orders.py`:`audit_report/pdf_path/plan_file` 已走 `_validate_report_artifact_path()` +- `admin/routes/web_public.py`:portal 支付回跳已改为 `/portal/{token}/payment-success`,不再用 `?token=` +- `scripts/check_coverage_gate.py`:已忽略 `tests/`、`admin/tests/`、`docs/` +- `scripts/backup_verify.sh`:live SQLite staging 已改用 `sqlite3.backup()` + +### 2.3 当前仍未闭环的代码事实 + +#### A. 删除/匿名化缺保留期门禁 + +直接搜索: + +- 在 `data/orders/*.py` 未找到 `retention_until` / `retain_until` / 删除拒绝门禁相关实现 +- `data/orders/deletion_service.py` 的 `delete_order()` / `anonymize_order()` 仅做存在性检查、文件删除、数据清空、审计落库,**没有任何保留期/支付审计期阻断逻辑** + +#### B. 失败支付状态未持久化 + +`data/payments/service.py` 当前逻辑: + +- `normalized_status not in {"paid", "TRADE_SUCCESS", "TRADE_FINISHED"}` 时直接 `raise PaymentError("payment status not successful")` +- 没有把 payment 记录更新为 `failed` + +#### C. 文档仍会误导上线判断 + +本轮直接复核到的关键残留: + +- `README.md` 顶部口径是正确的,但正文仍以产品能力介绍为主,容易盖过“非完整 Web 自助产品”的限制 +- `product/PRD.md` 第 9 章仍将 `Web系统` 放入渠道矩阵与定价矩阵,且 `AI审核版/基础版/标准版` 仍写 `闲鱼/Web`、`微信/Web` +- `product/ROADMAP.md` 仍把“AI方案审核 / 反扎堆 / 数据溯源”整体打成“已完成”,但当前 `audit run` 实际只承认“省规则 + 专业目录状态”两类检查 +- `docs/TECH_ARCHITECTURE.md` 已比旧版收紧,但仍有“当前(v2.0)技术栈 / 已实现模块 / v2.1新增技术栈”混排,容易让评审混淆 Current 与 Target + +--- + +## 3. 当前仍有效的问题清单(只保留当前真实问题) + +## P0 / 必须修复后才可按生产上线表述 + +### P0-1 文档真相漂移:PRD / ROADMAP 仍高估 Web 自助与 AI 审核覆盖面 + +**问题**: + +当前真相源 `docs/CURRENT_STATE.md` 已明确: + +- 项目定位是人工服务运营增强系统 +- 用户端 Web 自助闭环仅为本地 MVP / 目标态 +- `audit run` 当前只承认“省规则 + 专业目录状态”两类真实检查 + +但 PRD / ROADMAP 仍存在会误导上线判断的表述: + +- `product/PRD.md` 第 9 章仍将 `Web系统` 作为推广渠道、订单来源和适用定价渠道直接列入主表 +- `product/ROADMAP.md` 仍将“AI方案审核 / 反扎堆推荐 / 数据溯源”整体标为“已完成”,易被理解为当前完整产品能力已收口 + +**影响**: + +- 会把“本地 MVP / 目标态”误读成“已上线能力” +- 会把独立能力误读成 `audit run` 已全量覆盖 +- 会直接污染对外承诺、验收口径和后续 review 基线 + +**定位**: + +- `product/PRD.md` +- `product/ROADMAP.md` +- `docs/TECH_ARCHITECTURE.md` +- `README.md`(需进一步收口正文口径) + +--- + +### P0-2 删除/匿名化缺少保留期/审计期代码门禁 + +**问题**: + +`data/orders/deletion_service.py` 当前支持: + +- 直接删除订单 +- 匿名化订单 +- 清理文件、payments callback、order_intakes、delivery_notifications +- 写入删除审计表 + +但没有看到: + +- `retention_until` 一类字段 +- 删除前检查“支付审计期/争议期/法定保留期”的 guard +- “应拒绝删除”的回归测试 + +**影响**: + +- 当前后台可对仍处于保留期的订单直接删除/匿名化 +- 与 `docs/DATA_RETENTION_AND_DELETION.md` 的合规口径不一致 +- 这是当前最重的真实代码缺口 + +**定位**: + +- `data/orders/deletion_service.py` +- `data/orders/schema.py` +- `admin/routes/orders.py` +- `admin/tests/test_order_deletion.py` + +--- + +## P1 / 生产前应补齐 + +### P1-1 失败支付状态机未闭环 + +**问题**: + +`data/payments/service.py` 对失败 webhook 当前行为是: + +- 直接抛 `PaymentError("payment status not successful")` +- 不写 `payment.status = failed` +- 不形成失败持久化审计状态 + +**影响**: + +- 管理后台与后续排障无法从 payment 记录直接看到“失败态” +- 失败支付只能体现为一次 HTTP 失败,而不是完整业务状态 + +**定位**: + +- `data/payments/service.py` +- 相关 payment tests + +--- + +### P1-2 当前真相源未吸收 6/18-6/19 的真实变化 + +**问题**: + +- `docs/CURRENT_STATE.md` 仍停留在 2026-06-17 +- `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` 也未反映 6/18 strict review 后的修复与剩余问题 +- 昨日 review 与整改计划仍是未提交/未纳管状态 + +**影响**: + +- 后续 agent 或评审者仍会引用旧口径 +- 已修复项可能被继续误报为未完成 +- 当前仍有效问题无法进入唯一真相链 + +--- + +### P1-3 README/运行说明仍有少量“python3 直接运行”口径残留 + +**问题**: + +当前项目真实运行依赖 `.venv`。虽然顶层门禁已统一,但 README 正文仍有若干命令示例容易让人误以为系统 `python3` 直跑就是正式支持路径。 + +**影响**: + +- 新环境复现时仍可能踩到解释器/依赖漂移问题 + +--- + +## 4. 已确认收口、不应再重复列为当前阻塞的问题 + +以下问题本轮已直接复核为**代码层收口**,不应继续按“当前未修”表述: + +1. `backup_verify.sh` 对 WAL SQLite 的 live verify 不可信 +2. coverage gate 把测试代码算入应用覆盖率 +3. 后台无角色授权边界 +4. `audit_report/pdf_path/plan_file` 任意本地路径可被 portal 读出 +5. portal token 直接通过支付 URL / return_url 传播 +6. retention cleanup 漏掉 `delivery_notifications.payload_json` + +> 注意:这些问题的“代码修复已完成”不等于“整体上线完成”;仍需由当前真相源同步吸收,避免旧报告重复污染判断。 + +--- + +## 5. 当前上线判断 + +### 代码门禁 + +- **本地验证**:✅ 通过 +- 证据:`dev-verify.sh` 本轮实跑通过 + +### 运维/恢复门禁 + +- **本地恢复链**:✅ 基本通过 +- 证据:`backup_verify.sh` 已改用 SQLite backup 路径 + +### 文档/产品边界门禁 + +- **对外表述边界**:❌ 未通过 +- 原因:PRD / ROADMAP / README / CURRENT_STATE 尚未完全对齐当前真实定位 + +### 合规/业务闭环门禁 + +- **删除保留期门禁**:❌ 未通过 +- **失败支付状态机**:⚠️ 未完全通过 + +### 真实环境验收 + +- **线上真实支付/公网 notify/备案域名 acceptance**:⏸️ 仍待执行 + +--- + +## 6. 结论 + +**结论:不可上线(按“完整生产上线”标准)。** + +更准确的状态分级: + +- **代码与本地门禁**:本地验证完成 +- **生产文档真相源**:未完成 +- **生产合规删除门禁**:未完成 +- **支付失败业务状态机**:部分完成 +- **线上真实验收**:未完成 + +因此当前只能表述为: + +> 项目已具备较强的本地验证与受控试运行基础,但距离“生产上线可对外承诺”仍差一轮收口,当前阻塞集中在:文档真相、删除保留期门禁、失败支付状态机、真相源同步。 diff --git a/reports/STRICT_COMPREHENSIVE_REVIEW_2026-06-18.md b/reports/STRICT_COMPREHENSIVE_REVIEW_2026-06-18.md new file mode 100644 index 0000000..a673631 --- /dev/null +++ b/reports/STRICT_COMPREHENSIVE_REVIEW_2026-06-18.md @@ -0,0 +1,962 @@ +# 高考志愿填报系统严格全面 Review 报告 + +- 项目路径:`/home/long/project/gaokao-volunteer-system` +- 评审日期:2026-06-18 +- 评审方式:文档审计 + 结构化代码审计 + 安全/数据治理/运行时补充审计 + 定点命令验证 + 定点测试复核 +- 评审目标:识别项目真实状态、代码与运维质量、规划/设计/实现差距、对外承诺边界、隐藏安全问题与后续优先级 + +--- + +## 1. 执行结论 + +### 1.1 结论 + +本项目**不是完整的用户端 Web 自助 SaaS**。 + +当前更准确的定位仍是: + +> **人工服务运营增强系统 + AI 审核增强链路**。 +> +> 后台运营、订单、分享、渠道同步、AI 审核、规则真相源、专业目录、统一 CLI 回归入口已经形成; +> 用户端 Web 自助链路仍处在**本地 MVP / 目标态过渡**,尚未达到可以稳定对外承诺的完整商业闭环。 + +### 1.2 严格标准下的总体判断 + +> **项目有真实实现价值,但当前不宜按“完整 Web 自助产品”对外验收,也不宜按“安全/合规/恢复链已闭环”对外表述。** + +更严格标准下,当前核心问题不是“没有代码”,而是: + +1. **文档仍混写现状与目标态**,会误导评审、验收和对外承诺。 +2. **验证链仍高估质量**,尤其是 restore 真实性和 coverage 指标。 +3. **存在真实安全/授权/数据治理问题**,且部分已能构成可复现的利用链。 +4. **运行时与交付边界仍有假接线、可复现性不足和环境漂移问题**。 + +### 1.3 综合评级 + +| 维度 | 评级 | 结论 | +| --- | --- | --- | +| 真实状态可追踪性 | B- | `CURRENT_STATE` 清楚,但其他文档持续漂移 | +| 产品定位与对外边界 | C+ | 总口径已收紧,正文仍有误导性承诺 | +| 架构与模块化 | B | 分层务实,域边界基本清楚 | +| 实现质量 | C+ | 主链可运行,但存在授权、文件路径、半实现状态机等问题 | +| 安全与权限边界 | C | 认证存在,授权与能力 URL 边界不足 | +| 数据治理与合规 | C- | 加密/脱敏有基础,但保留期与旁路数据治理仍不完整 | +| 测试与验证可信度 | C | 测试数量多,但 coverage 和部分文档型测试高估质量 | +| 运维与恢复可信度 | C- | 快照路径尚可,live verify 对 WAL 不可信 | +| 对外稳定交付准备度 | D+ | 更接近受控试运行,不是完整对外交付状态 | + +--- + +## 2. 本次评审的事实基础 + +### 2.1 关键文档 + +- `docs/CURRENT_STATE.md` +- `README.md` +- `product/PRD.md` +- `product/ROADMAP.md` +- `product/MARKET_RESEARCH.md` +- `docs/TECH_ARCHITECTURE.md` +- `docs/IMPLEMENTATION_PLAN_v2.md` +- `docs/PAYMENT_DOMAIN_DESIGN.md` +- `docs/BACKUP_AND_RECOVERY_PLAN.md` +- `docs/DELIVERY_RETENTION_OPS_RUNBOOK.md` +- `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md` +- `docs/DATA_RETENTION_AND_DELETION.md` +- `docs/LEGAL_PRIVACY_BASELINE.md` +- `docs/PRIVACY_POLICY_DRAFT.md` +- `.github/workflows/ci.yml` +- `docker-compose.yml` +- `Dockerfile` +- `scripts/dev-verify.sh` +- `scripts/backup_snapshot.sh` +- `scripts/backup_verify.sh` + +### 2.2 关键代码抽样 + +- `admin/app.py` +- `admin/auth.py` +- `admin/config.py` +- `admin/routes/auth.py` +- `admin/routes/orders.py` +- `admin/routes/notifications.py` +- `admin/routes/ui.py` +- `admin/routes/web_public.py` +- `admin/share_page.py` +- `admin/users.py` +- `data/orders/models.py` +- `data/orders/schema.py` +- `data/orders/crypto.py` +- `data/orders/intake_store.py` +- `data/orders/deletion_service.py` +- `data/orders/retention_cleanup.py` +- `data/customer_portal/token.py` +- `data/payments/service.py` +- `data/payments/dao.py` +- `data/payments/provider_requirements.py` +- `data/payments/providers/mock_gateway.py` +- `data/payments/providers/alipay_sim.py` +- `data/payments/providers/alipay.py` +- `data/notifications/email_service.py` +- `data/notifications/dispatcher.py` +- `data/share/permission.py` +- `data/share/short_link.py` +- `requirements-admin.txt` +- `requirements-dev.txt` + +### 2.3 本次实际命令验证 + +已实际执行并观察结果: + +1. 定点 pytest: + +```bash +./.venv/bin/python -m pytest \ + tests/test_audit_engine_major_validation_phase2.py \ + admin/tests/test_payment_alipay_notify.py \ + tests/test_backup_workflow.py \ + tests/test_dev_verify_entrypoint.py \ + tests/test_cli_doctor_phase3.py -q +``` + +结果:`31 passed, 1 warning in 8.74s` + +2. 更严格补充审计 pytest: + +```bash +./.venv/bin/python -m pytest \ + admin/tests/test_web_public.py \ + admin/tests/test_order_status_page.py \ + admin/tests/test_p2_4_p2_5_secrets.py \ + tests/test_delivery_notification.py -q +``` + +结果:`45 passed, 1 warning in 4.49s` + +3. 数据治理补充审计 pytest: + +```bash +./.venv/bin/python -m pytest \ + admin/tests/test_order_info_upload.py \ + admin/tests/test_share_ui.py \ + admin/tests/test_order_deletion.py \ + admin/tests/test_order_info_form.py::test_portal_deletion_request_is_logged_and_visible_in_admin \ + data/share/tests/test_permission.py -q +``` + +结果:`54 passed, 1 warning in 2.28s` + +4. 统一 CLI doctor: + +```bash +./.venv/bin/python scripts/gaokao-cli doctor --json +``` + +结果要点: + +- `ok: true` +- `province_count: 27` +- `active_rule_count: 298` +- `missing_evidence_rule_count: 0` + +5. 生产配置 fail-closed 复核: + +```bash +set -a && source .env.docker.example && set +a && ./.venv/bin/python - <<'PY' +from admin.config import load_settings +load_settings() +PY +``` + +结果:按预期失败,报错 `GAOKAO_PAYMENT_PROVIDER=mock 在生产环境被禁止`。 + +6. 公开健康检查响应复核: + +```bash +env GAOKAO_ENV=dev \ + GAOKAO_JWT_SECRET=test-secret-12345678901234567890123456789012 \ + GAOKAO_PORTAL_TOKEN_SECRET=portal-secret-12345678901234567890123456789012 \ + GAOKAO_ORDERS_FERNET_KEY=test-orders-key \ + ./.venv/bin/python - <<'PY' +from admin.app import create_app +from fastapi.testclient import TestClient +app = create_app() +with TestClient(app) as client: + r = client.get('/health') + print(r.status_code) + print(r.json()) +PY +``` + +结果:`200`,且公开返回 `{'status': 'ok', 'env': 'dev', 'db_path': 'data/orders/admin.db', 'service': 'gaokao-admin', 'version': '0.1.0'}`。 + +7. 低权限后台账号越权复核: + +```bash +# 通过 AdminUserRepo.create(..., role='viewer') 建低权限账号 +# 再用其 Bearer token 调用后台订单接口 +``` + +结果:`login_status = 200`,`orders_status = 200`。 + +8. 任意本地文件读取链复核: + +```bash +# viewer 账号 PATCH audit_report=/etc/hosts +# 再推进订单到 delivered/completed +# 再访问 /portal/{token}/report +``` + +结果:`report_status = 200`,`contains_hosts_marker = true`。 + +9. 本地运行边界复核: + +```bash +./.venv/bin/pip freeze | wc -l +python3 - <<'PY' +mods = ['pytest','fastapi','uvicorn','weasyprint','locust'] +for name in mods: + try: + __import__(name) + print(name, 'ok') + except Exception as exc: + print(name, type(exc).__name__) +PY +GAOKAO_SOURCE_ONLY=1 PYTHON_BIN=python3 bash -c 'source scripts/dev-verify.sh; ensure_venv; python --version' && python3 --version +``` + +结果: + +- 当前本地 `.venv` 已装包数量:`82` +- 当前工作站系统 `python3` 直接导入 `pytest/fastapi/uvicorn/weasyprint/locust` 全部 `ModuleNotFoundError` +- `dev-verify.sh` 复用既有 `.venv` 时,`python --version = 3.11.13`;同机 `python3 --version = 3.12.3` + +10. 环境现实边界:系统 `python3` 直接运行并不等于项目可运行;当前验证依赖仓库 `.venv`。 + +--- + +## 3. 项目当前真实状态 + +### 3.1 已验证的真实定位 + +以下结论被文档真相源与代码/命令共同支撑: + +- 当前项目定位是**人工服务运营增强系统**,不是完整 Web 自助 SaaS。 + 证据:`docs/CURRENT_STATE.md:42-59`,`README.md:7-12` +- 已完成主线包括:后台运营、订单、分享、渠道同步、AI 审核、规则真相源、专业目录、统一 CLI 回归入口。 + 证据:`docs/CURRENT_STATE.md:46-51,62-75` +- T12 用户端 Web 自助只能表述为**已启动/实施中**;真实支付 acceptance、线上通知联调、线上交付调度仍未收口。 + 证据:`docs/CURRENT_STATE.md:96-115` +- 当前执行口径是:执行 Phase 1 / 1.5 / 2 已收口;下一阶段是执行 Phase 3 统一 CLI 命令面,未启动。 + 证据:`docs/CURRENT_STATE.md:25-38` + +### 3.2 已确认修复的历史问题 + +以下问题**代码层面已修复**,不应继续按“当前未解决”表述: + +- 支付回调不会再把 `refunded` payment 回写成 `paid`。 + 证据:`data/payments/service.py:194-289` +- 生产环境支付 provider fail-closed 已生效;`prod` 下只允许 `alipay`。 + 证据:`admin/config.py:155-173` +- 通知唯一键已包含 `channel`,多渠道事件不再天然冲突。 + 证据:`data/notifications/email_service.py:12-25` +- `audit run` 已从“假壳”变成真实调用 `RuleLoader + MajorsCatalogLoader + AuditEngine`。 + 证据:`data/rules/cli.py`,`data/rules/audit_engine.py` +- `backup_snapshot.sh` 已对 SQLite 使用 `sqlite3.backup()`,不再是裸文件复制。 + 证据:`scripts/backup_snapshot.sh:36-58` +- 公开下单链路要求 `GAOKAO_ORDERS_FERNET_KEY`,缺失即 503,不会降级为明文落盘。 + 证据:`admin/routes/web_public.py:174-183`,`data/orders/crypto.py:46-54` + +--- + +## 4. 主要问题清单 + +## 4.1 P0 / 严重问题 + +### P0-1 `backup_verify.sh` 的 live staging 路径对 WAL SQLite 不可信 + +**问题** + +`scripts/backup_verify.sh` 在 live 校验路径对 SQLite 用的是 `cp`,而不是 SQLite backup API,也没有处理 `-wal/-shm`。这意味着: + +- 对开启 WAL 的 live DB,复制出来的副本可能**缺表或缺数据**; +- 文档声称“可直接对 live 数据做恢复演练”,但当前只对非 WAL / 已 checkpoint 场景可信。 + +**证据** + +- `scripts/backup_verify.sh:33-43,70-79,128-155` +- `scripts/backup_snapshot.sh:36-58` +- `docs/BACKUP_AND_RECOVERY_PLAN.md:124-130` +- 定点审计实测:同类 WAL 临时库在 `backup_verify.sh --skip-smoke` 后出现 `tables=none`,而 `backup_snapshot.sh` 快照路径能保留数据。 + +**影响** + +- 恢复演练可能“看起来成功”,实际副本不可恢复。 +- 这是当前最重的运维阻断项。 + +--- + +### P0-2 coverage gate 被测试代码显著抬高,CI 绿灯不能真实代表业务代码覆盖质量 + +**问题** + +`scripts/dev-verify.sh` 用 `--cov=.` 覆盖整个仓库,`coverage.xml` 明确包含 `tests/**` 和 `admin/tests/**`。这让测试代码覆盖测试代码本身,显著抬高 overall 指标。 + +**证据** + +- `scripts/dev-verify.sh:63-71` +- `scripts/check_coverage_gate.py:16-22,64-83` +- `codecov.yml:30-39` +- 复核结果: + - overall coverage ≈ `90.35%` + - 非测试代码 coverage ≈ `82.31%` + - `test_lines=8902` + - `non_test_lines=8588` + +**影响** + +- “overall ≥ 80%”被系统性稀释。 +- CI / 本地 verify 的通过不再等价于“应用代码覆盖达标”。 + +--- + +### P0-3 `PRD` 与 `TECH_ARCHITECTURE` 仍能误导读者把目标态当现状 + +**问题** + +虽然 `CURRENT_STATE` 已收口,但核心对外文档仍存在两类严重漂移: + +1. `PRD` 把 Web 当作当前售卖/交付渠道写入业务与定价; +2. `TECH_ARCHITECTURE` 同时描述 Current/Target,却包含大量不存在的路径、API、CLI 与测试名。 + +**证据** + +- `product/PRD.md:472-523` +- `docs/TECH_ARCHITECTURE.md:384-444,617-628,726` +- `docs/API.md:147-213,244-299` +- 当前真相源反证:`docs/CURRENT_STATE.md:96-115,191-205` + +**影响** + +- 对外承诺失真 +- 验收标准漂移 +- 新成员 / 评审 / 商务容易按不存在的能力理解项目 + +--- + +### P0-4 订单路径字段可被后台写入任意本地路径,并经 portal 报告页读出 + +**问题** + +`audit_report` / `pdf_path` 是后台允许更新的字段;portal 报告页在阶段满足后会直接读取这些路径指向的本地文件内容或直接下发文件响应,缺少基目录白名单、后缀限制与来源校验。 + +这不是抽象风险,已经可以组合成**真实本地文件读取链**。 + +**证据** + +- `admin/routes/orders.py:104-106` +- `admin/routes/orders.py:528-555` +- `admin/routes/web_public.py:536-558` +- `admin/routes/web_public.py:2176-2189` +- 定点实跑:把订单 `audit_report` 更新成 `/etc/hosts`,再推进状态到 `delivered/completed` 后访问 `/portal/{token}/report`,结果 `report_status = 200`,`contains_hosts_marker = true` + +**影响** + +- 应用可读取宿主机本地文本文件并通过 portal 对外返回。 +- 一旦再叠加 token 泄露或低权限后台账号,风险进一步扩大。 + +--- + +## 4.2 P1 / 高优先级问题 + +### P1-1 `audit run` 已可执行,但实际规则审计广度远低于文档声称范围 + +**问题** + +`AuditEngine.audit_plan()` 的实际覆盖面仍很窄: + +- 省规则侧当前只做 `RULES.max_volunteers` +- major 侧只做 `MAJORS.not_found` / `MAJORS.non_active` +- 没有实现 mode、retrieval_rule、collection_count、subject_mode、数据完整性等检查 + +**证据** + +- `data/rules/audit_engine.py:43-78` +- `data/rules/cli.py` 的 `audit run` +- 对应测试只覆盖 `max_volunteers` 与 major 状态 + +**影响** + +- 当前 CLI 输出“通过”并不等价于方案已通过完整省规则审计。 + +--- + +### P1-2 删除/匿名化链路缺少文档要求的保留例外 / 支付审计期门禁 + +**问题** + +删除与匿名化能力已扩围,但仍未真正执行“受保留约束订单不得删除/匿名化”的合规逻辑。 + +**证据** + +- `admin/routes/orders.py:609-645` +- `data/orders/deletion_service.py:50-139` +- `docs/DATA_RETENTION_AND_DELETION.md:23-35` +- 测试主要覆盖 happy path,没有“应拒绝删除”的用例 + +**影响** + +- 支付审计与争议保留策略无法靠代码强制执行。 + +--- + +### P1-3 后台只做认证不做角色授权,任意已认证账号都拥有完整订单后台能力 + +**问题** + +后台当前只有“已认证”门槛,没有“角色授权”门槛。`role` 字段存在,但没有被任何后台订单路由使用。 + +**证据** + +- `admin/auth.py:112-141` 的 `get_current_user()` 只校验 token / user / is_active,直接 `return user` +- `admin/routes/orders.py:347-354,377-383,411-445,528-533` 的订单列表/导出/详情/创建/更新只依赖 `Depends(get_current_user)` +- `admin/db.py:130-149` 允许创建任意 `role` 的后台账号 +- 全仓未找到 `require_role` 实现或调用 +- 定点实跑:`AdminUserRepo.create(..., role='viewer')` 建低权限账号后,`login_status = 200`,随后 `orders_status = 200` + +**影响** + +- 当前一旦出现非 admin 账号(人工建号、脚本造号、未来扩角色),会直接获得完整后台订单能力。 +- 这不是“以后可能有问题”,而是当前角色字段已经存在、但不参与授权决策。 + +--- + +### P1-4 Portal token 作为 Bearer 能力被放进 URL / 支付回跳参数,并明文持久化到 payments 表 + +**问题** + +Portal token 当前同时出现在: + +- `/portal/{token}/status`、`/portal/{token}/info` 等能力 URL +- 模拟支付 checkout URL 的查询串 `?token=...` +- 真实支付宝 `return_url` 的查询串 `?token=...` +- `payments.checkout_token` 明文字段 + +**证据** + +- `admin/routes/web_public.py:195-205,293-298` +- `data/payments/providers/mock_gateway.py:16-24` +- `data/payments/providers/alipay_sim.py:17-25` +- `data/payments/providers/alipay.py:49-82` +- `data/payments/service.py:159-166` +- `data/payments/dao.py:21-23,71-84,189-191` +- 定点实跑:公开下单返回的 `checkout_url` 包含 token,`portal_status_url` 本身也是能力 URL + +**影响** + +- Portal token 的泄露面扩展到浏览器历史、代理日志、第三方支付回跳链、数据库快照。 +- 由于该 token 能访问状态、资料、通知、报告等整条 portal 链路,风险高于普通临时 query 参数。 + +--- + +### P1-5 `retention cleanup` 的 180 天匿名化链路没有处理 `delivery_notifications.payload_json` + +**问题** + +定时匿名化作业只更新 `orders`、清空 `payments.callback_payload`、清空 `order_intakes.payload_json`,但没有处理 `delivery_notifications.payload_json`。 + +**证据** + +- `data/orders/retention_cleanup.py:70-91` +- `data/orders/deletion_service.py:91-127` +- `data/notifications/email_service.py:12-25` +- `data/notifications/dispatcher.py:81-95,118-145` + +**影响** + +- 通知审计表仍可残留 `customer_email`、`audit_report`、`plan_file`、`pdf_path`、发送结果等信息。 +- 形成“订单已匿名化,但旁路通知数据仍可回溯”的保留期不一致。 + +--- + +### P1-6 后台通知与运维审计接口直接返回完整 payload/details,缺少服务端脱敏边界 + +**问题** + +后台通知审计与运维告警审计受 JWT 保护,但返回/展示的是原始 `payload_json` 与 `details`。其中通知 payload 已包含 `customer_email`、`pdf_path` 等敏感或内部路径字段。 + +**证据** + +- `data/orders/dao.py:601-614` +- `admin/routes/notifications.py:139-200` +- `admin/routes/notifications.py:203-267` +- `admin/routes/notifications.py:270-325` + +**影响** + +- 后台任一拥有 token 的账号可直接获取更多 PII / 内部路径,而不受字段级最小暴露控制。 + +--- + +### P1-7 运行时契约与依赖可复现性仍弱 + +**问题** + +当前至少有三层运行时/交付契约没有收敛: + +1. `docker-compose` 传入的 `GAOKAO_ADMIN_BIND/PORT` 对应用监听参数不生效;镜像命令把 `--host 0.0.0.0 --port 8000` 写死。 +2. 依赖无锁:仓库只有 `requirements-admin.txt` 与 `requirements-dev.txt`,没有 lock/constraints;CI cache key 还只 hash `requirements-dev.txt`,但会同时安装 `requirements-admin.txt`。 +3. `dev-verify.sh` 会静默复用既有 `.venv`,不会按当前 `PYTHON_BIN` 重建;本地通过不等于覆盖了当前系统解释器或声明矩阵。 + +**证据** + +- `docker-compose.yml:12-14,33-34` +- `Dockerfile:25` +- `admin/app.py` 只读 CLI host/port,不读对应 env +- `requirements-admin.txt:4-14` +- `requirements-dev.txt:5-18` +- `.github/workflows/ci.yml:36-67` +- `scripts/dev-verify.sh:25-50` +- 定点实跑: + - 带 `GAOKAO_ADMIN_BIND=9.9.9.9 GAOKAO_ADMIN_PORT=9999` 启动,日志仍是默认监听 + - `GAOKAO_SOURCE_ONLY=1 PYTHON_BIN=python3 ... ensure_venv; python --version` 输出仍为 `3.11.13` + - 同机 `python3 --version = 3.12.3` + +**影响** + +- 运行配置存在“假接线”; +- CI / 本地无法证明同提交一定落到同一依赖树; +- 本地验证通过不等于当前系统解释器也被验证过。 + +--- + +### P1-8 同意记录落库仍停留在布尔/版本字段,未达到文档自身最低要求 + +**问题** + +文档要求至少记录 `privacy_accepted_at`、`service_terms_accepted_at`、`consent_channel`、`consent_given_at`、`consent_operator` 等字段;当前落库仍只有 `consent_version`、`consent_scope` 与几个布尔值。 + +**证据** + +- `docs/LEGAL_PRIVACY_BASELINE.md:79-92,117-130` +- `admin/routes/web_public.py:1842-1863` +- `data/orders/intake_store.py:68-100` +- 代码中未发现上述审计元字段的实际落库实现 + +**影响** + +- 只能证明“勾选过”,不能证明“何时、由谁、通过什么渠道同意”。 + +--- + +### P1-9 联系邮箱 `customer_email` 明文落库,与手机号/身份证加密策略不一致 + +**问题** + +手机号与身份证会加密落盘;联系人邮箱 `customer_email` 则直接明文写进 `orders`,并继续进入通知 payload。 + +**证据** + +- `data/orders/schema.py:26-31` +- `data/orders/models.py:57-63,110-127` +- `data/orders/tests/test_models.py:46-57` +- `data/orders/dao.py:601-614` + +**影响** + +- 联系邮箱保护强度明显低于手机号/身份证。 +- 数据库泄露时,邮箱更容易被直接用于批量营销或钓鱼。 + +--- + +### P1-10 失败支付状态机半实现:UI 有 `payment_failed`,代码缺真实落库路径 + +**问题** + +Portal/UI 已建模 `payment_failed` 阶段,但当前并没有真实 `status='failed'` 的业务写入路径;非成功回调直接抛 `PaymentError("payment status not successful")`。 + +**证据** + +- `admin/routes/web_public.py:613-635` +- `data/payments/service.py:194-205` +- `data/payments/dao.py:13-23` + +**影响** + +- 页面状态机与服务状态机不一致。 + +--- + +### P1-11 `backup_verify.sh --from-backup` 可绕过 manifest 严格校验 + +**问题** + +`verify_manifest_if_present()` 在 manifest 缺失时只打印 skip 并继续,因此 `--from-backup` 更像“恢复 smoke”,不是“正式快照完整性校验”。 + +**证据** + +- `scripts/backup_verify.sh:91-125` +- `tests/test_backup_restore_service_level.py` + +**影响** + +- 恢复 smoke 与正式快照校验语义混淆。 + +--- + +### P1-12 `ROADMAP` 状态语义漂移 + +**问题** + +同一能力在不同文档中既像“未来计划”,又像“已本地验证 / 已启动”,缺少以下三层分隔: + +- 已本地验证 +- 待线上验收 +- 目标态里程碑 + +**证据** + +- `docs/CURRENT_STATE.md:52-55,98-115` +- `product/ROADMAP.md:270-288,333-345,427-445` +- `product/README.md:113-123` + +**影响** + +- 容易同时低估已做工作,又高估线上 readiness。 + +--- + +## 4.3 P2 / 中优先级问题 + +### P2-1 确认页前端使用 `innerHTML` 回填用户输入,存在 DOM XSS sink + +**证据** + +- `admin/routes/web_public.py:1866-1879` + +**说明** + +这更偏用户侧 / 自触发 DOM XSS,不如本地文件读取链严重,但按严格标准仍应记录。 + +--- + +### P2-2 前台附件上传接口把服务器绝对 `storage_path` 写入 payload 并回传给持 token 用户 + +**证据** + +- `admin/routes/web_public.py:363-385` +- `admin/routes/web_public.py:401-435` +- `data/orders/intake_store.py:68-97` +- `admin/tests/test_order_info_upload.py:59-65` + +**影响** + +- 持 token 用户可获知服务器目录结构与订单目录命名。 + +--- + +### P2-3 前台删除申请日志以明文 JSONL 持久化,但未纳入保留期治理 + +**证据** + +- `admin/routes/web_public.py:112-129` +- `admin/config.py` 默认 `deletion_request_log_path` +- `admin/routes/notifications.py:93-115` +- `admin/tests/test_order_info_form.py::test_portal_deletion_request_is_logged_and_visible_in_admin` + +**影响** + +- 删除申请本身形成新的旁路 PII 数据集。 + +--- + +### P2-4 分享链路 allowlist 与访问遥测边界仍偏宽 + +**问题** + +1. `edit/admin` 分享权限 allowlist 仍允许联系方式、身份证号、内部交付路径等高敏字段进入 `rendered.payload`; +2. `short_links.db` 中的 `share_link_access_events(visitor_token/ip/user_agent)` 有落盘,但现有保留期文档未把它纳管。 + +**证据** + +- `data/share/permission.py:47-83,269-309` +- `data/share/tests/test_permission.py:298-300` +- `data/share/short_link.py:298-311,499-536,756-763` + +**影响** + +- allowlist 已存在,但边界仍过宽; +- 分享访问遥测属于已落盘、未纳管数据。 + +--- + +### P2-5 `docker-compose.yml` 是 dev/local smoke 模板,不是生产部署定义 + +**证据** + +- `docker-compose.yml:1-2` +- dev 默认值能通过 `load_settings()`;同值切到 prod 会被 provider fail-closed 拒绝 + +**影响** + +- 容易给出“部署模板已审过”的错误安全感。 + +--- + +### P2-6 健康检查公开暴露内部环境与数据库路径 + +**证据** + +- `admin/routes/health.py:13-25` +- 本次实测公开返回 `env` / `db_path` / `service` / `version` + +**影响** + +- 为外部探测者提供额外环境情报。 + +--- + +### P2-7 文档对 `python3` 的直接使用表述过宽,容易掩盖真实前提 + +**问题** + +当前工作站上,系统 `python3` 直接运行并不能导入项目依赖;真实前提是“先建并激活装好依赖的 venv”。 + +**证据** + +- 系统 `python3` 直接导入 `pytest/fastapi/uvicorn/weasyprint/locust` 全部 `ModuleNotFoundError` +- `README.md`、`INSTALL.md` 多处直接写 `python3 ...` +- `dev-verify.sh` 会静默复用既有 `.venv` + +**说明** + +这不等于“Python 3.12 不支持”。更准确的结论是:**系统解释器直跑不可用;已装依赖的 venv 才是当前真实运行边界。** + +--- + +### P2-8 容器内 PDF 生成边界未被仓库验证闭环 + +**问题** + +宿主 `.venv` 上 WeasyPrint PDF smoke 能成功;但 `Dockerfile` 只做 pip install,没有任何容器内 PDF 生成 smoke 或依赖断言,因此仓库当前**不能证明** `python:3.12-slim` 容器内同样能稳定生成 PDF。 + +**证据** + +- `skills/gaokao-audit/scripts/report_generator.py` +- `requirements-admin.txt:10-11` +- `Dockerfile:16-19` + +**说明** + +这是“未被证明的交付边界”,不是本次已验证的运行失败事实。 + +--- + +## 5. 已确认做得好的部分 + +这些不是“文档声称”,而是本次确认过的正向事实: + +1. **配置 fail-closed 意识明显提升** + prod 下支付 provider、portal token secret、payment webhook secret 都有真实门禁。 + 证据:`admin/config.py` + +2. **支付与订单状态已明确分离** + `PaymentService.handle_webhook()` / `request_refund()` 都在同事务内推进 payment 与 order,且退款终态回调幂等已补齐。 + 证据:`data/payments/service.py` + +3. **通知模型比 6/17 评审基线更可信** + 唯一键已含 `channel`;`station` 与 `email` 生命周期被区分。 + 证据:`data/notifications/email_service.py`,`data/notifications/dispatcher.py` + +4. **规则真相源、专业目录、doctor 命令面已初步成形** + `gaokao-cli doctor --json` 能给出真实状态,说明 CLI 层已从散乱脚本向统一命令面过渡。 + 本次已实际验证。 + +5. **仓库对“当前不是完整 Web SaaS”这一总口径已有正确自我约束** + 这点在 `CURRENT_STATE` 与顶层 `README` 中是清楚的。 + 证据:`docs/CURRENT_STATE.md`,`README.md` + +--- + +## 6. 规划 / 设计 / 实现差距汇总 + +### 6.1 规划 vs 现状 + +- 规划文档仍倾向把 Web 自助与支付当成中期主线能力; +- 现状实际上仍以人工服务运营链路为主,Web 只是本地 MVP 过渡。 + +### 6.2 设计 vs 实现 + +- 设计文档把规则审计描述得更广; +- 实现目前只有 `max_volunteers + majors status` 这一层。 + +### 6.3 文档 vs 工程事实 + +- 文档已说“最小恢复基线已具备”; +- 但 live verify 在 WAL 场景不可信,这说明“备份存在”与“可恢复性成立”之间仍有断层。 + +### 6.4 测试数字 vs 真实质量 + +- 定点 pytest 可以过; +- overall coverage 也高; +- 但 coverage 指标被测试代码显著抬高,因此不能直接等价为“业务代码质量高”。 + +### 6.5 安全与数据治理 + +- 认证存在,但授权边界不完整;低权限角色与后台能力之间没有真正隔离。 +- Portal token、报告路径、附件路径、删除申请日志、分享遥测与通知 payload 共同说明:系统尚未形成统一的“能力 URL / 敏感路径 / 旁路数据”的最小暴露模型。 +- PII 保护策略不一致:手机号/身份证已加密,邮箱仍明文;删除/匿名化已扩围,但同意记录与保留期治理仍不足。 + +### 6.6 依赖与运行时 + +- 当前可运行性高度依赖本地 `.venv` 现状;仓库缺少锁文件与漏洞审计内建入口。 +- `dev-verify` 证明的是当前 `.venv` 下可过,不等于供应链可复现,也不等于当前系统解释器被重新验证过。 +- 容器交付边界仍有未被证明的部分,特别是 PDF 生成能力。 + +--- + +## 7. 下一阶段建议优先级 + +## 7.1 必须先做(P0) + +1. **修复 `backup_verify.sh` 的 live SQLite staging** + 与 `backup_snapshot.sh` 一样改为 SQLite backup API,或显式处理 WAL/SHM。 + +2. **重做 coverage gate 口径** + overall 指标排除 `tests/**`、`admin/tests/**`、纯文档/脚本样板;保证 CI 的 overall 代表应用代码。 + +3. **文档治理:先修 `PRD` 和 `TECH_ARCHITECTURE`** + 去掉把 Web 当成当前售卖/交付渠道的表达,拆分 Current / Target,删除不存在路径、CLI、API、测试名。 + +4. **阻断通过订单路径字段触发的本地文件读取链** + `audit_report` / `pdf_path` / `plan_file` 必须改成受控基目录、受控来源、受控后缀;portal report/download 不应直接信任任意本地路径。 + +## 7.2 高优先(P1) + +5. **补后台角色授权** + 至少明确“只有 admin 可访问后台订单写能力”;如果保留 `role` 字段,就必须真正执行 RBAC,而不是只做认证。 + +6. **收紧 Portal token 传播边界** + 不再把高权限 token 当 query 参数在支付回跳/checkout URL 中传播;减少或取消 `checkout_token` 明文持久化。 + +7. **补齐 retention / anonymize 的跨表治理** + 至少把 `delivery_notifications.payload_json`、删除申请日志、分享遥测纳入统一保留期策略。 + +8. **决定 `audit run` 的真实 contract** + 要么把文档降到真实范围;要么补齐 mode / retrieval_rule / collection_count / 数据完整性等检查。 + +9. **修复运行时契约漂移** + - 让 compose 的 host/port/env 契约真实生效,或删除假接线配置 + - 增加 lock / constraints 机制 + - 把 CI cache key 与运行依赖对齐 + - 让 `dev-verify` 显式检查 venv 解释器漂移 + +10. **把失败支付做成真实状态机,或删掉死分支** + +11. **把 `backup_verify.sh` 划分成两种模式** + `live-smoke` 与 `snapshot-verify`(强制 manifest)。 + +## 7.3 中优先(P2) + +12. **收紧 portal 前端 HTML 安全面** + 报告 HTML 至少加白名单净化或沙箱 iframe;确认页去掉 `innerHTML` 拼接用户输入。 + +13. **为后台审计接口建立字段级脱敏 / allowlist** + 通知 payload、ops alert details、删除申请日志都不应默认全量透传。 + +14. **补齐同意记录审计字段与邮箱保护策略** + 至少补 `*_accepted_at`、`consent_channel`、`consent_operator`;明确 `customer_email` 是加密、哈希索引还是显式例外。 + +15. **把公开健康检查降到最小暴露** + 只返回 readiness 信号,不返回 `db_path` / `env` / 详细版本。 + +16. **把文档中的 `python3` 直跑表述改成明确的 venv 前提** + +17. **为容器内 PDF 生成增加真实 smoke / CI 验证** + +--- + +## 8. 最终判断 + +### 8.1 如果问题是“这个项目有没有价值?” + +有。 + +它已经不是一个纯文档仓库,也不是只有 demo 的原型。后台、订单、支付域、通知域、规则真相源、专业目录、统一 CLI 都有真实实现和真实测试基础。 + +### 8.2 如果问题是“它现在是不是一个可稳定对外承诺的完整产品?” + +不是。 + +### 8.3 如果问题是“它最真实的成熟度是什么?” + +> **内部可演示 / 受控试运行 / 人工运营增强系统。** +> 不是完整的 Web 自助商业产品。 + +### 8.4 如果只用一句话概括本次更严格 review + +> **主链代码比 6/17 基线更健康,但授权边界、数据治理、恢复真实性、运行时契约和文档现状仍明显高估了项目的可交付程度。** + +--- + +## 9. 关键证据清单 + +- `docs/CURRENT_STATE.md:25-38,42-59,96-115,191-205` +- `README.md:7-12` +- `product/PRD.md:472-523` +- `product/ROADMAP.md:270-288,333-345,427-445` +- `product/README.md:113-123` +- `docs/TECH_ARCHITECTURE.md:384-444,617-628,726` +- `admin/auth.py:112-141` +- `admin/routes/orders.py:88-109,347-398,411-445,528-607` +- `admin/routes/notifications.py:139-325` +- `admin/routes/health.py:13-25` +- `admin/routes/web_public.py:112-129,174-183,195-205,293-298,363-385,401-435,536-558,613-677,1842-1879,1955-1967,2176-2189` +- `admin/share_page.py` +- `data/customer_portal/token.py:24-61` +- `data/orders/models.py:57-63,110-127,157-175` +- `data/orders/schema.py:26-31` +- `data/orders/intake_store.py:68-100` +- `data/orders/deletion_service.py:91-127` +- `data/orders/retention_cleanup.py:70-91` +- `data/orders/dao.py:601-614` +- `data/orders/crypto.py:46-54` +- `data/payments/service.py:159-166,194-289` +- `data/payments/dao.py:21-23,71-84,189-191` +- `data/payments/providers/mock_gateway.py:16-24` +- `data/payments/providers/alipay_sim.py:17-25` +- `data/payments/providers/alipay.py:49-82` +- `data/notifications/email_service.py:12-25` +- `data/notifications/dispatcher.py:81-95,118-145` +- `data/share/permission.py:47-83,269-309` +- `data/share/short_link.py:298-311,499-536,756-763,844-922` +- `docs/DATA_RETENTION_AND_DELETION.md:11-18,23-35` +- `docs/LEGAL_PRIVACY_BASELINE.md:79-92,117-130` +- `requirements-admin.txt:4-14` +- `requirements-dev.txt:5-18` +- `Dockerfile:16-25` +- `docker-compose.yml:1-36` +- `.github/workflows/ci.yml:36-67` +- `scripts/dev-verify.sh:25-50,53-81` +- `scripts/backup_snapshot.sh:36-58,176-198` +- `scripts/backup_verify.sh:70-223` + +--- + +## 10. 本次实际验证摘要 + +- 定点 pytest:`31 passed` +- 更严格补充审计 pytest:`45 passed` +- 数据治理补充审计 pytest:`54 passed` +- `gaokao-cli doctor --json`:`ok=true` +- `.env.docker.example` 在 prod 下按预期 fail-closed +- 低权限账号实测可访问后台订单接口:`orders_status = 200` +- 通过 `audit_report=/etc/hosts` + portal report 可复现本地文件读取:`report_status = 200`,`contains_hosts_marker = true` +- 公开 `/health` 实际暴露 `env` 与 `db_path` +- 当前 `.venv` 已装包数:`82` +- 系统 `python3` 直跑无法导入项目关键依赖;当前真实运行边界依赖 `.venv` +- `dev-verify` 会静默复用既有 `.venv`,未自动纠正解释器漂移 + +--- + +本报告对应当前工作树审计结论,不等价于历史报告快照。 \ No newline at end of file