docs(review): add comprehensive project review 2026-07-05
Some checks failed
CI / pytest (Python 3.10) (push) Has been cancelled
CI / pytest (Python 3.11) (push) Has been cancelled
CI / pytest (Python 3.12) (push) Has been cancelled

This commit is contained in:
Hermes Agent
2026-07-05 21:01:55 +08:00
parent 4059f144c4
commit 738ab68898

View File

@@ -0,0 +1,668 @@
# 2026-07-05 项目全面 Review 报告(真实复核版)
> 生成时间2026-07-05T20:59:15+08:00
> 审查对象:`/home/long/project/gaokao-volunteer-system`
> 当前 HEAD`4059f144c49398aec2fcc3040e639e1312061086`
> 审查方式:真实读取代码/CI/脚本/现有报告 + 本地门禁执行 + 静态扫描;未把历史报告当作当前事实。
> 重要边界:本报告是代码库与本地门禁 review不等同于线上真实支付、真实域名、真实用户流量验收。
> 复验更新:已在报告初稿后补装前端依赖并复跑 `pnpm typecheck/lint/test/build`,结果已回写到 §1.2 / M0。
---
## 0. 总结结论
**结论REQUEST_CHANGES / 不能宣称生产级完成。**
本轮 review 发现的当前有效问题:
| 严重级别 | 数量 | 摘要 |
|---|---:|---|
| HIGH | 4 | Admin 导航断链、React Admin mock 登录未接真实 JWT、前端 API client 未注入 Authorization、本地 Python 总门禁 mypy 失败 |
| MEDIUM | 4 | Chromatic token 未配置会阻断 CI、LHCI 启动/端口口径仍有漂移风险、dev-verify 100-case smoke 非阻塞导致主链路回归可能被降级为 warning、前端依赖缺失曾阻断本地 fresh gate已补装并复验通过 |
| LOW | 2 | 文档中仍大量保留 mock/sandbox 历史语境,旧截图报告目录仍在仓库中易误导;部分静态扫描命中需后续清理白名单 |
已确认的正向事实:
- `main / origin / gitea / tksea` 在上一轮已同步到 `4059f14`,本轮 review 起点工作区为 `## main`
- `apps/web/src/types/api-generated.d.ts``apps/web/src/schemas/api-generated.ts` 当前 `any_count=0`
- `/health` dev readiness 改动已进入 HEAD`settings_valid` 在 dev 环境允许占位密钥,但 prod fail-closed 仍由配置加载策略覆盖。
---
## 1. Gate 结果(当前真实输出)
### 1.1 Python / 后端总门禁
命令来源:`/tmp/gaokao-review-gates-20260705.log`,由本轮执行:
```bash
bash scripts/dev-verify.sh
```
结果:**FAIL**。关键输出:
```text
admin/routes/sprint3_api.py:292: error: Incompatible types in assignment (expression has type "str", variable has type "Literal['pending', 'in_progress', 'approved', 'rejected', 'changes_requested']") [assignment]
data/cli_compat_share.py:43: error: Item "None" of "_ArgumentGroup | None" has no attribute "_group_actions" [union-attr]
data/cli_compat_share.py:44: error: Item "Action" of "Action | Any" has no attribute "add_parser" [union-attr]
data/share/poster.py:123: error: Argument "candidate_name" to "PosterPayload" has incompatible type "str | None"; expected "str" [arg-type]
data/share/poster.py:273: error: Incompatible return value type (got "FreeTypeFont", expected "ImageFont") [return-value]
data/share/poster.py:274: error: Incompatible return value type (got "FreeTypeFont | ImageFont", expected "ImageFont") [return-value]
data/share/poster.py:319: error: Incompatible return value type (got "float", expected "int") [return-value]
data/share/poster.py:324: error: Incompatible return value type (got "float", expected "int") [return-value]
data/share/poster.py:329: error: Library stubs not installed for "qrcode" [import-untyped]
Found 9 errors in 3 files (checked 268 source files)
DEV_VERIFY_EXIT=1
```
影响:
- 这不是单测功能失败而是类型门禁失败按项目质量标准不能用“pytest 局部通过”覆盖。
- 失败集中在 poster / report 相关类型与缺失 third-party stubs说明分享海报链路仍存在类型质量债。
### 1.2 前端本地门禁
第一轮执行结果:**BLOCKED**,原因是本地缺少 `node_modules` / `turbo`,未进入真实业务门禁。随后已补执行:
```bash
pnpm install --frozen-lockfile
pnpm typecheck
pnpm lint
pnpm test
pnpm build
```
复验结果:**PASS**。fresh evidence 来自 `/tmp/gaokao-frontend-gates-20260705.log`
```text
=== FRONTEND FRESH GATES START ===
--- pnpm install ---
root node_modules exists
apps/web/node_modules exists
--- web typecheck ---
--- web lint ---
--- web test ---
--- web build ---
Tasks: 2 successful, 2 total
Tasks: 1 successful, 1 total
@gaokao/web:build: TOTAL │ 1.36 MB │ 393.60 KB │ ✅
@gaokao/web:build: ✅ T-B-25 验证通过
=== FRONTEND FRESH GATES END ===
```
更新判断:前端本地 typecheck/lint/test/build 当前已有 fresh gate 证据;原 H5 从 HIGH blocker 降级为 MEDIUM 环境/可复现性问题。仍需注意:这不自动证明 Playwright e2e、Chromatic、LHCI 或真实浏览器视觉验收已全部闭环。
---
## 2. HIGH Findings
### H1 · 后台导航 `/admin/review` 真实断链
**证据:**
`apps/web/src/layouts/AdminLayout.tsx` 注册了 `/admin/review`
- L12: `const adminNavItems = [`
- L13: ` { to: '/admin', labelKey: 'admin.nav.dashboard', icon: Home },`
- L14: ` { to: '/admin/orders', labelKey: 'admin.nav.orders', icon: FileText },`
- L15: ` { to: '/admin/cases', labelKey: 'admin.nav.cases', icon: Users },`
- L16: ` { to: '/admin/share-links', labelKey: 'admin.nav.shareLinks', icon: Link2 },`
- L17: ` { to: '/admin/score-lines', labelKey: 'admin.nav.scoreLines', icon: Database },`
- L18: ` { to: '/admin/rank-estimator', labelKey: 'admin.nav.rankEstimator', icon: TrendingUp },`
- L19: ` { to: '/admin/majors', labelKey: 'admin.nav.majors', icon: BarChart3 },`
- L20: ` { to: '/admin/schools', labelKey: 'admin.nav.schools', icon: School },`
- L21: ` { to: '/admin/review', labelKey: 'admin.nav.review', icon: ShieldCheck },`
- L22: ` { to: '/admin/posters', labelKey: 'admin.nav.posters', icon: BarChart3 },`
- L23: `] as const;`
`apps/web/src/router.tsx``/admin` 子路由没有 `review`
- L64: ` { path: '/admin/login', element: <AdminLoginPage /> },`
- L65: ` { path: '/403', element: <ForbiddenPage /> },`
- L66: ` {`
- L67: ` path: '/admin',`
- L68: ` element: (`
- L69: ` <RequireAuth>`
- L70: ` <AdminLayout />`
- L71: ` </RequireAuth>`
- L72: ` ),`
- L73: ` children: [`
- L74: ` { index: true, element: <AdminDashboardPage /> },`
- L75: ` { path: 'orders', element: <AdminOrdersPage /> },`
- L76: ` { path: 'orders/:orderId', element: <AdminOrderDetailPage /> },`
- L77: ` { path: 'cases', element: <AdminCasesPage /> },`
- L78: ` { path: 'cases/:caseId', element: <AdminCaseDetailPage /> },`
- L79: ` { path: 'share-links', element: <AdminShareLinksPage /> },`
- L80: ` { path: 'share-links/:code', element: <AdminShareLinkDetailPage /> },`
- L81: ` { path: 'posters', element: <AdminPostersPage /> },`
- L82: ` { path: 'score-lines', element: <AdminScoreLinesPage /> },`
- L83: ` { path: 'rank-estimator', element: <AdminRankEstimatorPage /> },`
- L84: ` { path: 'majors', element: <AdminMajorsPage /> },`
- L85: ` { path: 'schools', element: <AdminSchoolsPage /> },`
- L86: ` { path: 'error', element: <AdminErrorPage /> },`
- L87: ` { path: '*', element: <NotFoundPage /> },`
- L88: ` ],`
静态路由对照结果:
```text
nav=['/admin', '/admin/orders', '/admin/cases', '/admin/share-links', '/admin/score-lines', '/admin/rank-estimator', '/admin/majors', '/admin/schools', '/admin/review', '/admin/posters']
admin_routes=['/admin/*', '/admin/cases', '/admin/cases/:caseId', '/admin/error', '/admin/majors', '/admin/orders', '/admin/orders/:orderId', '/admin/posters', '/admin/rank-estimator', '/admin/schools', '/admin/score-lines', '/admin/share-links', '/admin/share-links/:code']
missing=['/admin/review']
```
**影响:** 后台用户点击“复核 / Review”会进入 NotFound属于真实用户路径断裂。
**建议修复:** 新增 `/admin/review` 子路由并接入已有 Review 页面或移除导航项;新增 e2e 遍历所有 admin nav links断言不落入 NotFound。
---
### H2 · React Admin 登录仍是前端 mock未接 `/api/auth/login`
**证据:**
`apps/web/src/pages/admin/LoginPage.tsx` 只校验固定验证码 `123456`,直接写 user store
- L53: ` const onSubmit = async (values: LoginFormValues): Promise<void> => {`
- L54: ` setSubmitError(null);`
- L55: ` await new Promise((resolve) => window.setTimeout(resolve, 80));`
- L56: ``
- L57: ` if (values.code !== '123456') {`
- L58: ` setSubmitError(intl.formatMessage({ id: 'admin.login.mockCodeError' }));`
- L59: ` return;`
- L60: ` }`
- L61: ``
- L62: ` setUser({`
- L63: ` id: `admin-${values.phone.slice(-4)}`,`
- L64: ` name: intl.formatMessage({ id: 'admin.login.mockAdminName' }, { suffix: values.phone.slice(-4) }),`
- L65: ` phone: values.phone,`
- L66: ` role: 'admin',`
- L67: ` });`
- L68: ` toast.success(intl.formatMessage({ id: 'admin.login.toastSuccess' }), {`
- L69: ` description: intl.formatMessage({ id: 'admin.login.toastSuccessDescription' }),`
- L70: ` });`
- L71: ` void navigate(from, { replace: true });`
- L72: ` };`
- L102: ` <h2 className="text-2xl font-bold text-slate-950 dark:text-white">`
- L103: ` <FormattedMessage id="admin.login.title" />`
- L104: ` </h2>`
- L105: ` <p className="text-sm text-slate-500 dark:text-slate-400">`
- L106: ` <FormattedMessage id="admin.login.mockHint" />`
- L107: ` </p>`
- L132: ` <div className="relative mt-2">`
- L133: ` <LockKeyhole className="pointer-events-none absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" aria-hidden="true" />`
- L134: ` <input`
- L135: ` id="admin-code"`
- L136: ` type="text"`
- L137: ` inputMode="numeric"`
- L138: ` autoComplete="one-time-code"`
- L139: ` placeholder="123456"`
- L140: ` className="min-h-12 w-full rounded-2xl border border-slate-200 bg-white px-11 text-sm text-slate-950 shadow-sm transition placeholder:text-slate-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-slate-700 dark:bg-slate-950 dark:text-white"`
后端真实登录端点存在:`admin/routes/auth.py` 声明 `POST /api/auth/login : 用户名 + 密码 → JWT`,但 React 登录页没有调用该端点。
**影响:** 真实后端环境会出现“前端显示已登录,但后续 admin API 请求没有 JWT返回 401/403”的集成失败。
**建议修复:** 登录页改为调用 `/api/auth/login`;保存 JWT 到明确策略(优先内存/session避免长期 localStorage失败态显示真实错误`LoginPage` 集成测试和 admin e2e 登录主链路。
---
### H3 · `apiClient` 没有统一注入 `Authorization: Bearer`Admin API 契约未闭环
**证据:**
`apps/web/src/lib/api-client.ts` 只设置 Content-Type / Accept 和调用方传入 headers没有从 auth/user store 注入 Bearer
- L101: ` const init: RequestInit = {`
- L102: ` method,`
- L103: ` headers: {`
- L104: ` 'Content-Type': 'application/json',`
- L105: ` Accept: 'application/json',`
- L106: ` ...headers,`
- L107: ` },`
- L108: ` signal,`
- L109: ` };`
- L110: ``
- L111: ` if (body !== undefined) {`
- L112: ` init.body = JSON.stringify(body);`
- L113: ` }`
- L114: ``
- L115: ` if (isWriteMethod(method)) {`
- L116: ` await waitUntilOnline(signal);`
- L117: ` }`
- L118: ``
- L119: ` const res = await fetch(`${BASE_URL}${path}`, init);`
- L120: ``
- L155: `export const apiClient = {`
- L156: ` get: <T>(path: string, schema: ZodType<T, ZodTypeDef, unknown>, signal?: AbortSignal): Promise<T> =>`
- L157: ` request(path, schema, { method: 'GET', signal }),`
- L158: ` post: <T, B = unknown>(path: string, body: B, schema: ZodType<T, ZodTypeDef, unknown>, signal?: AbortSignal): Promise<T> =>`
- L159: ` request(path, schema, { method: 'POST', body, signal }),`
- L160: ` put: <T, B = unknown>(path: string, body: B, schema: ZodType<T, ZodTypeDef, unknown>, signal?: AbortSignal): Promise<T> =>`
- L161: ` request(path, schema, { method: 'PUT', body, signal }),`
- L162: ` patch: <T, B = unknown>(path: string, body: B, schema: ZodType<T, ZodTypeDef, unknown>, signal?: AbortSignal): Promise<T> =>`
- L163: ` request(path, schema, { method: 'PATCH', body, signal }),`
- L164: ` delete: <T>(path: string, schema: ZodType<T, ZodTypeDef, unknown>, signal?: AbortSignal): Promise<T> =>`
- L165: ` request(path, schema, { method: 'DELETE', signal }),`
- L166: `};`
后端 `admin/auth.py` 依赖 `Authorization: Bearer`,还支持 URL query `t=` fallback
- L115: ` credentials: Optional[HTTPAuthorizationCredentials] = Depends(_BEARER_SCHEME),`
- L116: ` settings: Settings = Depends(get_settings),`
- L117: `) -> AdminUser:`
- L118: ` """FastAPI 依赖:从 Authorization: Bearer *** JWT,返回 AdminUser。`
- L119: ``
- L120: ` 缺失/无效/过期一律 401 (走业务错误码 E012xx 系列).`
- L121: ` 支持从 URL query 参数 ``t`` 获取 token仅用于管理后台 Web 页面场景)。`
- L122: ` """`
- L123: ` raw_token: str | None = None`
- L124: ` if credentials is not None and credentials.scheme.lower() == "bearer":`
- L125: ` raw_token = credentials.credentials`
- L126: ` # fallback: URL query 参数 t管理后台 Web 登录页跳转场景)`
- L127: ` if raw_token is None:`
- L128: ` query_token = request.query_params.get("t")`
- L129: ` if query_token:`
- L130: ` raw_token = query_token`
- L131: ` if raw_token is None:`
- L132: ` raise BusinessError(`
- L133: ` AUTH_TOKEN_INVALID, detail={"reason": "missing bearer token"}`
**影响:** 即使 H2 修复了登录,如果 apiClient 不统一注入 token后台订单/案例/分享等受保护 API 仍会失败;若继续依赖 URL `?t=`token 容易进入浏览器历史、日志或 Referer。
**建议修复:** 建立 `authStore` / token providerapiClient 统一注入 Authorization逐步减少 URL token fallback 的使用范围,仅保留后台 Web 兼容场景并加安全注释和测试。
---
### H4 · Python 总门禁 mypy 当前失败,不能宣称后端质量门禁通过
**证据:** `scripts/dev-verify.sh` 会运行 `python -m mypy .`
- L87: ` log "running pytest with coverage gate"`
- L88: ` # Single source of truth threshold: matches scripts/check_coverage_gate.py`
- L89: ` if [[ "${SKIP_PRE_EXISTING}" == "1" ]]; then`
- L90: ` log "skip pre-existing failures: --skip-pre-existing"`
- L91: ` for node in "${PRE_EXISTING_IGNORES[@]}"; do`
- L92: ` PYTEST_IGNORE_ARGS+=("--deselect" "$node")`
- L93: ` done`
- L94: ` fi`
- L95: ` python -m pytest admin/tests tests data \`
- L96: ` --ignore=.venv \`
- L97: ` --ignore=.worktrees \`
- L98: ` --cov=admin \`
- L99: ` --cov=data \`
- L100: ` --cov=skills \`
- L101: ` --cov-report=term-missing \`
- L102: ` --cov-report=xml \`
- L103: ` --cov-fail-under=80 \`
- L104: ` -q \`
- L105: ` "${PYTEST_IGNORE_ARGS[@]}"`
- L106: ``
- L107: ` log "running core coverage verifier"`
- L108: ` python scripts/check_coverage_gate.py coverage.xml`
- L109: ``
- L110: ` log "running ruff"`
- L111: ` python -m ruff check . --exclude .venv,.worktrees`
- L112: ``
- L113: ` log "running mypy"`
- L114: ` python -m mypy .`
- L115: ``
- L116: ` log "crowd_db quality summary (防漂移监控)"`
- L117: ` python -m data.crowd_db.quality_summary --human`
- L118: ``
- L119: ` # P1-7/P1-8: 100-case smoke 作为独立验证步骤,失败不阻断核心门禁`
- L120: ` log "running 100-case smoke e2e (non-blocking)"`
- L121: ` python scripts/score_range_fullchain_100_e2e.py --batch smoke || log "WARN: 100-case smoke e2e failed (non-blocking, see /tmp/score-range-fullchain-100-e2e.log)"`
本轮执行结果显示 mypy 有 9 个错误,涉及 `data/share/poster.py` 等文件:
```text
admin/routes/sprint3_api.py:292: error: Incompatible types in assignment (expression has type "str", variable has type "Literal['pending', 'in_progress', 'approved', 'rejected', 'changes_requested']") [assignment]
data/cli_compat_share.py:43: error: Item "None" of "_ArgumentGroup | None" has no attribute "_group_actions" [union-attr]
data/cli_compat_share.py:44: error: Item "Action" of "Action | Any" has no attribute "add_parser" [union-attr]
data/share/poster.py:123: error: Argument "candidate_name" to "PosterPayload" has incompatible type "str | None"; expected "str" [arg-type]
data/share/poster.py:273: error: Incompatible return value type (got "FreeTypeFont", expected "ImageFont") [return-value]
data/share/poster.py:274: error: Incompatible return value type (got "FreeTypeFont | ImageFont", expected "ImageFont") [return-value]
data/share/poster.py:319: error: Incompatible return value type (got "float", expected "int") [return-value]
data/share/poster.py:324: error: Incompatible return value type (got "float", expected "int") [return-value]
data/share/poster.py:329: error: Library stubs not installed for "qrcode" [import-untyped]
Found 9 errors in 3 files (checked 268 source files)
DEV_VERIFY_EXIT=1
```
**影响:** 类型门禁失败会影响 poster/share 相关链路长期可维护性,也说明 CI clean env 可能无法稳定通过。
**建议修复:**`data/share/poster.py` 返回类型;安装/声明 `types-qrcode` 或在 mypy 配置中有边界地忽略;修后复跑 `bash scripts/dev-verify.sh`
---
---
## 3. MEDIUM Findings
### M0 · 前端本地门禁曾因依赖缺失不可执行;已补装依赖并 fresh gate 通过,但应沉淀环境准备要求
**证据:** 首轮 `/tmp/gaokao-review-gates-20260705.log` 显示 `apps/web/node_modules missing``turbo: not found`;补执行 `/tmp/gaokao-frontend-gates-20260705.log` 后,前端 gate 结果为 **PASS**
```text
=== FRONTEND FRESH GATES START ===
--- pnpm install ---
root node_modules exists
apps/web/node_modules exists
--- web typecheck ---
--- web lint ---
--- web test ---
--- web build ---
Tasks: 2 successful, 2 total
Tasks: 1 successful, 1 total
@gaokao/web:build: TOTAL │ 1.36 MB │ 393.60 KB │ ✅
@gaokao/web:build: ✅ T-B-25 验证通过
=== FRONTEND FRESH GATES END ===
```
**影响:** 该问题当前不再是代码质量 blocker但说明本地 review/交接流程需要先执行依赖安装,否则容易把“环境未准备”误报为“前端 gate 失败”。
**建议修复:** 在 review runbook / README 的前端 gate 前置条件中加入 `pnpm install --frozen-lockfile`,并在 CI/本地脚本中显式检查 `turbo` 可用后再运行 gate。
---
## 3. MEDIUM Findings
### M1 · Chromatic job 无 token 条件保护,可能阻断 CI
**证据:** `.github/workflows/web-ci.yml` 直接使用 secret
- L96: ` chromatic:`
- L97: ` runs-on: ubuntu-latest`
- L98: ` needs: ci`
- L99: ` timeout-minutes: 15`
- L100: ` if: github.event_name == 'push' || github.event_name == 'pull_request'`
- L101: ` steps:`
- L102: ` - uses: actions/checkout@v4`
- L103: ` with:`
- L104: ` fetch-depth: 0`
- L105: ``
**影响:** 未配置 `CHROMATIC_TOKEN`push/PR 可能被 Chromatic 阻断;这与“外部 token 待配置”的状态口径不一致。
**建议修复:** 给 chromatic job 增加 secret 存在性条件或降级为非阻塞;在报告中明确 Chromatic 是否为 release blocker。
---
### M2 · LHCI 配置与启动口径仍有漂移风险
**证据:** workflow 使用 treosh action + `serverUrl: 8080`,而 lighthouserc 注释仍写“vite preview 启动8081
`.github/workflows/web-ci.yml`
- L106: ` - name: Setup pnpm`
- L107: ` uses: pnpm/action-setup@v4`
- L108: ` with:`
- L109: ` version: 10`
- L110: ``
- L111: ` - name: Setup Node.js`
- L112: ` uses: actions/setup-node@v4`
- L113: ` with:`
- L114: ` node-version: '20'`
- L115: ` cache: 'pnpm'`
- L116: ``
- L117: ` - name: Install dependencies`
- L118: ` run: pnpm install --frozen-lockfile`
- L119: ``
- L120: ` - name: Build`
- L121: ` run: pnpm --filter @gaokao/web build`
- L122: ``
- L123: ` - name: Publish to Chromatic`
- L124: ` uses: chromaui/action@v1`
- L125: ` with:`
- L126: ` projectToken: ${{ secrets.CHROMATIC_TOKEN }}`
- L127: ` workingDir: apps/web`
- L128: ` buildScriptName: build`
- L129: ` exitZeroOnChanges: true`
- L130: ``
- L131: ` lighthouse:`
- L132: ` # G3 闸门真实化P/A/B/S 均 ≥ 90`
- L133: ` runs-on: ubuntu-latest`
- L134: ` needs: ci`
- L135: ` timeout-minutes: 15`
- L136: ` if: github.event_name == 'push' || github.event_name == 'pull_request'`
- L137: ` steps:`
- L138: ` - uses: actions/checkout@v4`
- L139: ``
- L140: ` - name: Setup pnpm`
- L141: ` uses: pnpm/action-setup@v4`
- L142: ``
- L143: ` - name: Setup Node.js`
- L144: ` uses: actions/setup-node@v4`
- L145: ` with:`
- L146: ` node-version: '20'`
- L147: ` cache: 'pnpm'`
- L148: ``
- L149: ` - name: Install dependencies`
- L150: ` run: pnpm install --frozen-lockfile`
- L151: ``
- L152: ` - name: Build`
- L153: ` run: pnpm --filter @gaokao/web build`
- L154: ``
- L155: ` - name: Run Lighthouse CI (G3 闸门P/A/B/S ≥ 90)`
- L156: ` uses: treosh/lighthouse-ci-action@v12`
- L157: ` with:`
- L158: ` configPath: ./apps/web/lighthouserc.cjs`
- L159: ` uploadArtifacts: true`
- L160: ` temporaryPublicStorage: true`
- L161: ` runs: 3`
- L162: ` serverUrl: http://127.0.0.1:8080/`
- L163: ` # treosh action 自动启动 vite preview @ 8080`
- L164: ` url: |`
- L165: ` http://127.0.0.1:8080/`
- L166: ` http://127.0.0.1:8080/data-query`
- L167: ` http://127.0.0.1:8080/plans`
- L168: ` http://127.0.0.1:8080/about`
`apps/web/lighthouserc.cjs`
- L14: ` collect: {`
- L15: ` // 静态服务器从 vite preview 启动8081见 CI workflow`
- L16: ` url: [`
- L17: ` 'http://127.0.0.1:8080/',`
- L18: ` 'http://127.0.0.1:8080/data-query',`
- L19: ` 'http://127.0.0.1:8080/plans',`
- L20: ` 'http://127.0.0.1:8080/about',`
- L21: ` ],`
- L22: ` numberOfRuns: 3, // 取 P75median`
- L23: ` settings: {`
- L24: ` // P75 算分(默认 median = P50`
- L25: ` preset: 'desktop',`
- L26: ` chromeFlags: '--no-sandbox --headless=new',`
- L27: ` },`
- L29: ` assert: {`
- L30: ` // G3 闸门:每类 ≥ 90`
- L31: ` assertions: {`
- L32: ` 'categories:performance': ['error', { minScore: 0.9 }],`
- L33: ` 'categories:accessibility': ['error', { minScore: 0.9 }],`
- L34: ` 'categories:best-practices': ['error', { minScore: 0.9 }],`
- L35: ` 'categories:seo': ['error', { minScore: 0.9 }],`
- L36: ` },`
- L37: ` },`
- L38: ` upload: {`
- L39: ` target: 'temporary-public-storage', // 公开 storage 30 天;后续接 LHCI server`
- L40: ` },`
**影响:** LHCI 可能在 CI 上因为服务启动机制/端口口径不一致而假失败或假通过。
**建议修复:** 在 LHCI 配置或 workflow 中显式指定 preview 启动命令、ready pattern、端口修正文档注释CI 上保留 artifact。
---
### M3 · `scripts/dev-verify.sh` 将 100-case smoke 设为 non-blocking主链路回归可能被 warning 化
**证据:**
- L87: ` log "running pytest with coverage gate"`
- L88: ` # Single source of truth threshold: matches scripts/check_coverage_gate.py`
- L89: ` if [[ "${SKIP_PRE_EXISTING}" == "1" ]]; then`
- L90: ` log "skip pre-existing failures: --skip-pre-existing"`
- L91: ` for node in "${PRE_EXISTING_IGNORES[@]}"; do`
- L92: ` PYTEST_IGNORE_ARGS+=("--deselect" "$node")`
- L93: ` done`
- L94: ` fi`
- L95: ` python -m pytest admin/tests tests data \`
- L96: ` --ignore=.venv \`
- L97: ` --ignore=.worktrees \`
- L98: ` --cov=admin \`
- L99: ` --cov=data \`
- L100: ` --cov=skills \`
- L101: ` --cov-report=term-missing \`
- L102: ` --cov-report=xml \`
- L103: ` --cov-fail-under=80 \`
- L104: ` -q \`
- L105: ` "${PYTEST_IGNORE_ARGS[@]}"`
- L106: ``
- L107: ` log "running core coverage verifier"`
- L108: ` python scripts/check_coverage_gate.py coverage.xml`
- L109: ``
- L110: ` log "running ruff"`
- L111: ` python -m ruff check . --exclude .venv,.worktrees`
- L112: ``
- L113: ` log "running mypy"`
- L114: ` python -m mypy .`
- L115: ``
- L116: ` log "crowd_db quality summary (防漂移监控)"`
- L117: ` python -m data.crowd_db.quality_summary --human`
- L118: ``
- L119: ` # P1-7/P1-8: 100-case smoke 作为独立验证步骤,失败不阻断核心门禁`
- L120: ` log "running 100-case smoke e2e (non-blocking)"`
- L121: ` python scripts/score_range_fullchain_100_e2e.py --batch smoke || log "WARN: 100-case smoke e2e failed (non-blocking, see /tmp/score-range-fullchain-100-e2e.log)"`
第 119-121 行说明 100-case smoke 失败不阻断核心门禁。
**影响:** 对当前高信任付费/志愿服务项目100-case fullchain smoke 更接近业务真实回归;长期 non-blocking 可能掩盖主链路问题。
**建议修复:** 至少把 smoke 结果拆为 `核心门禁 PASS/FAIL``业务主链路 PASS/FAIL` 两个明确状态;在 release gate 中设置必过子集。
---
## 4. LOW / 文档真相风险
### L1 · 历史 review 与当前 review 容易混用
仓库内存在多个历史 review`reports/REVIEW_REPORT_V10_FRONTEND_2026-07-05.md``REVIEW_REPORT_2026-07-02_SENIOR_DEVELOPER.md``STRICT_SYSTEM_REVIEW_*` 等。历史报告仍有价值,但不应替代当前 HEAD + 当前门禁结果。
**建议:** 将本报告作为 2026-07-05 当前全面 review 真相入口;旧报告顶部应逐步加“历史快照”提示或 CURRENT REVIEW 指针。
### L2 · 旧截图报告产物仍在仓库中,易被误当当前前端验收材料
`reports/user_simulation_2026_06_20/` 仍包含旧截图和 captures.json。上一轮已避免把这些旧产物合并进新提交本轮 review 继续建议把它们标注为历史材料,不作为 V10 当前验收证据。
---
## 5. 已验证不是问题 / 当前正向事实
- `apps/web/src/types/api-generated.d.ts``apps/web/src/schemas/api-generated.ts``any_count=0`
```text
apps/web/src/types/api-generated.d.ts 0
apps/web/src/schemas/api-generated.ts 0
```
- `admin/routes/health.py` 当前 dev readiness 逻辑可解释:
- L65: `def _check_settings_valid(settings: Settings) -> bool:`
- L66: ` """检查 prod fail-closed 通过 (JWT + admin password + payment)。`
- L67: ``
- L68: ` dev 环境允许占位密钥,仅 prod 环境强制安全密钥。`
- L69: ` """`
- L70: ` if settings.env == "dev":`
- L71: ` # dev 环境允许占位密钥,不做 fail-closed`
- L72: ` return True`
- L73: ` secure, _ = is_jwt_secret_secure(settings)`
- L74: ` return secure`
- L75: ``
- L76: ``
- L77: `@router.get("/health", summary="健康检查")`
- L78: `def health(settings: Settings = Depends(get_settings_dep)) -> JSONResponse:`
- L79: ` """公开端点。只返回 readiness, 不暴露环境/路径/版本细节。`
- L80: ``
- L81: ` 返回结构:`
- L82: ` - status: "ok" 或 "degraded"(任一 readiness 检查失败时降级)`
- L83: ` - checks: {db_writable, disk_writable, settings_valid} 子对象`
- L84: ``
- L85: ` readiness 语义2026-06-27 P1-4 修复):`
- L86: ` - 所有 checks 通过 → status="ok", HTTP 200`
- L87: ` - 任一 check 失败 → status="degraded", HTTP 503`
- L88: ` - K8s/systemd readiness probe 应判 HTTP status不只判 status 字段`
- L89: ` """`
- L90: ` checks = {`
- L91: ` "db_writable": _check_db_writable(settings),`
- L92: ` "disk_writable": _check_disk_writable(settings),`
- L93: ` "settings_valid": _check_settings_valid(settings),`
- L94: ` }`
- L95: ` all_ok = all(checks.values())`
- L96: ` return JSONResponse(`
- L97: ` status_code=200 if all_ok else 503,`
- L98: ` content={`
- L99: ` "status": "ok" if all_ok else "degraded",`
- L100: ` "checks": checks,`
- L101: ` },`
- 静态扫描未发现 Python 裸 `except:`
---
## 6. 建议整改优先级
### P0 / 立即阻断生产完成声明
1. 修复 H1 `/admin/review` 断链并补全 admin nav e2e。
2. 修复 H2/H3React Admin 真实登录 + JWT 注入 + auth 状态测试。
3. 修复 H4mypy 9 errors复跑 `scripts/dev-verify.sh` 到 green。
4. 前端依赖已补装并复跑 typecheck/lint/test/build后续仍需补 Playwright e2e / LHCI / Chromatic fresh evidence。
### P1 / CI 与验收口径收敛
1. Chromatic token 缺失时不阻断或明确设为必配 release gate。
2. LHCI preview 启动和端口口径统一。
3. 100-case smoke 的 release gate 语义升级,不再只作为 warning。
### P2 / 文档与证据治理
1. 给历史 review 报告加当前真相入口。
2. 标注旧截图/旧 user simulation 产物为历史材料。
---
## 7. 本轮命令与证据索引
```text
## main
4059f144c49398aec2fcc3040e639e1312061086
4059f14 (HEAD -> main, tksea/main, origin/main, gitea/main) fix(health): allow dev placeholder readiness
da99dec docs: add systemic frontend review findings
5d52e07 docs: update frontend review status after e2e fixes
```
本轮主要证据:
- 本地 gate 日志:`/tmp/gaokao-review-gates-20260705.log`
- 当前报告:`reports/REVIEW_REPORT_2026-07-05_COMPREHENSIVE_PROJECT_REVIEW.md`
- 核查文件:
- `apps/web/src/layouts/AdminLayout.tsx`
- `apps/web/src/router.tsx`
- `apps/web/src/pages/admin/LoginPage.tsx`
- `apps/web/src/lib/api-client.ts`
- `.github/workflows/web-ci.yml`
- `apps/web/lighthouserc.cjs`
- `scripts/dev-verify.sh`
- `admin/routes/health.py`
- `admin/auth.py`
---
## 8. 最终判断
**不能宣称项目整体生产级完成。**
可以宣称:
- 本轮已完成一次当前 HEAD 的多维真实 review。
- 已发现并固化当前有效问题到今日 review 报告。
- 当前最大阻断不是“未知”而是明确集中在后台真实鉴权闭环、admin nav 路由完整性、Python 类型门禁、Playwright/LHCI/Chromatic 与真实视觉验收 fresh evidence、CI 外部服务口径。
仍不能宣称:
- 前端 V10 全部门禁(含 Playwright e2e / LHCI / Chromatic / 真实视觉验收)当前全部通过。
- Admin 真实 JWT 登录已接通。
- CI/LHCI/Chromatic 生产级验收闭环。
- 线上真实支付/真实域名/真实用户流量验收完成。