From f6a25958051f7d931f623eabc1654c3c473adb1c Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 24 Aug 2026 11:25:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9A=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=85=A8=E9=A1=B9=E7=9B=AE=E5=B7=A5=E7=A8=8B=E4=BD=93=E6=A3=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .codemap/codemap.html | 634 +++++++++++++++++ .codemap/codemap.md | 242 +++++++ .codemap/config.json | 8 + .codemap/modules.json | 1409 ++++++++++++++++++++++++++++++++++++++ .gitignore | 6 + AUDIT_TASK.md | 53 ++ DEVELOPMENT_LOG.md | 12 + NEXT_STEPS.md | 15 + PROJECT_AUDIT.md | 750 ++++++++++++++++++++ sonar-project.properties | 14 + 10 files changed, 3143 insertions(+) create mode 100644 .codemap/codemap.html create mode 100644 .codemap/codemap.md create mode 100644 .codemap/config.json create mode 100644 .codemap/modules.json create mode 100644 AUDIT_TASK.md create mode 100644 PROJECT_AUDIT.md create mode 100644 sonar-project.properties diff --git a/.codemap/codemap.html b/.codemap/codemap.html new file mode 100644 index 0000000..e3af82e --- /dev/null +++ b/.codemap/codemap.html @@ -0,0 +1,634 @@ + + + + + + +Functional Architecture Map + + + +
+
+
+
+

Functional Architecture Map

+
+
+
+ + + + + + + + +
+
+
+ + + + + + +
+
click a module · scroll to pan
+
+ +
+ + + + + diff --git a/.codemap/codemap.md b/.codemap/codemap.md new file mode 100644 index 0000000..0bdd3c3 --- /dev/null +++ b/.codemap/codemap.md @@ -0,0 +1,242 @@ + + +# NiuMa Studio — Functional Module Quality Audit + +> **Interactive view:** [`.codemap/codemap.html`](codemap.html) — per-module scores, findings, LoC, and the dependency graph. This file is the written report. + +**Generated:** 2026-08-24 · **Modules:** 13 · **Size:** 47576 tracked LoC across 131 files + +## Health by layer + +| Layer | Modules | Avg score | +|---|--:|--:| +| 界面 · API | 2 | 64 | +| 业务编排 | 3 | 57 | +| 媒体与 AI 处理 | 4 | 65 | +| 外部执行边界 | 2 | 62 | +| 持久化与运维 | 2 | 66 | + +## Per-module lines of code & score + +_LoC is the representative file/folder per module; folder-level modules overlap and are not additive._ + +### 界面 · API + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| Frontend UI | 13,840 | 65 C | god-component, bloat, glue, duplication, legacy | +| API & Runtime | 2,713 | 63 C | fallback, legacy, dual-format, duplication, glue, bloat, god-component, placeholder, silent-except, monkeypatch, over-fit, any-escape | + +### 业务编排 + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| Publish Center | 5,561 | 52 D | god-component, bloat, legacy, dual-format, fallback, silent-except, placeholder, duplication | +| Task Review & Cut | 2,436 | 61 C | god-component, glue, duplication, dual-format, fallback, legacy, over-fit, silent-except | +| Pipeline & Job Queue | 1,745 | 58 D | fallback, silent-except, legacy, dual-format, stub, god-component, glue, over-fit | + +### 媒体与 AI 处理 + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| AI Selection | 4,420 | 63 C | fallback, silent-except, legacy, dual-format, stub, fake-output, bloat, duplication, god-component | +| Subtitle | 2,391 | 70 C | fallback, silent-except, legacy, bloat, god-component, monkeypatch | +| Transcription | 1,930 | 58 D | over-fit, fallback, fake-output, dual-format, silent-except, duplication, god-component, glue | +| Media & Storage | 1,300 | 69 C | fallback, silent-except, legacy, dual-format, bloat, glue | + +### 外部执行边界 + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| Publishers & Worker | 2,116 | 62 C | any-escape, fallback, legacy, dual-format, glue, god-component, duplication | +| Publish Scheduler | 1,754 | 62 C | god-component, bloat, glue, fallback, legacy, silent-except | + +### 持久化与运维 + +| Module | LoC | Score | Tags | +|---|--:|:--|:--| +| Ops & Delivery | 4,347 | 68 C | fallback, legacy, dual-format, duplication, bloat, glue, silent-except, monkeypatch | +| SQLite Persistence | 3,023 | 63 C | silent-except, fallback, legacy, dual-format, glue, bloat, god-component, monkeypatch | + +## Worst offenders + +- **Publish Center (52/D)** — app/services/publish_service.py:564: 配置/账号归一化仅新增 masked 字段却保留原始 Secret/Token,GET 接口直接返回。 +- **Transcription (58/D)** — app/services/transcription_checkpoint_service.py:22: 文件指纹只哈希大小与首尾各 1MiB;大文件中部变化但首尾/大小不变会错误复用旧转写 checkpoint。 +- **Pipeline & Job Queue (58/D)** — app/services/job_service.py:329: checkpoint、progress 与终态更新只按 job_id,不校验 lease_owner/status;旧 worker 可覆盖新 attempt。 +- **Task Review & Cut (61/C)** — tests/test_task_query_service.py:175: 测试清理函数无条件 DELETE 多张核心整表,fixture 每个用例前后执行;若继承真实数据库路径会清空真实任务与发布记录。 +- **Publish Scheduler (62/C)** — app/services/publish_repository.py:54: 发布结果只按 job_id 写回,无 execution/worker/claim 代际条件;旧执行可覆盖新 claim 终态。 +- **Publishers & Worker (62/C)** — scripts/publish_host_worker.py:74: execution_id 直接拼 journal 路径且仅限长度,../、分隔符或盘符可逃逸 Worker 状态目录,读写异常 JSON。 +- **API & Runtime (63/C)** — app/main.py:97: API 写保护仅在 LOCAL_ADMIN_TOKEN 非空时启用;默认 Token 为空。若端口可被局域网访问,写 API 默认无认证。 +- **AI Selection (63/C)** — app/services/ai_config_service.py:318: get_ai_config_context 返回含 AI/ASR Key 的完整 values,GET /api/settings/ai 无读取鉴权,页面也复用该上下文。 +- **SQLite Persistence (63/C)** — app/db/database.py:22: 活动库 foreign_key_check 已有 17 条违规:16 条 publish_jobs、1 条 subtitle_jobs 指向不存在的 output_clip。 +- **Frontend UI (65/C)** — app/static/js/app.js:211: 多个写请求绕过统一 apiFetch;启用 LOCAL_ADMIN_TOKEN 时可能缺失 Authorization 并被 API 拒绝。 + +## All findings + +### HIGH (25) + +- **Frontend UI** · `app/static/js/app.js:211` — 多个写请求绕过统一 apiFetch;启用 LOCAL_ADMIN_TOKEN 时可能缺失 Authorization 并被 API 拒绝。 +- **Frontend UI** · `app/static/js/app.js:1729` — 接口或任务数据直接拼接到 innerHTML;publish-center.js:2152 有同类路径,存在本地 DOM XSS/页面结构破坏风险。 +- **Media & Storage** · `app/services/storage_service.py:178` — _storage_path_from_dir_name 直接把 PureWindowsPath(task_dir_name) 的 parts 拼接到 tasks_dir,未复用 _safe_relative_parts;核心写入/读取原语缺少 traversal 覆盖。 +- **Media & Storage** · `app/services/storage_service.py:218` — allocate_task_dir_name 采用查询数据库+检查目录+返回的非原子分配,且无明确唯一约束/独占创建;并发上传或重试可能获得同一目录名。 +- **Media & Storage** · `app/services/storage_service.py:302` — resolve_video_file_path 对已存在路径直接原样返回;媒体接口只检查 exists/is_file 即 FileResponse,异常数据库路径可能暴露允许根目录外的本地文件。 +- **Transcription** · `app/services/transcription_checkpoint_service.py:22` — 文件指纹只哈希大小与首尾各 1MiB;大文件中部变化但首尾/大小不变会错误复用旧转写 checkpoint。 +- **Transcription** · `app/services/transcript_service.py:260` — 捕获所有 Exception 会把 TranscriptCancelledError 包成 RuntimeError,取消可能被标记 failed 而非 cancelled。 +- **Transcription** · `app/services/transcript_workflow_service.py:318` — transcript.md 存在即返回 completed,不校验源指纹或最近生成是否成功;失败后旧转写可能被误当当前结果。 +- **AI Selection** · `app/services/ai_config_service.py:318` — get_ai_config_context 返回含 AI/ASR Key 的完整 values,GET /api/settings/ai 无读取鉴权,页面也复用该上下文。 +- **Task Review & Cut** · `tests/test_task_query_service.py:175` — 测试清理函数无条件 DELETE 多张核心整表,fixture 每个用例前后执行;若继承真实数据库路径会清空真实任务与发布记录。 +- **Task Review & Cut** · `app/services/video_cut_workflow_service.py:248` — 每个切片记录独立提交;中途异常时前项已提交但 cut_run 未失败,留下半成功状态。 +- **Task Review & Cut** · `app/services/task_lifecycle_service.py:152` — 状态更新无合法转移和产物前置条件校验,API 可把空任务直接标记完成。 +- **Task Review & Cut** · `app/services/task_service.py:451` — ffprobe 调用无 timeout 且未完整捕获 IO 异常,NAS/损坏媒体可令详情请求阻塞或 500。 +- **Pipeline & Job Queue** · `app/services/job_service.py:329` — checkpoint、progress 与终态更新只按 job_id,不校验 lease_owner/status;旧 worker 可覆盖新 attempt。 +- **Pipeline & Job Queue** · `app/services/job_worker.py:38` — already_claimed 跳过 claim 且不核对当前 owner,已被接管的旧子进程仍可继续执行写结果。 +- **Pipeline & Job Queue** · `app/services/job_service.py:287` — claim_next_job 可把达到 max_attempts 的 running 任务直接失败而不要求 lease 过期,慢 worker 可能被误判。 +- **Pipeline & Job Queue** · `app/services/pipeline_engine.py:118` — 取消只在 step 前检查,step 内不中断;取消后可能显示失败/完成且副作用无回滚。 +- **Pipeline & Job Queue** · `app/services/pipeline_engine.py:158` — READY_TO_PUBLISH 写入后同一 run 立即写 COMPLETED,人工确认语义成为不可稳定观察的瞬态。 +- **Publish Center** · `app/services/publish_service.py:564` — 配置/账号归一化仅新增 masked 字段却保留原始 Secret/Token,GET 接口直接返回。 +- **Publish Scheduler** · `app/services/publish_repository.py:54` — 发布结果只按 job_id 写回,无 execution/worker/claim 代际条件;旧执行可覆盖新 claim 终态。 +- **Publish Scheduler** · `app/services/publish_scheduler.py:495` — repair_and_publish 检查与克隆间无状态锁;并发确认/重复点击可在源任务已发布后仍新建排期。 +- **Publishers & Worker** · `scripts/publish_host_worker.py:74` — execution_id 直接拼 journal 路径且仅限长度,../、分隔符或盘符可逃逸 Worker 状态目录,读写异常 JSON。 +- **Publishers & Worker** · `scripts/publish_host_worker.py:264` — 同 execution_id 不检查既有终态即重新发布;实例内锁无法协调同 ID 并发/重放,可能重复投稿。 +- **SQLite Persistence** · `app/db/database.py:22` — 活动库 foreign_key_check 已有 17 条违规:16 条 publish_jobs、1 条 subtitle_jobs 指向不存在的 output_clip。 +- **Ops & Delivery** · `scripts/migrate_task_dirs_to_project_names.py:211` — 任务目录迁移用非 WAL-aware 主库 copy,先移动目录再统一更新提交,无文件补偿,异常会让路径/DB 不一致。 + +### MED (82) + +- **Frontend UI** · `app/templates/system_status.html:98` — 配置/API Key 字段进入 DOM,base.html:11 还承载本地管理 Token;需确认全链路始终掩码。 +- **Frontend UI** · `app/static/css/styles.css:870` — 使用多个未在 :root 定义的 CSS 自定义属性,相关声明可能失效。 +- **Frontend UI** · `app/static/js/app.js:1` — 任务、转写、AI、审核、切片、字幕和配置行为集中在超大全局脚本,回归半径较大。 +- **Frontend UI** · `tests/test_publish_center_browser.py:16` — Playwright 缺失时核心浏览器测试可静默跳过,字幕交互与鉴权失败路径覆盖不足。 +- **API & Runtime** · `app/main.py:97` — API 写保护仅在 LOCAL_ADMIN_TOKEN 非空时启用;默认 Token 为空。若端口可被局域网访问,写 API 默认无认证。 +- **API & Runtime** · `app/routers/tasks.py:220` — 多个 async 路由直接调用同步 FFmpeg/文件长任务,可能阻塞 FastAPI 事件循环;项目另有持久化异步 Job 路径。 +- **API & Runtime** · `app/models/task.py:7` — TaskStatus 同时定义大写自动流水线与小写手动状态,API 可直接提交枚举状态但不表达合法状态迁移。 +- **Media & Storage** · `app/services/storage_service.py:213` — 读取已有任务目录遇 sqlite3.Error 静默回空集合/None,数据库锁定或损坏会被伪装成无目录并走错误 fallback。 +- **Media & Storage** · `app/services/storage_service.py:533` — 清理计划逐个 rmtree,而调用方随后才更新数据库;后续失败时已删文件无法随数据库回滚,形成半删除。 +- **Media & Storage** · `app/services/storage_service.py:557` — move_task_directory_to_trash 复用未校验的目录拼接;虽当前无运行调用,但保留未来误用风险。 +- **Media & Storage** · `app/services/managed_process_service.py:18` — Windows taskkill 返回码/超时异常被忽略,未确认进程树退出;上层可能释放租约但 FFmpeg 仍继续。 +- **Media & Storage** · `tests/test_path_resolution.py:44` — 所谓唯一命名测试未写数据库或制造同名目录,未验证并发/冲突核心行为。 +- **Transcription** · `app/services/transcript_service.py:552` — 远程响应无 utterances 时把 text/message 包成 0-1 秒片段,错误 message 可能被当成转写正文。 +- **Transcription** · `app/services/transcript_workflow_service.py:25` — 运行/取消状态仅用进程级集合且 check-then-add 非原子,多进程或重启可能重复转写且取消失联。 +- **Transcription** · `app/services/transcript_service.py:831` — 进度文件读失败/JSON 损坏时静默返回空字典,隐藏损坏并可能触发重跑。 +- **Transcription** · `app/services/transcript_service.py:316` — 本地与火山转写各自复制分块、checkpoint、进度和错误循环,行为易漂移。 +- **Transcription** · `app/services/transcript_service.py:83` — 单文件混合 FFmpeg、Provider、分块、checkpoint、导出与读取,职责和回归面过大。 +- **Transcription** · `app/services/transcript_service.py:68` — 活动 Provider/模型/设备为进程级可变全局,并发任务可能互相覆盖进度元数据。 +- **AI Selection** · `app/services/ai/ai_clip_service.py:4` — generate_candidate_clips_placeholder 返回三条硬编码 ClipCandidate;虽当前无调用方,误接线会产生 fake output。 +- **AI Selection** · `app/services/ai/ai_clip_analyzer.py:131` — 分段分析失败窗口被跳过,只要其他窗口有候选便返回;通用路径缺少最低覆盖率门槛。 +- **AI Selection** · `app/services/ai/variety_comedy_analyzer.py:513` — 全局评审失败时隐式降级到扩展阶段评分,Provider 失败仍可形成候选。 +- **AI Selection** · `app/services/ai/local_model_provider.py:19` — fallback_protocol 对任意 AIProviderError 都执行第二协议,未区分限流、认证和协议错误,可能重复调用与计费。 +- **AI Selection** · `app/services/ai_analysis_workflow_service.py:738` — 候选事务替换、JSON 文件和 run 历史分步持久化,后续失败会形成数据库与文件/历史不一致。 +- **AI Selection** · `app/services/ai/long_live_talk_analyzer.py:427` — checkpoint 更新无 run/lease/owner fencing,并发或旧进程恢复可能互相覆盖窗口状态。 +- **AI Selection** · `app/services/ai_analysis_workflow_service.py:71` — 分析元文件不存在、损坏或读取异常时静默返回空字典,无法区分未分析与产物损坏。 +- **AI Selection** · `app/services/ai_analysis_workflow_service.py:685` — 单文件集中 profile、Provider、状态、候选、文件、历史与恢复,模块回归半径大。 +- **AI Selection** · `app/services/ai/ai_clip_analyzer.py:252` — 多个分析器重复偏好摘要、时间转换、默认字段和 AI 输出解析逻辑。 +- **AI Selection** · `tests/test_split_services.py:200` — 缺少真实 Provider 成功路径、文件写入失败一致性、并发运行和旧 checkpoint 覆盖测试。 +- **AI Selection** · `tests/test_codex_cli_provider.py:12` — Provider 测试主要 monkeypatch,缺少 HTTP 429/500/超时和协议 fallback 重复调用边界。 +- **Task Review & Cut** · `app/services/task_service.py:14` — task_service 集中导入多领域私有函数并与 task_query_service 反向依赖,职责重叠、回归半径大。 +- **Task Review & Cut** · `app/services/task_query_service.py:134` — 批量 output clip 逻辑复制 task_service 中的路径、字幕与发布就绪判断,口径易漂移。 +- **Task Review & Cut** · `app/services/task_service.py:717` — 单条候选异常时间格式即可让整个候选列表抛错,缺少逐条坏数据隔离。 +- **Task Review & Cut** · `app/services/task_service.py:587` — get_task 不过滤 is_deleted,已删除但保留历史的任务仍可被详情和动作接口读取。 +- **Task Review & Cut** · `app/services/video_cut_workflow_service.py:18` — MAX(run_number) 与 INSERT 分离且无唯一约束,并发重切可分配重复版本号。 +- **Task Review & Cut** · `app/services/video_cut_workflow_service.py:274` — 发布同步异常返回 partial,但任务主状态已先标完成,恢复含义分散。 +- **Subtitle** · `app/services/subtitle_data_service.py:352` — 手工 revision 的 active/base 检查在事务外,并发编辑可同时通过并让后提交者覆盖 active 选择。 +- **Subtitle** · `app/services/subtitle_data_service.py:489` — 批准 revision 在事务外校验,后续按 id 无条件批准激活;并发/重放旧请求可回退 active 版本。 +- **Subtitle** · `app/services/subtitle_data_service.py:135` — source/clip track 均先查后插;NULL output_clip_id 唯一约束不足,并发可能重复源轨或 500。 +- **Subtitle** · `app/services/subtitle_auto_workflow_service.py:87` — 批量批准逐 clip 独立提交,统一 Job 后创建;中途失败会留下前项已批准但无队列的部分成功。 +- **Subtitle** · `app/services/subtitle_data_service.py:168` — source revision 提交后逐 clip 独立同步,任一异常形成 source 已更新而部分 clip 仍旧的混合状态。 +- **Subtitle** · `app/services/subtitle_workflow_service.py:472` — 先标 completed 再单独激活,更新缺 expected status/revision/owner 条件;迟到 worker 可激活旧成片。 +- **Subtitle** · `app/services/subtitle_data_service.py:1045` — 字幕数据与渲染服务分别聚合过多职责,修改与测试回归半径大。 +- **Subtitle** · `tests/test_subtitle_editor.py:189` — 未覆盖并发 save/approve/ensure track 竞态和批量中途失败后的部分提交。 +- **Pipeline & Job Queue** · `app/services/job_service.py:51` — Job JSON 损坏时静默保留原字符串,执行器随后按 dict 使用并产生不明确错误。 +- **Pipeline & Job Queue** · `app/services/job_worker.py:175` — 子进程 stdout/stderr 均丢弃,父进程只保留退出码,恢复与诊断证据不足。 +- **Pipeline & Job Queue** · `app/services/pipeline_engine.py:104` — run context 只在内存,无按 step 持久化副作用 checkpoint;崩溃重试可能重复 AI/封面/排期动作。 +- **Pipeline & Job Queue** · `app/services/pipeline_engine.py:143` — 异常后写 summary 仍读取源文件和多个产物,二次失败可遮蔽原始错误。 +- **Pipeline & Job Queue** · `app/services/job_service.py:226` — mark_job_running 无当前状态/owner 条件,已完成或已取消 Job 也可被内部调用重置。 +- **Pipeline & Job Queue** · `app/services/job_service.py:15` — 声明 ai_analysis/publish Job 类型但 execute_job 未实现,属于可创建不可执行的 legacy/stub。 +- **Publish Center** · `app/services/publish_service.py:4562` — 4k+ 行文件混合 CRUD、OAuth、内容、封面、同步、历史、多个 Publisher 与页面上下文,是高耦合 God Component。 +- **Publish Center** · `app/services/publish_service.py:2935` — 历史 SQL 先用大写状态过滤再规范化,旧小写 ready/published/failed 记录可能被提前排除。 +- **Publish Center** · `app/services/publish_service.py:2137` — 封面生成异常只写 item cover_error 未计入 errors,整体仍可能返回 ok,形成隐式部分成功。 +- **Publish Center** · `app/services/publish_service.py:2784` — 批量创建遇无效 output_clip_id 直接跳过且仍返回 ok,全无效时也可能显示成功创建 0 条。 +- **Publish Center** · `app/services/publish_readiness.py:167` — api_publish 可被创建且兼容入口仍在,但 readiness 正常调度统一判 unsupported,模式契约不一致。 +- **Publish Center** · `app/services/publish_providers.py:216` — 旧 API 发布超时统一 failed;若平台已接收而响应丢失,重试可能重复投稿且未进入 NEED_REVIEW。 +- **Publish Scheduler** · `app/services/publish_scheduler.py:1403` — 丢弃 create_task 引用,关闭只 stop 不 await,存在 Sonar S7502 的生命周期/优雅停机缺口。 +- **Publish Scheduler** · `app/services/publish_scheduler.py:264` — 每轮加载全部 SCHEDULED 再由 Python 判断到期,无 SQL due 条件/批量上限。 +- **Publish Scheduler** · `app/services/publish_scheduler.py:617` — 全部 PUBLISHING 任务串行查询 Worker execution;多个超时会拖长整轮扫描。 +- **Publish Scheduler** · `app/services/publish_scheduler.py:1305` — 风险 JSON 损坏时静默回空列表,可能绕过风险复核继续投稿,属于 fail-open。 +- **Publish Scheduler** · `app/services/publish_scheduler.py:961` — 人工发布 URL 只做域名子串匹配,不解析 hostname,错误域名/query 也可通过。 +- **Publish Scheduler** · `tests/test_publish_scheduler_state_machine.py:302` — 未覆盖旧 execution 回写、新旧 claim、repair 并发、后台 Task 生命周期和损坏 risk_flags。 +- **Publishers & Worker** · `scripts/publish_host_worker.py:231` — 账号操作先 check locked 再后台获取锁,存在竞态;锁仅进程内,多 Worker/重启不持久。 +- **Publishers & Worker** · `app/services/publishers/browser_runtime.py:32` — platform/account_id 直接拼浏览器 profile/artifact 路径,异常 ID 可路径穿越或造成账号目录冲突。 +- **Publishers & Worker** · `scripts/publish_host_worker.py:293` — Worker 把 result/diagnostics 原样写 journal,兼容 Provider 异常输出中的 Token/Cookie 可能长期落盘并经接口返回。 +- **Publishers & Worker** · `app/services/publishers/page_scripts.py:14` — Worker 页面脚本延迟导入超大 publish_service 私有函数,形成 legacy glue 与高回归半径。 +- **SQLite Persistence** · `app/db/database.py:31` — init_db 依赖结构探测而无 user_version/schema_migrations;中断后只能猜测并重跑部分 Schema/数据迁移。 +- **SQLite Persistence** · `app/db/database.py:644` — 索引创建失败被 sqlite.Error 静默吞掉;旧唯一索引先删除,可能在缺约束状态继续启动。 +- **SQLite Persistence** · `app/db/database.py:967` — 旧表只 ALTER 加列,不会补齐缺失 FK/UNIQUE/ON DELETE,历史约束不完整会永久保留。 +- **SQLite Persistence** · `scripts/backup_restore.py:359` — 备份验证只检查主 DB、quick_check 和四张表,不检查 FK、完整 Schema、索引或迁移版本。 +- **SQLite Persistence** · `app/services/database_backup_service.py:287` — Schema 迁移备份未把 WAL/SHM sidecar 纳入完整清理/验证;已有大量 tmp/wal/shm 运维噪声。 +- **SQLite Persistence** · `scripts/backup_restore.py:529` — restore 核心无进程锁/活动连接检查,绕过 PowerShell 包装时可在活跃 WAL 写入下替换数据库。 +- **SQLite Persistence** · `scripts/migrate_task_dirs_to_project_names.py:225` — 目录迁移先移动文件再更新 DB,无文件回滚且未复用统一连接设置,中断会使磁盘/DB 不一致。 +- **SQLite Persistence** · `tests/test_p0_security.py:352` — FK 测试不断言 foreign_keys=1 也不执行 foreign_key_check,现有 17 条违规无法被测试发现。 +- **SQLite Persistence** · `app/db/database.py:31` — database.py 聚合 Schema、迁移、索引、数据修复、种子与备份触发,回归半径覆盖全应用。 +- **Ops & Delivery** · `.github/workflows/ci.yml:3` — CI 仅监听 master push/PR;feature/docs 直接 push 不即时验证,问题延迟到开 PR。 +- **Ops & Delivery** · `scripts/start_docker_opencli.ps1:24` — 健康检查异常只警告不返回非零,服务失败也可能被调用方视为启动成功。 +- **Ops & Delivery** · `scripts/start.ps1:30` — Demo 覆盖生效前先对正式 E 盘配置运行 doctor,新机/迁移机的隔离 Demo 可能被旧路径阻断。 +- **Ops & Delivery** · `scripts/start.ps1:71` — Worker 启动异常被捕获后继续并报告工作台成功,发布能力不可用时易被误判完整健康。 +- **Ops & Delivery** · `scripts/restore.ps1:27` — 恢复只以健康响应判断 App 运行,StopServices 只停 Compose;native/异常健康进程可能仍持有 DB 时执行替换。 +- **Ops & Delivery** · `scripts/backup.ps1:33` — 备份默认包含 .env 且不加密,仅显式 ExcludeEnv 才排除,包被共享时泄漏本机秘密。 +- **Ops & Delivery** · `tests/test_native_scripts.py:11` — 启停测试主要是源码字符串断言,Windows smoke 不做实际 restore/冲突/清理失败与恢复后健康验证。 +- **Ops & Delivery** · `scripts/seed_demo_data.py:32` — 缺 FFmpeg 时仍插入无媒体路径 Demo 数据并成功退出,数量检查通过但媒体 smoke 不可信。 + +### LOW (39) + +- **Frontend UI** · `app/static/js/subtitle-editor.js:171` — 字幕编辑器已有 escapeHtml、虚拟列表、竞态 token 与自动保存版本控制,是可保留的正向实现。 +- **Frontend UI** · `app/static/js/publish-center.js:4` — 前端明确只创建抖音任务,B站保留后端兼容;这是当前产品边界而非应机械删除的代码。 +- **API & Runtime** · `app/main.py:153` — /health 只证明进程可响应,不检查数据库、Job Runner、Scheduler、FFmpeg 或发布 Worker,就绪语义不足。 +- **API & Runtime** · `app/core/config.py:80` — local_admin_token 重复定义;AI 新旧兼容配置与 TaskCreate/Settings 默认值存在重复和漂移。 +- **API & Runtime** · `app/routers/pages.py:40` — 页面 Router 直接组合多个领域 Service,页面数据变化跨层影响面较大。 +- **API & Runtime** · `app/routers/publish.py:64` — OAuth callback 把通用异常字符串拼入 redirect query,可能进入地址栏、历史或 Referer。 +- **API & Runtime** · `app/main.py:115` — 所有静态资源都 no-store,适合开发但导致每次页面加载重复下载。 +- **API & Runtime** · `tests/test_p0_security.py:352` — SQLite PRAGMA 安全测试只检查查询结果非空,没有断言实际开启值;关键 API 边界测试不足。 +- **API & Runtime** · `app/models/subtitle.py:86` — speaker_styles 使用 Any 嵌套字典,模型层无法保证渲染输入结构完整。 +- **Media & Storage** · `tests/test_path_resolution.py:142` — 测试通过 reload 改写全局配置/模块状态且未恢复,顺序或并行执行可能泄漏临时路径。 +- **Media & Storage** · `tests/test_p0_security.py:274` — 子进程测试未覆盖 Windows taskkill 失败、超时和残留进程树,关键边界仍依赖人工验证。 +- **Transcription** · `tests/test_long_live_foundation.py:65` — 未覆盖取消异常传播、损坏进度、并发转写、大文件中部变更、真实响应错误和 FFmpeg/网络恢复。 +- **AI Selection** · `app/services/ai/ai_clip_analyzer.py:456` — 输出归一化同时接受多组历史字段并填大量默认值,兼容有效但维护成本高。 +- **AI Selection** · `app/services/ai_config_service.py:55` — 旧 AI_REMOTE 与新分析/发布配置并存,运行时还改写全局 settings,形成双路径。 +- **AI Selection** · `tests/test_variety_comedy_selection.py:291` — 算法分支覆盖较好,但真实三阶段 Provider、资源消耗和全局评审降级未由集成测试锁定。 +- **Task Review & Cut** · `tests/test_split_services.py:119` — 测试直接把无产物任务设为 completed,固化了任意状态跳跃。 +- **Task Review & Cut** · `tests/test_versioning_rollback.py:163` — 版本测试使用虚构媒体路径,仅验证数据库标记,不代表真实 FFmpeg 闭环。 +- **Subtitle** · `app/services/subtitle_auto_workflow_service.py:273` — auto_config_json 损坏时静默降为空并写回 delivery mode,可能丢弃其余配置。 +- **Subtitle** · `app/services/subtitle_workflow_service.py:312` — 统一 revision 之外仍保留旧调用方导出适配,存在历史漂移成本。 +- **Subtitle** · `tests/test_subtitle_auto_workflow.py:172` — FFmpeg/FFprobe 缺失会跳过,AI Provider 测试固定合法 JSON,真实异常路径不足。 +- **Subtitle** · `app/services/subtitle_data_service.py:761` — 波形先把完整 PCM 载入内存再降采样,超长媒体会产生时长线性内存峰值。 +- **Subtitle** · `app/services/subtitle_data_service.py:1024` — 导入 cue 仅校验非空,单 cue 可接近文件上限并放大渲染/导出/Prompt 资源消耗。 +- **Pipeline & Job Queue** · `tests/test_job_queue.py:154` — 未覆盖旧 owner fencing、stale worker 更新拒绝、重启副作用去重或重复终态。 +- **Pipeline & Job Queue** · `tests/test_auto_pipeline.py:204` — 缺少 PipelineEngine 成功闭环、READY 状态可观察性、取消竞态、重启和真实子进程测试。 +- **Pipeline & Job Queue** · `app/services/pipeline_engine.py:570` — JSON/时间配置损坏时静默回退默认排期,降低故障可见性并可能意外排期。 +- **Publish Center** · `app/services/publish_providers.py:204` — 旧 multipart 发布把完整视频和完整请求体同时载入内存,大文件有约双倍峰值。 +- **Publish Center** · `app/services/publish_service.py:1579` — 批量 job 规范化在缺 accounts 时逐项查询账号,存在 N+1。 +- **Publish Center** · `app/services/publish_service.py:61` — 旧小写与新大写状态字典重复定义/覆盖,增加双格式维护成本。 +- **Publish Center** · `tests/test_publish_history.py:111` — 未覆盖旧状态 SQL 过滤、原始 Secret 响应、批量无效 ID、封面 partial 和 API 超时不确定结果。 +- **Publish Scheduler** · `app/services/publish_repository.py:33` — provider_response 脱敏但 publish_result 直接序列化完整结果,兼容 Publisher 敏感字段可能入库。 +- **Publish Scheduler** · `app/services/publish_scheduler.py:1321` — 发布日志写异常被静默吞,审计日志缺失无健康告警。 +- **Publishers & Worker** · `scripts/opencli_host_bridge.py:74` — 旧 OpenCLI Bridge 类无 Worker Token 校验,虽 main 已转调新 Worker,双入口容易造成安全边界误解。 +- **Publishers & Worker** · `tests/test_publish_worker_client.py:138` — 未覆盖 execution id 重放/并发、恶意路径、账号锁竞态、profile 隔离和 journal 敏感字段清洗。 +- **SQLite Persistence** · `app/services/storage_service.py:197` — 路径查询绕过统一连接且静默吞 DB 错误,可能错误 fallback 到 task_id 目录。 +- **SQLite Persistence** · `scripts/backup_restore_runtime.py:143` — 运行时通过 monkeypatch 改写 backup core,全量/运行时两套入口因导入顺序产生不同语义。 +- **SQLite Persistence** · `scripts/backup_restore.py:253` — 备份 CLI 默认包含 .env 且不加密;备份被同步/共享时会扩大 Secret 外泄面。 +- **Ops & Delivery** · `scripts/acceptance.ps1:115` — 验收递归扫描正式任务目录,媒体量大/锁文件会显著拖慢或阻断 release gate。 +- **Ops & Delivery** · `scripts/backup_restore_runtime.py:142` — import 时 monkeypatch backup core,全局行为依赖导入顺序。 +- **Ops & Delivery** · `docs/PORTABLE_SETUP.md:127` — 文档描述与当前 start.ps1 实现不符,旧 Next Steps 又保留兼容入口,启动排障认知漂移。 + +## Cross-cutting themes + +- **本地单体架构是当前可运行的主要原因.** FastAPI + SQLite WAL + 文件产物 + 持久化 Job + Windows Worker 与个人本机规模匹配;不需要微服务化,现有恢复骨架应保留。 +- **数据一致性是最高优先级.** Persistence、Task/Review/Cut 和 Media Storage 共同暴露外键孤儿、迁移无账本、批次半提交与文件删除无法随数据库回滚。 +- **旧执行覆盖新执行是跨模块重复风险.** Workflow Job、Publish Scheduler 和 Windows Worker 都缺少完整的 owner/attempt/execution fencing;异常恢复顺序变化时可能重复执行或写错终态。 +- **可用性 fallback 正在掩盖降级结果.** AI 部分窗口、转写旧产物、损坏 JSON、封面生成、批量发布和风险字段多处静默继续,用户不一定能区分完整成功、部分成功和旧结果。 +- **发布与配置读取是主要安全边界.** Publish Center、AI Config、Frontend 和 Worker 共同存在原始 Secret 响应、可选写鉴权、DOM XSS、路径字符和 journal 脱敏缺口。 +- **复杂度集中而非全项目平均恶化.** publish_service.py、publish-center.js、app.js、publish_scheduler.py、subtitle_data_service.py 和 transcript_service.py 是主要 God Component;应按业务边界渐进拆分。 +- **测试数量足够但故障隔离仍不安全.** 500 项测试全部通过,但 Coverage 未采集,真实 E2E 和并发恢复不足;一个 fixture 在数据库路径错误时还可能清空真实表。 diff --git a/.codemap/config.json b/.codemap/config.json new file mode 100644 index 0000000..84a8873 --- /dev/null +++ b/.codemap/config.json @@ -0,0 +1,8 @@ +{ + "lang": "zh", + "project": "NiuMa Studio", + "subtitle": "牛马片场 · 本地 AI 高光生产后台工程体检", + "outputDir": ".codemap", + "htmlFile": "codemap.html", + "mdFile": "codemap.md" +} diff --git a/.codemap/modules.json b/.codemap/modules.json new file mode 100644 index 0000000..4c9d948 --- /dev/null +++ b/.codemap/modules.json @@ -0,0 +1,1409 @@ +{ + "meta": { + "project": "NiuMa Studio", + "lang": "zh", + "subtitle": "牛马片场 · 本地 AI 高光生产后台工程体检", + "generatedAt": "2026-08-24", + "htmlPath": ".codemap/codemap.html", + "mdPath": ".codemap/codemap.md", + "spineDesc": "用户通过页面/API 创建素材任务,SQLite Job 驱动转写、AI 选片、审核切片和字幕,再由发送中心排期并交给 Windows Chrome Worker,所有状态与审计证据回写 SQLite。", + "tracked_loc": 47576, + "tracked_files": 131, + "rev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + "excludes": [ + ".venv/", + "__pycache__/", + ".pytest_cache/", + ".ruff_cache/", + ".scannerwork/", + "app/static/vendor/", + "app/static/img/", + "data/", + "tasks/", + "workspace/", + "outputs/", + "backups/", + "acceptance-results/" + ], + "bands": [ + { + "id": "presentation", + "tier": "fe", + "t": "界面 · API", + "d": "浏览器页面、前端交互、FastAPI 路由与运行配置" + }, + { + "id": "wire1", + "wire": true, + "t": "HTTP / JSON / 文件上传" + }, + { + "id": "workflow", + "tier": "be", + "t": "业务编排", + "d": "任务、流水线、发送中心和状态机" + }, + { + "id": "processing", + "tier": "be", + "t": "媒体与 AI 处理", + "d": "转写、AI 选片、切片和字幕" + }, + { + "id": "execution", + "tier": "be", + "t": "外部执行边界", + "d": "调度、Publisher 与 Windows Chrome Worker" + }, + { + "id": "persistence", + "tier": "be", + "t": "持久化与运维", + "d": "SQLite Schema/迁移/备份与本地交付脚本" + } + ], + "spine": [ + "frontend_ui", + "api_runtime", + "pipeline_queue", + "transcription", + "ai_selection", + "task_review_cut", + "subtitle", + "publish_center", + "publish_scheduler", + "publisher_worker", + "persistence" + ], + "reportThemes": [ + [ + "本地单体架构是当前可运行的主要原因", + "FastAPI + SQLite WAL + 文件产物 + 持久化 Job + Windows Worker 与个人本机规模匹配;不需要微服务化,现有恢复骨架应保留。" + ], + [ + "数据一致性是最高优先级", + "Persistence、Task/Review/Cut 和 Media Storage 共同暴露外键孤儿、迁移无账本、批次半提交与文件删除无法随数据库回滚。" + ], + [ + "旧执行覆盖新执行是跨模块重复风险", + "Workflow Job、Publish Scheduler 和 Windows Worker 都缺少完整的 owner/attempt/execution fencing;异常恢复顺序变化时可能重复执行或写错终态。" + ], + [ + "可用性 fallback 正在掩盖降级结果", + "AI 部分窗口、转写旧产物、损坏 JSON、封面生成、批量发布和风险字段多处静默继续,用户不一定能区分完整成功、部分成功和旧结果。" + ], + [ + "发布与配置读取是主要安全边界", + "Publish Center、AI Config、Frontend 和 Worker 共同存在原始 Secret 响应、可选写鉴权、DOM XSS、路径字符和 journal 脱敏缺口。" + ], + [ + "复杂度集中而非全项目平均恶化", + "publish_service.py、publish-center.js、app.js、publish_scheduler.py、subtitle_data_service.py 和 transcript_service.py 是主要 God Component;应按业务边界渐进拆分。" + ], + [ + "测试数量足够但故障隔离仍不安全", + "500 项测试全部通过,但 Coverage 未采集,真实 E2E 和并发恢复不足;一个 fixture 在数据库路径错误时还可能清空真实表。" + ] + ], + "modules": [ + { + "id": "frontend_ui", + "label": "Frontend UI", + "band": "presentation", + "path": "app/templates + app/static", + "paths": [ + "app/templates/**/*.html", + "app/static/css/styles.css", + "app/static/js/*.js" + ], + "coupling": "high", + "deps": [ + "api_runtime" + ], + "desc": "用 Jinja2、原生 JavaScript 和 CSS 提供任务、审核、字幕与发送中心界面。", + "tests": [ + "tests/test_*browser.py", + "tests/test_group_select_all.py", + "tests/test_subtitle_editor.py" + ], + "loc": 13840, + "contentHash": "37d7c00aa927136779156b45423d7686a1fca6e45a3c05350880bb5ffc1122c5", + "score": 65, + "grade": "C", + "tags": [ + "god-component", + "bloat", + "glue", + "duplication", + "legacy" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "app/static/js/app.js:211", + "text": "多个写请求绕过统一 apiFetch;启用 LOCAL_ADMIN_TOKEN 时可能缺失 Authorization 并被 API 拒绝。" + }, + { + "sev": "HIGH", + "loc": "app/static/js/app.js:1729", + "text": "接口或任务数据直接拼接到 innerHTML;publish-center.js:2152 有同类路径,存在本地 DOM XSS/页面结构破坏风险。" + }, + { + "sev": "MED", + "loc": "app/templates/system_status.html:98", + "text": "配置/API Key 字段进入 DOM,base.html:11 还承载本地管理 Token;需确认全链路始终掩码。" + }, + { + "sev": "MED", + "loc": "app/static/css/styles.css:870", + "text": "使用多个未在 :root 定义的 CSS 自定义属性,相关声明可能失效。" + }, + { + "sev": "MED", + "loc": "app/static/js/app.js:1", + "text": "任务、转写、AI、审核、切片、字幕和配置行为集中在超大全局脚本,回归半径较大。" + }, + { + "sev": "MED", + "loc": "tests/test_publish_center_browser.py:16", + "text": "Playwright 缺失时核心浏览器测试可静默跳过,字幕交互与鉴权失败路径覆盖不足。" + }, + { + "sev": "LOW", + "loc": "app/static/js/subtitle-editor.js:171", + "text": "字幕编辑器已有 escapeHtml、虚拟列表、竞态 token 与自动保存版本控制,是可保留的正向实现。" + }, + { + "sev": "LOW", + "loc": "app/static/js/publish-center.js:4", + "text": "前端明确只创建抖音任务,B站保留后端兼容;这是当前产品边界而非应机械删除的代码。" + } + ], + "auditedHash": "37d7c00aa927136779156b45423d7686a1fca6e45a3c05350880bb5ffc1122c5", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "api_runtime", + "label": "API & Runtime", + "band": "presentation", + "path": "app/main.py + core/models/routers", + "paths": [ + "app/main.py", + "app/core/**/*.py", + "app/models/**/*.py", + "app/routers/**/*.py" + ], + "coupling": "core", + "deps": [ + "media_storage", + "transcription", + "ai_selection", + "task_review_cut", + "subtitle", + "pipeline_queue", + "publish_center", + "publish_scheduler", + "persistence" + ], + "desc": "加载配置、初始化应用与安全中间件,并把页面/API 请求路由到业务服务。", + "tests": [ + "tests/test_p0_security.py", + "tests/test_publish_api_flow.py", + "tests/test_task_defaults.py" + ], + "loc": 2713, + "contentHash": "20597a10925da6f5430298691ed2ace1ea04fa00fe4f94aeb75cc7701625eae9", + "score": 63, + "grade": "C", + "tags": [ + "fallback", + "legacy", + "dual-format", + "duplication", + "glue", + "bloat", + "god-component", + "placeholder", + "silent-except", + "monkeypatch", + "over-fit", + "any-escape" + ], + "findings": [ + { + "sev": "MED", + "loc": "app/main.py:97", + "text": "API 写保护仅在 LOCAL_ADMIN_TOKEN 非空时启用;默认 Token 为空。若端口可被局域网访问,写 API 默认无认证。" + }, + { + "sev": "MED", + "loc": "app/routers/tasks.py:220", + "text": "多个 async 路由直接调用同步 FFmpeg/文件长任务,可能阻塞 FastAPI 事件循环;项目另有持久化异步 Job 路径。" + }, + { + "sev": "MED", + "loc": "app/models/task.py:7", + "text": "TaskStatus 同时定义大写自动流水线与小写手动状态,API 可直接提交枚举状态但不表达合法状态迁移。" + }, + { + "sev": "LOW", + "loc": "app/main.py:153", + "text": "/health 只证明进程可响应,不检查数据库、Job Runner、Scheduler、FFmpeg 或发布 Worker,就绪语义不足。" + }, + { + "sev": "LOW", + "loc": "app/core/config.py:80", + "text": "local_admin_token 重复定义;AI 新旧兼容配置与 TaskCreate/Settings 默认值存在重复和漂移。" + }, + { + "sev": "LOW", + "loc": "app/routers/pages.py:40", + "text": "页面 Router 直接组合多个领域 Service,页面数据变化跨层影响面较大。" + }, + { + "sev": "LOW", + "loc": "app/routers/publish.py:64", + "text": "OAuth callback 把通用异常字符串拼入 redirect query,可能进入地址栏、历史或 Referer。" + }, + { + "sev": "LOW", + "loc": "app/main.py:115", + "text": "所有静态资源都 no-store,适合开发但导致每次页面加载重复下载。" + }, + { + "sev": "LOW", + "loc": "tests/test_p0_security.py:352", + "text": "SQLite PRAGMA 安全测试只检查查询结果非空,没有断言实际开启值;关键 API 边界测试不足。" + }, + { + "sev": "LOW", + "loc": "app/models/subtitle.py:86", + "text": "speaker_styles 使用 Any 嵌套字典,模型层无法保证渲染输入结构完整。" + } + ], + "auditedHash": "20597a10925da6f5430298691ed2ace1ea04fa00fe4f94aeb75cc7701625eae9", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "media_storage", + "label": "Media & Storage", + "band": "processing", + "path": "storage/media/FFmpeg services", + "paths": [ + "app/services/storage_service.py", + "app/services/media_preflight_service.py", + "app/services/managed_process_service.py", + "app/services/video_cut_service.py", + "app/services/audio_reaction_service.py" + ], + "coupling": "high", + "deps": [ + "persistence" + ], + "desc": "管理 E 盘任务目录、路径安全、素材预检、FFmpeg/FFprobe 与受控子进程。", + "tests": [ + "tests/test_media_storage_lifecycle.py", + "tests/test_path_resolution.py", + "tests/test_long_live_foundation.py" + ], + "loc": 1300, + "contentHash": "12bf70550a6f4ec8340b5b7145dfcaa0343b424c6ed451a1839ea37b810a6628", + "score": 69, + "grade": "C", + "tags": [ + "fallback", + "silent-except", + "legacy", + "dual-format", + "bloat", + "glue" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "app/services/storage_service.py:178", + "text": "_storage_path_from_dir_name 直接把 PureWindowsPath(task_dir_name) 的 parts 拼接到 tasks_dir,未复用 _safe_relative_parts;核心写入/读取原语缺少 traversal 覆盖。" + }, + { + "sev": "HIGH", + "loc": "app/services/storage_service.py:218", + "text": "allocate_task_dir_name 采用查询数据库+检查目录+返回的非原子分配,且无明确唯一约束/独占创建;并发上传或重试可能获得同一目录名。" + }, + { + "sev": "HIGH", + "loc": "app/services/storage_service.py:302", + "text": "resolve_video_file_path 对已存在路径直接原样返回;媒体接口只检查 exists/is_file 即 FileResponse,异常数据库路径可能暴露允许根目录外的本地文件。" + }, + { + "sev": "MED", + "loc": "app/services/storage_service.py:213", + "text": "读取已有任务目录遇 sqlite3.Error 静默回空集合/None,数据库锁定或损坏会被伪装成无目录并走错误 fallback。" + }, + { + "sev": "MED", + "loc": "app/services/storage_service.py:533", + "text": "清理计划逐个 rmtree,而调用方随后才更新数据库;后续失败时已删文件无法随数据库回滚,形成半删除。" + }, + { + "sev": "MED", + "loc": "app/services/storage_service.py:557", + "text": "move_task_directory_to_trash 复用未校验的目录拼接;虽当前无运行调用,但保留未来误用风险。" + }, + { + "sev": "MED", + "loc": "app/services/managed_process_service.py:18", + "text": "Windows taskkill 返回码/超时异常被忽略,未确认进程树退出;上层可能释放租约但 FFmpeg 仍继续。" + }, + { + "sev": "MED", + "loc": "tests/test_path_resolution.py:44", + "text": "所谓唯一命名测试未写数据库或制造同名目录,未验证并发/冲突核心行为。" + }, + { + "sev": "LOW", + "loc": "tests/test_path_resolution.py:142", + "text": "测试通过 reload 改写全局配置/模块状态且未恢复,顺序或并行执行可能泄漏临时路径。" + }, + { + "sev": "LOW", + "loc": "tests/test_p0_security.py:274", + "text": "子进程测试未覆盖 Windows taskkill 失败、超时和残留进程树,关键边界仍依赖人工验证。" + } + ], + "auditedHash": "12bf70550a6f4ec8340b5b7145dfcaa0343b424c6ed451a1839ea37b810a6628", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "transcription", + "label": "Transcription", + "band": "processing", + "path": "transcript services", + "paths": [ + "app/services/transcript_service.py", + "app/services/transcript_workflow_service.py", + "app/services/transcription_checkpoint_service.py" + ], + "coupling": "high", + "deps": [ + "media_storage", + "persistence" + ], + "desc": "提取音频,调用火山引擎或 faster-whisper,并以分块 checkpoint 生成时间戳转写。", + "tests": [ + "tests/test_job_queue.py", + "tests/test_auto_pipeline.py", + "tests/test_long_live_foundation.py" + ], + "loc": 1930, + "contentHash": "46138f9338e0b3092d14b63dd2b427abbe71e210b966ed7b136601e1ba192132", + "score": 58, + "grade": "D", + "tags": [ + "over-fit", + "fallback", + "fake-output", + "dual-format", + "silent-except", + "duplication", + "god-component", + "glue" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "app/services/transcription_checkpoint_service.py:22", + "text": "文件指纹只哈希大小与首尾各 1MiB;大文件中部变化但首尾/大小不变会错误复用旧转写 checkpoint。" + }, + { + "sev": "HIGH", + "loc": "app/services/transcript_service.py:260", + "text": "捕获所有 Exception 会把 TranscriptCancelledError 包成 RuntimeError,取消可能被标记 failed 而非 cancelled。" + }, + { + "sev": "HIGH", + "loc": "app/services/transcript_workflow_service.py:318", + "text": "transcript.md 存在即返回 completed,不校验源指纹或最近生成是否成功;失败后旧转写可能被误当当前结果。" + }, + { + "sev": "MED", + "loc": "app/services/transcript_service.py:552", + "text": "远程响应无 utterances 时把 text/message 包成 0-1 秒片段,错误 message 可能被当成转写正文。" + }, + { + "sev": "MED", + "loc": "app/services/transcript_workflow_service.py:25", + "text": "运行/取消状态仅用进程级集合且 check-then-add 非原子,多进程或重启可能重复转写且取消失联。" + }, + { + "sev": "MED", + "loc": "app/services/transcript_service.py:831", + "text": "进度文件读失败/JSON 损坏时静默返回空字典,隐藏损坏并可能触发重跑。" + }, + { + "sev": "MED", + "loc": "app/services/transcript_service.py:316", + "text": "本地与火山转写各自复制分块、checkpoint、进度和错误循环,行为易漂移。" + }, + { + "sev": "MED", + "loc": "app/services/transcript_service.py:83", + "text": "单文件混合 FFmpeg、Provider、分块、checkpoint、导出与读取,职责和回归面过大。" + }, + { + "sev": "MED", + "loc": "app/services/transcript_service.py:68", + "text": "活动 Provider/模型/设备为进程级可变全局,并发任务可能互相覆盖进度元数据。" + }, + { + "sev": "LOW", + "loc": "tests/test_long_live_foundation.py:65", + "text": "未覆盖取消异常传播、损坏进度、并发转写、大文件中部变更、真实响应错误和 FFmpeg/网络恢复。" + } + ], + "auditedHash": "46138f9338e0b3092d14b63dd2b427abbe71e210b966ed7b136601e1ba192132", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "ai_selection", + "label": "AI Selection", + "band": "processing", + "path": "AI providers and analyzers", + "paths": [ + "app/services/ai/**/*.py", + "app/services/ai_analysis_workflow_service.py", + "app/services/ai_config_service.py", + "app/services/ai_prompt_preset_service.py", + "app/services/ai_clip_service.py", + "app/services/clip_feedback_service.py" + ], + "coupling": "core", + "deps": [ + "transcription", + "persistence" + ], + "desc": "选择 Codex/远程/本地 Provider,解析不稳定 AI 输出并生成通用、综艺或长直播候选片段。", + "tests": [ + "tests/test_ai_json_parsing.py", + "tests/test_codex_cli_provider.py", + "tests/test_long_live_selection.py", + "tests/test_variety_comedy_selection.py" + ], + "loc": 4420, + "contentHash": "5315d1044880aa0ad4e0364cfeea5ce10122b740f2a8ddfe8ab3d237d46b6b8c", + "score": 63, + "grade": "C", + "tags": [ + "fallback", + "silent-except", + "legacy", + "dual-format", + "stub", + "fake-output", + "bloat", + "duplication", + "god-component" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "app/services/ai_config_service.py:318", + "text": "get_ai_config_context 返回含 AI/ASR Key 的完整 values,GET /api/settings/ai 无读取鉴权,页面也复用该上下文。" + }, + { + "sev": "MED", + "loc": "app/services/ai/ai_clip_service.py:4", + "text": "generate_candidate_clips_placeholder 返回三条硬编码 ClipCandidate;虽当前无调用方,误接线会产生 fake output。" + }, + { + "sev": "MED", + "loc": "app/services/ai/ai_clip_analyzer.py:131", + "text": "分段分析失败窗口被跳过,只要其他窗口有候选便返回;通用路径缺少最低覆盖率门槛。" + }, + { + "sev": "MED", + "loc": "app/services/ai/variety_comedy_analyzer.py:513", + "text": "全局评审失败时隐式降级到扩展阶段评分,Provider 失败仍可形成候选。" + }, + { + "sev": "MED", + "loc": "app/services/ai/local_model_provider.py:19", + "text": "fallback_protocol 对任意 AIProviderError 都执行第二协议,未区分限流、认证和协议错误,可能重复调用与计费。" + }, + { + "sev": "MED", + "loc": "app/services/ai_analysis_workflow_service.py:738", + "text": "候选事务替换、JSON 文件和 run 历史分步持久化,后续失败会形成数据库与文件/历史不一致。" + }, + { + "sev": "MED", + "loc": "app/services/ai/long_live_talk_analyzer.py:427", + "text": "checkpoint 更新无 run/lease/owner fencing,并发或旧进程恢复可能互相覆盖窗口状态。" + }, + { + "sev": "MED", + "loc": "app/services/ai_analysis_workflow_service.py:71", + "text": "分析元文件不存在、损坏或读取异常时静默返回空字典,无法区分未分析与产物损坏。" + }, + { + "sev": "LOW", + "loc": "app/services/ai/ai_clip_analyzer.py:456", + "text": "输出归一化同时接受多组历史字段并填大量默认值,兼容有效但维护成本高。" + }, + { + "sev": "LOW", + "loc": "app/services/ai_config_service.py:55", + "text": "旧 AI_REMOTE 与新分析/发布配置并存,运行时还改写全局 settings,形成双路径。" + }, + { + "sev": "MED", + "loc": "app/services/ai_analysis_workflow_service.py:685", + "text": "单文件集中 profile、Provider、状态、候选、文件、历史与恢复,模块回归半径大。" + }, + { + "sev": "MED", + "loc": "app/services/ai/ai_clip_analyzer.py:252", + "text": "多个分析器重复偏好摘要、时间转换、默认字段和 AI 输出解析逻辑。" + }, + { + "sev": "MED", + "loc": "tests/test_split_services.py:200", + "text": "缺少真实 Provider 成功路径、文件写入失败一致性、并发运行和旧 checkpoint 覆盖测试。" + }, + { + "sev": "MED", + "loc": "tests/test_codex_cli_provider.py:12", + "text": "Provider 测试主要 monkeypatch,缺少 HTTP 429/500/超时和协议 fallback 重复调用边界。" + }, + { + "sev": "LOW", + "loc": "tests/test_variety_comedy_selection.py:291", + "text": "算法分支覆盖较好,但真实三阶段 Provider、资源消耗和全局评审降级未由集成测试锁定。" + } + ], + "auditedHash": "5315d1044880aa0ad4e0364cfeea5ce10122b740f2a8ddfe8ab3d237d46b6b8c", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "task_review_cut", + "label": "Task Review & Cut", + "band": "workflow", + "path": "task and cut workflow services", + "paths": [ + "app/services/task_lifecycle_service.py", + "app/services/task_service.py", + "app/services/task_query_service.py", + "app/services/task_log_service.py", + "app/services/video_cut_workflow_service.py" + ], + "coupling": "core", + "deps": [ + "media_storage", + "ai_selection", + "publish_center", + "persistence" + ], + "desc": "维护任务状态与视图,保存候选审核选择,生成切片版本并同步发送中心。", + "tests": [ + "tests/test_split_services.py", + "tests/test_task_query_service.py", + "tests/test_clip_review_publish_sync.py", + "tests/test_versioning_rollback.py" + ], + "loc": 2436, + "contentHash": "39fe8feac1c349c1e0bd2a893ea4abf5f544aca4e112149350a4ad06857bc440", + "score": 61, + "grade": "C", + "tags": [ + "god-component", + "glue", + "duplication", + "dual-format", + "fallback", + "legacy", + "over-fit", + "silent-except" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "tests/test_task_query_service.py:175", + "text": "测试清理函数无条件 DELETE 多张核心整表,fixture 每个用例前后执行;若继承真实数据库路径会清空真实任务与发布记录。" + }, + { + "sev": "HIGH", + "loc": "app/services/video_cut_workflow_service.py:248", + "text": "每个切片记录独立提交;中途异常时前项已提交但 cut_run 未失败,留下半成功状态。" + }, + { + "sev": "HIGH", + "loc": "app/services/task_lifecycle_service.py:152", + "text": "状态更新无合法转移和产物前置条件校验,API 可把空任务直接标记完成。" + }, + { + "sev": "HIGH", + "loc": "app/services/task_service.py:451", + "text": "ffprobe 调用无 timeout 且未完整捕获 IO 异常,NAS/损坏媒体可令详情请求阻塞或 500。" + }, + { + "sev": "MED", + "loc": "app/services/task_service.py:14", + "text": "task_service 集中导入多领域私有函数并与 task_query_service 反向依赖,职责重叠、回归半径大。" + }, + { + "sev": "MED", + "loc": "app/services/task_query_service.py:134", + "text": "批量 output clip 逻辑复制 task_service 中的路径、字幕与发布就绪判断,口径易漂移。" + }, + { + "sev": "MED", + "loc": "app/services/task_service.py:717", + "text": "单条候选异常时间格式即可让整个候选列表抛错,缺少逐条坏数据隔离。" + }, + { + "sev": "MED", + "loc": "app/services/task_service.py:587", + "text": "get_task 不过滤 is_deleted,已删除但保留历史的任务仍可被详情和动作接口读取。" + }, + { + "sev": "MED", + "loc": "app/services/video_cut_workflow_service.py:18", + "text": "MAX(run_number) 与 INSERT 分离且无唯一约束,并发重切可分配重复版本号。" + }, + { + "sev": "MED", + "loc": "app/services/video_cut_workflow_service.py:274", + "text": "发布同步异常返回 partial,但任务主状态已先标完成,恢复含义分散。" + }, + { + "sev": "LOW", + "loc": "tests/test_split_services.py:119", + "text": "测试直接把无产物任务设为 completed,固化了任意状态跳跃。" + }, + { + "sev": "LOW", + "loc": "tests/test_versioning_rollback.py:163", + "text": "版本测试使用虚构媒体路径,仅验证数据库标记,不代表真实 FFmpeg 闭环。" + } + ], + "auditedHash": "39fe8feac1c349c1e0bd2a893ea4abf5f544aca4e112149350a4ad06857bc440", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "subtitle", + "label": "Subtitle", + "band": "processing", + "path": "subtitle services", + "paths": [ + "app/services/subtitle_*.py" + ], + "coupling": "high", + "deps": [ + "media_storage", + "task_review_cut", + "ai_selection", + "persistence" + ], + "desc": "维护字幕轨、不可变 revision、cue、AI 建议、渲染验证与自动流水线交付决定。", + "tests": [ + "tests/test_subtitle_editor.py", + "tests/test_subtitle_auto_workflow.py" + ], + "loc": 2391, + "contentHash": "3b08a6ef8c6419060117e04d8123f31d5dbe1aff8891da3274e2171d6356e3a5", + "score": 70, + "grade": "C", + "tags": [ + "fallback", + "silent-except", + "legacy", + "bloat", + "god-component", + "monkeypatch" + ], + "findings": [ + { + "sev": "MED", + "loc": "app/services/subtitle_data_service.py:352", + "text": "手工 revision 的 active/base 检查在事务外,并发编辑可同时通过并让后提交者覆盖 active 选择。" + }, + { + "sev": "MED", + "loc": "app/services/subtitle_data_service.py:489", + "text": "批准 revision 在事务外校验,后续按 id 无条件批准激活;并发/重放旧请求可回退 active 版本。" + }, + { + "sev": "MED", + "loc": "app/services/subtitle_data_service.py:135", + "text": "source/clip track 均先查后插;NULL output_clip_id 唯一约束不足,并发可能重复源轨或 500。" + }, + { + "sev": "MED", + "loc": "app/services/subtitle_auto_workflow_service.py:87", + "text": "批量批准逐 clip 独立提交,统一 Job 后创建;中途失败会留下前项已批准但无队列的部分成功。" + }, + { + "sev": "MED", + "loc": "app/services/subtitle_data_service.py:168", + "text": "source revision 提交后逐 clip 独立同步,任一异常形成 source 已更新而部分 clip 仍旧的混合状态。" + }, + { + "sev": "MED", + "loc": "app/services/subtitle_workflow_service.py:472", + "text": "先标 completed 再单独激活,更新缺 expected status/revision/owner 条件;迟到 worker 可激活旧成片。" + }, + { + "sev": "LOW", + "loc": "app/services/subtitle_auto_workflow_service.py:273", + "text": "auto_config_json 损坏时静默降为空并写回 delivery mode,可能丢弃其余配置。" + }, + { + "sev": "MED", + "loc": "app/services/subtitle_data_service.py:1045", + "text": "字幕数据与渲染服务分别聚合过多职责,修改与测试回归半径大。" + }, + { + "sev": "LOW", + "loc": "app/services/subtitle_workflow_service.py:312", + "text": "统一 revision 之外仍保留旧调用方导出适配,存在历史漂移成本。" + }, + { + "sev": "MED", + "loc": "tests/test_subtitle_editor.py:189", + "text": "未覆盖并发 save/approve/ensure track 竞态和批量中途失败后的部分提交。" + }, + { + "sev": "LOW", + "loc": "tests/test_subtitle_auto_workflow.py:172", + "text": "FFmpeg/FFprobe 缺失会跳过,AI Provider 测试固定合法 JSON,真实异常路径不足。" + }, + { + "sev": "LOW", + "loc": "app/services/subtitle_data_service.py:761", + "text": "波形先把完整 PCM 载入内存再降采样,超长媒体会产生时长线性内存峰值。" + }, + { + "sev": "LOW", + "loc": "app/services/subtitle_data_service.py:1024", + "text": "导入 cue 仅校验非空,单 cue 可接近文件上限并放大渲染/导出/Prompt 资源消耗。" + } + ], + "auditedHash": "3b08a6ef8c6419060117e04d8123f31d5dbe1aff8891da3274e2171d6356e3a5", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "pipeline_queue", + "label": "Pipeline & Job Queue", + "band": "workflow", + "path": "pipeline and workflow job services", + "paths": [ + "app/services/pipeline_engine.py", + "app/services/job_service.py", + "app/services/job_worker.py", + "app/services/job_worker_process.py", + "app/services/auto_publish_service.py", + "app/services/metadata_generator.py" + ], + "coupling": "core", + "deps": [ + "transcription", + "ai_selection", + "task_review_cut", + "subtitle", + "publish_center", + "persistence" + ], + "desc": "用 SQLite Job、Lease、Heartbeat、Checkpoint 和子进程串联长任务与自动流水线。", + "tests": [ + "tests/test_auto_pipeline.py", + "tests/test_job_queue.py", + "tests/test_subtitle_auto_workflow.py" + ], + "loc": 1745, + "contentHash": "006dbc8658a43c084fea542d3dbfa895af2cddf2b324b1f0b7c3ea13ae4baa0b", + "score": 58, + "grade": "D", + "tags": [ + "fallback", + "silent-except", + "legacy", + "dual-format", + "stub", + "god-component", + "glue", + "over-fit" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "app/services/job_service.py:329", + "text": "checkpoint、progress 与终态更新只按 job_id,不校验 lease_owner/status;旧 worker 可覆盖新 attempt。" + }, + { + "sev": "HIGH", + "loc": "app/services/job_worker.py:38", + "text": "already_claimed 跳过 claim 且不核对当前 owner,已被接管的旧子进程仍可继续执行写结果。" + }, + { + "sev": "HIGH", + "loc": "app/services/job_service.py:287", + "text": "claim_next_job 可把达到 max_attempts 的 running 任务直接失败而不要求 lease 过期,慢 worker 可能被误判。" + }, + { + "sev": "HIGH", + "loc": "app/services/pipeline_engine.py:118", + "text": "取消只在 step 前检查,step 内不中断;取消后可能显示失败/完成且副作用无回滚。" + }, + { + "sev": "HIGH", + "loc": "app/services/pipeline_engine.py:158", + "text": "READY_TO_PUBLISH 写入后同一 run 立即写 COMPLETED,人工确认语义成为不可稳定观察的瞬态。" + }, + { + "sev": "MED", + "loc": "app/services/job_service.py:51", + "text": "Job JSON 损坏时静默保留原字符串,执行器随后按 dict 使用并产生不明确错误。" + }, + { + "sev": "MED", + "loc": "app/services/job_worker.py:175", + "text": "子进程 stdout/stderr 均丢弃,父进程只保留退出码,恢复与诊断证据不足。" + }, + { + "sev": "MED", + "loc": "app/services/pipeline_engine.py:104", + "text": "run context 只在内存,无按 step 持久化副作用 checkpoint;崩溃重试可能重复 AI/封面/排期动作。" + }, + { + "sev": "MED", + "loc": "app/services/pipeline_engine.py:143", + "text": "异常后写 summary 仍读取源文件和多个产物,二次失败可遮蔽原始错误。" + }, + { + "sev": "MED", + "loc": "app/services/job_service.py:226", + "text": "mark_job_running 无当前状态/owner 条件,已完成或已取消 Job 也可被内部调用重置。" + }, + { + "sev": "MED", + "loc": "app/services/job_service.py:15", + "text": "声明 ai_analysis/publish Job 类型但 execute_job 未实现,属于可创建不可执行的 legacy/stub。" + }, + { + "sev": "LOW", + "loc": "tests/test_job_queue.py:154", + "text": "未覆盖旧 owner fencing、stale worker 更新拒绝、重启副作用去重或重复终态。" + }, + { + "sev": "LOW", + "loc": "tests/test_auto_pipeline.py:204", + "text": "缺少 PipelineEngine 成功闭环、READY 状态可观察性、取消竞态、重启和真实子进程测试。" + }, + { + "sev": "LOW", + "loc": "app/services/pipeline_engine.py:570", + "text": "JSON/时间配置损坏时静默回退默认排期,降低故障可见性并可能意外排期。" + } + ], + "auditedHash": "006dbc8658a43c084fea542d3dbfa895af2cddf2b324b1f0b7c3ea13ae4baa0b", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "publish_center", + "label": "Publish Center", + "band": "workflow", + "path": "publish content preparation services", + "paths": [ + "app/services/publish_service.py", + "app/services/publish_copy_rules.py", + "app/services/publish_domain.py", + "app/services/publish_adapters.py", + "app/services/publish_readiness.py", + "app/services/publish_providers.py" + ], + "coupling": "core", + "deps": [ + "task_review_cut", + "subtitle", + "publish_scheduler", + "publisher_worker", + "persistence" + ], + "desc": "准备标题、简介、话题、封面、账号和发布草稿,并保留历史兼容执行入口。", + "tests": [ + "tests/test_publish_copy_rules.py", + "tests/test_publish_readiness.py", + "tests/test_publish_task_linkage.py", + "tests/test_publish_history.py" + ], + "loc": 5561, + "contentHash": "1ba69edf18466bd5cd73890b918c0b1145a66f5c441e9c885b79e104ea95645a", + "score": 52, + "grade": "D", + "tags": [ + "god-component", + "bloat", + "legacy", + "dual-format", + "fallback", + "silent-except", + "placeholder", + "duplication" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "app/services/publish_service.py:564", + "text": "配置/账号归一化仅新增 masked 字段却保留原始 Secret/Token,GET 接口直接返回。" + }, + { + "sev": "MED", + "loc": "app/services/publish_service.py:4562", + "text": "4k+ 行文件混合 CRUD、OAuth、内容、封面、同步、历史、多个 Publisher 与页面上下文,是高耦合 God Component。" + }, + { + "sev": "MED", + "loc": "app/services/publish_service.py:2935", + "text": "历史 SQL 先用大写状态过滤再规范化,旧小写 ready/published/failed 记录可能被提前排除。" + }, + { + "sev": "MED", + "loc": "app/services/publish_service.py:2137", + "text": "封面生成异常只写 item cover_error 未计入 errors,整体仍可能返回 ok,形成隐式部分成功。" + }, + { + "sev": "MED", + "loc": "app/services/publish_service.py:2784", + "text": "批量创建遇无效 output_clip_id 直接跳过且仍返回 ok,全无效时也可能显示成功创建 0 条。" + }, + { + "sev": "MED", + "loc": "app/services/publish_readiness.py:167", + "text": "api_publish 可被创建且兼容入口仍在,但 readiness 正常调度统一判 unsupported,模式契约不一致。" + }, + { + "sev": "MED", + "loc": "app/services/publish_providers.py:216", + "text": "旧 API 发布超时统一 failed;若平台已接收而响应丢失,重试可能重复投稿且未进入 NEED_REVIEW。" + }, + { + "sev": "LOW", + "loc": "app/services/publish_providers.py:204", + "text": "旧 multipart 发布把完整视频和完整请求体同时载入内存,大文件有约双倍峰值。" + }, + { + "sev": "LOW", + "loc": "app/services/publish_service.py:1579", + "text": "批量 job 规范化在缺 accounts 时逐项查询账号,存在 N+1。" + }, + { + "sev": "LOW", + "loc": "app/services/publish_service.py:61", + "text": "旧小写与新大写状态字典重复定义/覆盖,增加双格式维护成本。" + }, + { + "sev": "LOW", + "loc": "tests/test_publish_history.py:111", + "text": "未覆盖旧状态 SQL 过滤、原始 Secret 响应、批量无效 ID、封面 partial 和 API 超时不确定结果。" + } + ], + "auditedHash": "1ba69edf18466bd5cd73890b918c0b1145a66f5c441e9c885b79e104ea95645a", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "publish_scheduler", + "label": "Publish Scheduler", + "band": "execution", + "path": "scheduler/repository/executor", + "paths": [ + "app/publish_scheduler.py", + "app/services/publish_scheduler.py", + "app/services/publish_repository.py", + "app/services/publish_executor.py", + "app/services/publish_time.py" + ], + "coupling": "core", + "deps": [ + "publish_center", + "publisher_worker", + "persistence" + ], + "desc": "计算排期、原子领取发布任务、重试、恢复不确定执行并维护发布状态机。", + "tests": [ + "tests/test_publish_scheduler.py", + "tests/test_publish_scheduler_state_machine.py", + "tests/test_publish_timezones.py" + ], + "loc": 1754, + "contentHash": "01583447b65ecbbc65b7b91bfdadfbcd4f6b6e1a27b60a5f2c6fee0c13af44ff", + "score": 62, + "grade": "C", + "tags": [ + "god-component", + "bloat", + "glue", + "fallback", + "legacy", + "silent-except" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "app/services/publish_repository.py:54", + "text": "发布结果只按 job_id 写回,无 execution/worker/claim 代际条件;旧执行可覆盖新 claim 终态。" + }, + { + "sev": "HIGH", + "loc": "app/services/publish_scheduler.py:495", + "text": "repair_and_publish 检查与克隆间无状态锁;并发确认/重复点击可在源任务已发布后仍新建排期。" + }, + { + "sev": "MED", + "loc": "app/services/publish_scheduler.py:1403", + "text": "丢弃 create_task 引用,关闭只 stop 不 await,存在 Sonar S7502 的生命周期/优雅停机缺口。" + }, + { + "sev": "MED", + "loc": "app/services/publish_scheduler.py:264", + "text": "每轮加载全部 SCHEDULED 再由 Python 判断到期,无 SQL due 条件/批量上限。" + }, + { + "sev": "MED", + "loc": "app/services/publish_scheduler.py:617", + "text": "全部 PUBLISHING 任务串行查询 Worker execution;多个超时会拖长整轮扫描。" + }, + { + "sev": "MED", + "loc": "app/services/publish_scheduler.py:1305", + "text": "风险 JSON 损坏时静默回空列表,可能绕过风险复核继续投稿,属于 fail-open。" + }, + { + "sev": "MED", + "loc": "app/services/publish_scheduler.py:961", + "text": "人工发布 URL 只做域名子串匹配,不解析 hostname,错误域名/query 也可通过。" + }, + { + "sev": "LOW", + "loc": "app/services/publish_repository.py:33", + "text": "provider_response 脱敏但 publish_result 直接序列化完整结果,兼容 Publisher 敏感字段可能入库。" + }, + { + "sev": "MED", + "loc": "tests/test_publish_scheduler_state_machine.py:302", + "text": "未覆盖旧 execution 回写、新旧 claim、repair 并发、后台 Task 生命周期和损坏 risk_flags。" + }, + { + "sev": "LOW", + "loc": "app/services/publish_scheduler.py:1321", + "text": "发布日志写异常被静默吞,审计日志缺失无健康告警。" + } + ], + "auditedHash": "01583447b65ecbbc65b7b91bfdadfbcd4f6b6e1a27b60a5f2c6fee0c13af44ff", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "publisher_worker", + "label": "Publishers & Worker", + "band": "execution", + "path": "publisher registry and Windows worker", + "paths": [ + "app/services/publishers/**/*.py", + "scripts/publish_host_worker.py", + "scripts/opencli_host_bridge.py" + ], + "coupling": "high", + "deps": [ + "media_storage", + "persistence" + ], + "desc": "通过 Publisher Registry 和 Windows Chrome Worker 执行抖音/B站投稿、导出与兼容模式。", + "tests": [ + "tests/test_publisher_registry.py", + "tests/test_local_browser_publishers.py", + "tests/test_page_script_publishers.py", + "tests/test_publish_worker_client.py" + ], + "loc": 2116, + "contentHash": "78ab88269d9dd7bb446b912964846491e2250c48c9292edae33368591ee301cd", + "score": 62, + "grade": "C", + "tags": [ + "any-escape", + "fallback", + "legacy", + "dual-format", + "glue", + "god-component", + "duplication" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "scripts/publish_host_worker.py:74", + "text": "execution_id 直接拼 journal 路径且仅限长度,../、分隔符或盘符可逃逸 Worker 状态目录,读写异常 JSON。" + }, + { + "sev": "HIGH", + "loc": "scripts/publish_host_worker.py:264", + "text": "同 execution_id 不检查既有终态即重新发布;实例内锁无法协调同 ID 并发/重放,可能重复投稿。" + }, + { + "sev": "MED", + "loc": "scripts/publish_host_worker.py:231", + "text": "账号操作先 check locked 再后台获取锁,存在竞态;锁仅进程内,多 Worker/重启不持久。" + }, + { + "sev": "MED", + "loc": "app/services/publishers/browser_runtime.py:32", + "text": "platform/account_id 直接拼浏览器 profile/artifact 路径,异常 ID 可路径穿越或造成账号目录冲突。" + }, + { + "sev": "MED", + "loc": "scripts/publish_host_worker.py:293", + "text": "Worker 把 result/diagnostics 原样写 journal,兼容 Provider 异常输出中的 Token/Cookie 可能长期落盘并经接口返回。" + }, + { + "sev": "MED", + "loc": "app/services/publishers/page_scripts.py:14", + "text": "Worker 页面脚本延迟导入超大 publish_service 私有函数,形成 legacy glue 与高回归半径。" + }, + { + "sev": "LOW", + "loc": "scripts/opencli_host_bridge.py:74", + "text": "旧 OpenCLI Bridge 类无 Worker Token 校验,虽 main 已转调新 Worker,双入口容易造成安全边界误解。" + }, + { + "sev": "LOW", + "loc": "tests/test_publish_worker_client.py:138", + "text": "未覆盖 execution id 重放/并发、恶意路径、账号锁竞态、profile 隔离和 journal 敏感字段清洗。" + } + ], + "auditedHash": "78ab88269d9dd7bb446b912964846491e2250c48c9292edae33368591ee301cd", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "persistence", + "label": "SQLite Persistence", + "band": "persistence", + "path": "app/db + backup services", + "paths": [ + "app/db/**/*.py", + "app/services/database_backup_service.py", + "scripts/backup_restore.py", + "scripts/backup_restore_runtime.py", + "scripts/cleanup_database_backups.py" + ], + "coupling": "core", + "deps": [], + "desc": "创建 SQLite Schema、执行启动时兼容迁移和索引,并提供 WAL 安全备份恢复。", + "tests": [ + "tests/test_backup_restore.py", + "tests/test_backup_restore_wal.py", + "tests/test_database_backup_service.py", + "tests/test_p1_1_db_performance.py" + ], + "loc": 3023, + "contentHash": "18a27dfc8ba1bbf86653d9709070d1f221505a53bdc69ce614ee49c381ed193c", + "score": 63, + "grade": "C", + "tags": [ + "silent-except", + "fallback", + "legacy", + "dual-format", + "glue", + "bloat", + "god-component", + "monkeypatch" + ], + "findings": [ + { + "sev": "HIGH", + "loc": "app/db/database.py:22", + "text": "活动库 foreign_key_check 已有 17 条违规:16 条 publish_jobs、1 条 subtitle_jobs 指向不存在的 output_clip。" + }, + { + "sev": "MED", + "loc": "app/db/database.py:31", + "text": "init_db 依赖结构探测而无 user_version/schema_migrations;中断后只能猜测并重跑部分 Schema/数据迁移。" + }, + { + "sev": "MED", + "loc": "app/db/database.py:644", + "text": "索引创建失败被 sqlite.Error 静默吞掉;旧唯一索引先删除,可能在缺约束状态继续启动。" + }, + { + "sev": "MED", + "loc": "app/db/database.py:967", + "text": "旧表只 ALTER 加列,不会补齐缺失 FK/UNIQUE/ON DELETE,历史约束不完整会永久保留。" + }, + { + "sev": "MED", + "loc": "scripts/backup_restore.py:359", + "text": "备份验证只检查主 DB、quick_check 和四张表,不检查 FK、完整 Schema、索引或迁移版本。" + }, + { + "sev": "MED", + "loc": "app/services/database_backup_service.py:287", + "text": "Schema 迁移备份未把 WAL/SHM sidecar 纳入完整清理/验证;已有大量 tmp/wal/shm 运维噪声。" + }, + { + "sev": "MED", + "loc": "scripts/backup_restore.py:529", + "text": "restore 核心无进程锁/活动连接检查,绕过 PowerShell 包装时可在活跃 WAL 写入下替换数据库。" + }, + { + "sev": "MED", + "loc": "scripts/migrate_task_dirs_to_project_names.py:225", + "text": "目录迁移先移动文件再更新 DB,无文件回滚且未复用统一连接设置,中断会使磁盘/DB 不一致。" + }, + { + "sev": "LOW", + "loc": "app/services/storage_service.py:197", + "text": "路径查询绕过统一连接且静默吞 DB 错误,可能错误 fallback 到 task_id 目录。" + }, + { + "sev": "LOW", + "loc": "scripts/backup_restore_runtime.py:143", + "text": "运行时通过 monkeypatch 改写 backup core,全量/运行时两套入口因导入顺序产生不同语义。" + }, + { + "sev": "MED", + "loc": "tests/test_p0_security.py:352", + "text": "FK 测试不断言 foreign_keys=1 也不执行 foreign_key_check,现有 17 条违规无法被测试发现。" + }, + { + "sev": "MED", + "loc": "app/db/database.py:31", + "text": "database.py 聚合 Schema、迁移、索引、数据修复、种子与备份触发,回归半径覆盖全应用。" + }, + { + "sev": "LOW", + "loc": "scripts/backup_restore.py:253", + "text": "备份 CLI 默认包含 .env 且不加密;备份被同步/共享时会扩大 Secret 外泄面。" + } + ], + "auditedHash": "18a27dfc8ba1bbf86653d9709070d1f221505a53bdc69ce614ee49c381ed193c", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + }, + { + "id": "ops_delivery", + "label": "Ops & Delivery", + "band": "persistence", + "path": "Docker/PowerShell/CI", + "paths": [ + "Dockerfile", + "docker-compose*.yml", + ".github/**/*.yml", + "scripts/**/*.ps1", + "scripts/doctor.ps1", + "scripts/seed_demo_data.py", + "scripts/purge_deleted_task_media.py", + "scripts/migrate_task_dirs_to_project_names.py" + ], + "coupling": "med", + "deps": [ + "api_runtime", + "publisher_worker", + "persistence" + ], + "desc": "提供 Windows/Docker 启停、诊断、CI、发布门禁、Demo 与维护脚本。", + "tests": [ + "tests/test_native_scripts.py", + "tests/test_release_version.py", + "tests/test_publish_worker_autostart.py" + ], + "loc": 4347, + "contentHash": "a15f8a498170bdd4b4d07b5f653330431297ac6a58c77b1466255bb820ae0c03", + "score": 68, + "grade": "C", + "tags": [ + "fallback", + "legacy", + "dual-format", + "duplication", + "bloat", + "glue", + "silent-except", + "monkeypatch" + ], + "findings": [ + { + "sev": "MED", + "loc": ".github/workflows/ci.yml:3", + "text": "CI 仅监听 master push/PR;feature/docs 直接 push 不即时验证,问题延迟到开 PR。" + }, + { + "sev": "MED", + "loc": "scripts/start_docker_opencli.ps1:24", + "text": "健康检查异常只警告不返回非零,服务失败也可能被调用方视为启动成功。" + }, + { + "sev": "MED", + "loc": "scripts/start.ps1:30", + "text": "Demo 覆盖生效前先对正式 E 盘配置运行 doctor,新机/迁移机的隔离 Demo 可能被旧路径阻断。" + }, + { + "sev": "MED", + "loc": "scripts/start.ps1:71", + "text": "Worker 启动异常被捕获后继续并报告工作台成功,发布能力不可用时易被误判完整健康。" + }, + { + "sev": "HIGH", + "loc": "scripts/migrate_task_dirs_to_project_names.py:211", + "text": "任务目录迁移用非 WAL-aware 主库 copy,先移动目录再统一更新提交,无文件补偿,异常会让路径/DB 不一致。" + }, + { + "sev": "MED", + "loc": "scripts/restore.ps1:27", + "text": "恢复只以健康响应判断 App 运行,StopServices 只停 Compose;native/异常健康进程可能仍持有 DB 时执行替换。" + }, + { + "sev": "MED", + "loc": "scripts/backup.ps1:33", + "text": "备份默认包含 .env 且不加密,仅显式 ExcludeEnv 才排除,包被共享时泄漏本机秘密。" + }, + { + "sev": "MED", + "loc": "tests/test_native_scripts.py:11", + "text": "启停测试主要是源码字符串断言,Windows smoke 不做实际 restore/冲突/清理失败与恢复后健康验证。" + }, + { + "sev": "MED", + "loc": "scripts/seed_demo_data.py:32", + "text": "缺 FFmpeg 时仍插入无媒体路径 Demo 数据并成功退出,数量检查通过但媒体 smoke 不可信。" + }, + { + "sev": "LOW", + "loc": "scripts/acceptance.ps1:115", + "text": "验收递归扫描正式任务目录,媒体量大/锁文件会显著拖慢或阻断 release gate。" + }, + { + "sev": "LOW", + "loc": "scripts/backup_restore_runtime.py:142", + "text": "import 时 monkeypatch backup core,全局行为依赖导入顺序。" + }, + { + "sev": "LOW", + "loc": "docs/PORTABLE_SETUP.md:127", + "text": "文档描述与当前 start.ps1 实现不符,旧 Next Steps 又保留兼容入口,启动排障认知漂移。" + } + ], + "auditedHash": "a15f8a498170bdd4b4d07b5f653330431297ac6a58c77b1466255bb820ae0c03", + "auditedAt": "2026-08-24", + "auditedRev": "38fd60386a609b6ea85a3eb511b83cb296f4d2a1" + } + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 15d015f..458ac2f 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,9 @@ tasks/* .DS_Store Thumbs.db + +# 本地工程审计产物 +.scannerwork/ +.coverage +coverage.xml +test-results.xml diff --git a/AUDIT_TASK.md b/AUDIT_TASK.md new file mode 100644 index 0000000..8c7323e --- /dev/null +++ b/AUDIT_TASK.md @@ -0,0 +1,53 @@ +# NiuMa Studio 全面工程体检任务书 + +## 背景 + +当前项目为 Windows 本地 AI 高光生产后台 V2.1.0。本轮联合使用 Codemap、Code Overhaul 与本地 SonarQube,回答项目为什么能运行、哪些部分可靠、哪些部分存在高风险技术债,以及应按什么顺序低风险整改。 + +## 目标 + +- 建立当前项目的功能模块图、依赖图、核心数据流与状态流。 +- 审计架构、业务、代码、数据、稳定性、测试、安全、性能、可观测性和维护性。 +- 运行现有安全测试、Lint、覆盖率与 SonarQube 静态扫描。 +- 交叉验证三方结果并生成 `PROJECT_AUDIT.md`。 + +## 允许修改范围 + +- `.codemap/` 下的 Codemap 状态、配置和生成报告。 +- `sonar-project.properties` 等仅用于本轮静态扫描的工程配置。 +- `AUDIT_TASK.md`、`PROJECT_AUDIT.md`。 +- 按项目规则追加 `DEVELOPMENT_LOG.md`、`NEXT_STEPS.md` 的审计记录。 + +## 禁止修改范围 + +- `app/`、`scripts/`、`tests/`、`prompts/` 中的生产逻辑和测试逻辑。 +- 数据库 Schema、Migration、真实数据、浏览器登录态、发布队列和任务文件。 +- `.env`、Cookie、Token、API Key、浏览器数据及任何秘密内容。 +- 依赖版本、运行时行为、外部平台状态与远端系统。 + +## 已确定实现要求 + +- Codemap 以功能模块为单位,核心/高耦合模块独立评分,小型叶子模块可同一子任务内分别评分。 +- Code Overhaul 使用 FULL AUDIT 模式,不在各章节暂停整改。 +- SonarQube 优先复用现有本地容器;无法获得的指标必须明确写为“未取得”,不得估算成 Sonar 指标。 +- Dead/Legacy/Mock/兼容代码只列出,不删除。 +- 自动化测试不得连接真实 AI Provider 或触发真实投稿。 + +## 验收标准 + +- `PROJECT_AUDIT.md` 包含用户要求的全部章节、100 分健康度、Sonar 指标、P0-P3、Top 10、删除候选、暂不修改区和可独立回滚的整改路线图。 +- 关键发现有 `file:line` 证据;Sonar 事实与人工审查结论明确区分。 +- 记录实际测试命令、退出码、通过/失败/跳过数量和覆盖率。 +- 最终 `git diff` 不包含生产代码、数据库、日志、缓存或敏感信息。 + +## 测试与扫描命令 + +- `.venv\Scripts\python.exe -m pytest` +- `.venv\Scripts\python.exe -m ruff check app tests scripts/seed_demo_data.py scripts/backup_restore.py scripts/backup_restore_runtime.py` +- `.venv\Scripts\python.exe -m coverage run -m pytest`(仅在 Coverage 可用时) +- Sonar Scanner(使用本地 SonarQube 与隔离的扫描配置) + +## 返回格式 + +- 子代理返回精确命令、退出码、耗时与 `file:line` 证据。 +- 主代理统一输出 `PROJECT_AUDIT.md`,并说明审计产物、测试结果、Git 提交、推送与 PR 状态。 diff --git a/DEVELOPMENT_LOG.md b/DEVELOPMENT_LOG.md index fede4c0..ded35e9 100644 --- a/DEVELOPMENT_LOG.md +++ b/DEVELOPMENT_LOG.md @@ -1,5 +1,17 @@ # Development Log +## 2026-08-24 全项目工程体检:Codemap + Code Overhaul + SonarQube + +- 按“只审计、不整改”边界完成 13 个功能模块的 Codemap 独立评分,并生成 `.codemap/modules.json`、`.codemap/codemap.html` 和 `.codemap/codemap.md`。 +- 完成架构、业务状态、数据库、并发恢复、AI/转写、发布安全、性能、测试和 Dead/Legacy 的 Code Overhaul 全量 Review。 +- 使用本机 SonarQube Community Build 26.8 和官方 Scanner 镜像实扫 `app/`、`scripts/`:38,963 ncloc,29 Bugs、2 Vulnerabilities、0 Hotspots、479 Code Smells、0.2% Duplication,维护性 A、可靠性 C、安全性 D,技术债估算约 58 小时。 +- Sonar Coverage 为 0% 是因为项目没有生成 `coverage.xml`;JUnit 已导入 500 项测试且成功率 100%,报告没有把“无覆盖率输入”误写成“代码没有测试”。 +- 隔离测试通过:`500 passed, 9 warnings`;Ruff、Python 编译、前端 JS 语法、PowerShell Parser、三组 Docker Compose 配置和 `pip check` 均通过。Ruff format check 显示 105 个文件会被重排,本轮没有格式化。 +- 只读核验当前原生服务与 Windows 发布 Worker 均在 `127.0.0.1` 健康;没有调用真实 AI、没有计费生成、没有真实投稿。 +- 发现并记录 P0:测试 fixture 在错误数据库环境下可整表删除、活动 SQLite 已有 17 条外键违规、永久删除的文件系统操作无法随数据库事务回滚。 +- 项目综合健康度定为 `59/100`,成熟度为“可用 V1”;完整证据、技术债 Top 10、删除候选、暂时不要动的区域和独立可回滚路线图见 `PROJECT_AUDIT.md`。 +- 本轮仅新增审计文档、Codemap 状态/可视化和 Sonar 本地扫描配置;没有修改生产业务代码、Schema、真实数据或发布流程。 + ## 2026-08-24 字幕审核、异步渲染与自动流水线整合(PR 4) - 全自动流水线在切片后创建原片/切片字幕草稿,并停在 `pending_subtitle_review`;不会继续生成文案或发送任务。 diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md index 528b504..10eda65 100644 --- a/NEXT_STEPS.md +++ b/NEXT_STEPS.md @@ -1,5 +1,20 @@ # Next Steps +## 2026-08-24 工程体检后的整改顺序(等待用户确认) + +本轮审计已经完成,先停止修改生产代码。请先阅读 `PROJECT_AUDIT.md`,确认以下顺序后再逐轮整改;不要一次性全面重构: + +1. **P0.1 测试数据库保护**:让 pytest 只能使用唯一临时库,发现活动库路径立即拒绝启动。 +2. **P0.2 数据一致性**:先做 WAL-aware 备份和 dry-run,再逐条处理活动库 17 条外键违规;修复前不要手工删记录。 +3. **P0.3 可恢复删除**:把“直接删文件后提交数据库”改成带隔离区和 manifest 的两阶段删除。 +4. **P1.1 Secret 与本地鉴权**:读取接口不再返回原始 API Key/OAuth Token;非 loopback 部署要求明确保护。 +5. **P1.2 路径边界**:统一任务目录、媒体响应和 Worker execution/account id 的允许根与字符校验。 +6. **P1.3 Job/Publish fencing**:旧 Worker/旧 execution 不得覆盖新 attempt,重复 execution id 不得再次投稿。 +7. **P1.4 状态与部分成功**:逐步封住任务状态跳跃、切片/字幕批次半提交、转写旧结果复用和 AI partial 成本边界。 +8. P0/P1 稳定后,再补 Coverage、真实故障测试、readiness/日志,然后渐进拆分 `publish_service.py` 与前端大脚本。 + +整改期间继续遵守:每轮范围有限、独立测试、独立提交、可回滚;不更换 SQLite、不引入微服务/React/Vue、不删除历史 Publisher、不改掉 `NEED_REVIEW` 人工复核。 + ## 2026-08-23 长直播四阶段进度 - [x] PR 1:模式必选、已有文件入口、媒体/磁盘预检、持久化重型 Job、转写断点与词级时间戳。 diff --git a/PROJECT_AUDIT.md b/PROJECT_AUDIT.md new file mode 100644 index 0000000..70f80a7 --- /dev/null +++ b/PROJECT_AUDIT.md @@ -0,0 +1,750 @@ +# NiuMa Studio 全项目工程体检报告 + +> 审计日期:2026-08-24 +> +> 审计基线:`38fd60386a609b6ea85a3eb511b83cb296f4d2a1` +> +> 审计分支:`docs/project-engineering-audit` +> +> 方法:Codemap 独立模块审计 + Code Overhaul 全量工程 Review + SonarQube Community Build 26.8 实扫 +> +> 边界:本轮只审计、运行隔离测试并生成文档/审计配置;没有修改生产业务代码、数据库 Schema 或真实发布流程。 + +## 1. Executive Summary + +### 结论 + +当前项目健康度为 **59 / 100**,成熟度属于 **可用 V1**。 + +它已经明显超过 Demo:真实的素材接入、转写、AI 选片、人工审核、切片、字幕审核、内容准备、排期和 Windows Chrome Worker 发布链路都存在;当前本机的 FastAPI 服务和发布 Worker 也都在 `127.0.0.1` 正常监听并返回健康状态。500 项隔离测试全部通过,Ruff、Python 编译、前端 JavaScript 语法、PowerShell 脚本解析和 Docker Compose 配置检查也通过。 + +但它还不能称为“稳定 V1”。原因不是页面不好看或代码格式不统一,而是几个关键失败边界尚未封口:测试数据库隔离可被外部环境变量绕过,活动 SQLite 库已经存在 17 条外键不一致,长任务租约缺少 fencing,文件删除与数据库事务无法原子回滚,发布/AI 配置读取接口可能返回原始密钥,路径边界、状态转移和部分成功恢复仍较依赖调用顺序。 + +### 这个项目为什么现在能够运行 + +1. **运行模型简单且适合个人本地工具。** FastAPI、SQLite、文件系统、FFmpeg 和后台 Job 均在一台 Windows 主机上,只有真实发布被明确隔离到 Chrome Worker;没有不必要的微服务、Kafka、Kubernetes、CQRS 或 Event Sourcing。 +2. **有一条真实闭环。** 系统不是展示型壳子;任务可从素材一路走到可发布视频和排期记录。 +3. **已经有可靠性骨架。** SQLite 启用了 WAL、外键和 busy timeout;长任务有持久化 Job、lease、heartbeat、checkpoint;切片/字幕有版本记录;发布不确定时会进入 `NEED_REVIEW`,不会把“可能成功”伪装成确定成功。 +4. **文件产物帮助恢复。** 音频、转写、分析、切片、字幕、封面和发布包分目录保存,许多步骤可以从已有产物继续。 +5. **测试数量和工程脚本已经形成保护网。** 500 项测试本轮全过,CI 还覆盖 Windows/Docker smoke 的入口。 + +### 哪些部分可靠,哪些部分只是尚未暴露问题 + +| 判断 | 代表区域 | 依据 | +| --- | --- | --- | +| 相对可靠 | SQLite WAL、基础 Job 去重、发布任务条件抢占、上传失败清理、发布后不确定状态 | 有明确事务/状态、现有测试和代码证据 | +| 有条件可靠 | 字幕 revision、视频版本、AI 分析、转写 checkpoint、自动流水线 | 正常顺序可用,但并发、重启、旧 Worker、部分提交时边界不完整 | +| 高风险侥幸 | 测试库隔离、现有外键一致性、原始密钥读取、路径解析、删除事务、租约 fencing | 已发现可复现的代码路径或真实数据证据,不是代码风格问题 | + +### 审计限制 + +- 本轮没有调用真实 AI Provider、没有产生计费请求、没有真实投稿抖音/B站,也没有绕过登录、验证码或平台风控。 +- 没有执行 Docker 镜像重建;已验证三组 Compose 配置合法。 +- 没有运行完整真实素材 E2E,因此“500 tests passed”不能替代一次真实长视频和真实账号验收。 +- 项目未安装 Coverage 工具,也没有生成 `coverage.xml`。SonarQube 的 0% 是“没有覆盖率输入”,不是测试实际覆盖率为零。 +- 依赖过期查询遇到 PyPI SSL EOF,未得到可靠的最新版本清单;`pip check` 已通过。 + +## 2. 项目架构图 + +### 2.1 运行拓扑 + +```mermaid +flowchart LR + U[本地用户 / 浏览器] --> UI[Jinja2 + 原生 JS/CSS] + UI --> API[FastAPI Routers] + API --> TS[Task / Review / Cut] + API --> SUB[Subtitle Workbench] + API --> PUB[Publish Center] + + TS --> FS[任务目录 / E 盘素材] + TS --> TR[FFmpeg + Transcription] + TR --> AI[AI Provider + Clip Analyzer] + AI --> CUT[审核 + 视频切片] + CUT --> SUB + SUB --> PUB + + TS --> JOB[Workflow Jobs / Lease / Heartbeat] + TR --> JOB + AI --> JOB + CUT --> JOB + SUB --> JOB + + PUB --> SCH[SQLite Publish Scheduler] + SCH --> REG[Publisher Registry] + REG --> WK[Windows Chrome Worker :8765] + WK --> PLAT[抖音 / B站 / 本地导出] + + DB[(SQLite WAL)] --- API + DB --- JOB + DB --- SCH + FS --- TR + FS --- CUT + FS --- SUB + FS --- WK +``` + +### 2.2 核心模块与职责 + +| 模块 | 职责 | 关键依赖 | Codemap 判断 | +| --- | --- | --- | --- | +| Frontend UI | 任务、审核、字幕、发送中心界面 | API Runtime | 大脚本/God Component,存在鉴权绕行和 `innerHTML` 风险 | +| API Runtime | 启动、配置、路由、健康检查 | 几乎全部业务模块 | 本地单机适配合理,但鉴权、状态边界和同步长任务混杂 | +| Media & Storage | 任务目录、上传、路径、FFmpeg 进程 | Persistence | 正常路径完整,路径安全、非原子命名和删除回滚有缺口 | +| Transcription | 音频提取、本地/火山转写、分块、checkpoint | Media、AI config | 职责过宽;旧转写复用、取消传播和并发元数据存在风险 | +| AI Selection | Provider、Prompt、解析、三种选片 profile | Transcription、Persistence | 宽松解析提升可用性,但会掩盖部分窗口失败和重复调用成本 | +| Task / Review / Cut | 状态、候选审核、切片版本、发送中心同步 | AI、Media、Publish | 核心 God Service;状态跳跃、半提交和查询口径重复 | +| Subtitle | track/revision/cue、AI 建议、渲染、交付门禁 | Cut、FFmpeg、Publish | 设计方向正确,但批量批准和并发激活不是整体原子 | +| Pipeline & Queue | 自动流程、持久化 Job、子进程、lease | 所有处理模块 | 有恢复骨架,缺 owner fencing 与 step 级副作用 checkpoint | +| Publish Center | 文案、封面、账号、草稿、历史、兼容发布 | AI、Cut、Scheduler | 后端最大热点,密钥 DTO 和历史兼容边界是主要风险 | +| Publish Scheduler | 排期、抢占、重试、恢复、人工复核 | Publisher、Worker、DB | 状态策略成熟,但后台 task 引用、全量扫描和幂等仍需加强 | +| Publisher Worker | Chrome、账号、执行日志、真实平台边界 | Scheduler、文件系统 | 正确保留人工确认,但 execution id 幂等和路径字符校验不足 | +| Persistence | Schema、启动迁移、索引、备份恢复 | 全系统 | WAL/备份是优点;无版本账本、索引异常吞掉、真实库已有孤儿关系 | +| Ops & Delivery | CI、启动、备份、诊断、Docker | Runtime、Persistence | 脚本丰富,但文档历史层叠、工具版本与环境存在漂移 | + +### 2.3 Codemap 模块健康评分 + +13 个功能模块均由独立只读子任务按同一固定量表评分:结构 20、正确性 20、可维护性 20、测试 15、性能 15、安全 10。平均分为 **62.6 / 100**;10 个 C、3 个 D,没有 A/B。 + +| 模块 | LoC | 耦合 | 分数 | 等级 | 核心判断 | +| --- | --: | --- | --: | :--: | --- | +| Frontend UI | 13,840 | High | 65 | C | 能支撑当前 UI,但大脚本、XSS 和鉴权请求口径需收敛 | +| API Runtime | 2,713 | Core | 63 | C | 启动简单;鉴权、状态 API 和同步长任务边界不足 | +| Media Storage | 1,300 | High | 69 | C | 正常路径完整;允许根、命名和删除原子性有风险 | +| Transcription | 1,930 | High | 58 | D | 旧结果复用、取消、并发元数据和职责过宽 | +| AI Selection | 4,420 | Core | 63 | C | 兼容强,但 partial/fallback/Secret context 风险明显 | +| Task / Review / Cut | 2,436 | Core | 61 | C | 状态跳跃、半提交、重复查询和测试隔离风险 | +| Subtitle | 2,391 | High | 70 | C | revision 设计较好;批次、并发激活和真实媒体测试不足 | +| Pipeline / Queue | 1,745 | Core | 58 | D | 缺 lease fencing、step checkpoint 和稳定 READY 状态 | +| Publish Center | 5,561 | Core | 52 | D | Secret 泄漏 + 最大 God Service + 多种隐式 partial | +| Publish Scheduler | 1,754 | Core | 62 | C | 发布安全思想正确;execution fencing、后台 Task 和恢复竞态不足 | +| Publisher Worker | 2,116 | High | 62 | C | 真实边界清楚;execution id 幂等/路径和 journal 脱敏不足 | +| Persistence | 3,023 | Core | 63 | C | WAL/备份是优点;真实 FK 违规、迁移账本和索引错误处理是核心债 | +| Ops / Delivery | 4,347 | Medium | 68 | C | CI/脚本较完整;恢复、Demo fail-open、默认含 `.env` 和文档漂移 | + +Codemap 分数高于总健康度 59 分,是因为总健康度额外纳入了“活动数据库已经发生 FK 违规”和“测试误指真实库可整表删除”这两项运行态 P0 证据,而模块评分主要评价对应代码边界。 + +### 2.4 核心业务和状态流 + +```text +素材进入 + → 媒体预检 / 任务目录 + → 音频提取 + → 带时间戳转写 + → AI 分段选片 + → 人工审核候选 + → 生成并验证切片 + → 字幕草稿 + → 人工审核并烧录,或明确跳过 + → 标题 / 简介 / 话题 / 封面 + → 创建排期 + → Scheduler 抢占任务 + → Publisher Registry + → Windows Chrome Worker + → PUBLISHED / FAILED / NEED_REVIEW / CANCELLED +``` + +关键状态并不是一个统一状态机,而是四组并行状态: + +- `tasks.status`:大写自动流水线状态 + 小写手动流程状态。 +- `workflow_jobs.status`:`queued/running/completed/failed/cancelled`。 +- `subtitle_jobs.status` 与 revision 激活状态。 +- `publish_jobs.status`:`DRAFT/WAITING/SCHEDULED/PUBLISHING/PUBLISHED/EXPORTED/FAILED/NEED_REVIEW/CANCELLED`。 + +这解释了系统为什么能承载多步流程,也解释了恢复困难的来源:一个用户动作可能同时更新任务主状态、Job 状态、文件产物、字幕 revision 和发布草稿。 + +### 2.5 修改影响半径 + +| 修改点 | 可能被影响的下游 | +| --- | --- | +| `task_service.py` | 任务详情、候选审核、切片、字幕、发送中心同步、Dashboard | +| `publish_service.py` | 文案、封面、账号、草稿、历史、兼容 Publisher、页面上下文 | +| `database.py` | 启动、所有 Service、迁移、索引、Prompt 种子、备份恢复 | +| `job_service.py` / `job_worker.py` | 转写、切片、自动流水线、字幕以及未来 Job 类型 | +| `storage_service.py` | 上传、媒体预览、任务删除、转写、切片、发布 Worker | +| `app.js` / `publish-center.js` | 多个页面的写请求、轮询、状态呈现和本地鉴权 | + +## 3. 项目健康度 + +| 维度 | 分数 | 依据 | +| --- | --: | --- | +| 架构合理性 | 7/10 | 单体 + SQLite + Worker 符合个人本地项目规模;没有过度设计。扣分来自 Service 直接访问 DB、动态导入和几个 God Service。 | +| 业务逻辑 | 7/10 | 真实闭环完整,发布不确定状态处理正确;扣分来自任意状态跳跃、部分成功和四套状态并行。 | +| 代码质量 | 5/10 | Ruff/编译通过;但 Sonar 479 个 Smell、聚合 Cognitive Complexity 7307,多个 1k-4k 行热点。 | +| 数据设计 | 4/10 | WAL、FK、备份存在;但真实库 17 条 FK 违规、迁移无版本账本、索引失败被吞、部分操作非原子。 | +| 稳定性 | 5/10 | Job、checkpoint、NEED_REVIEW 提供恢复骨架;lease fencing、取消、旧产物复用和文件/DB 原子性不足。 | +| 测试 | 7/10 | 500 项全过,覆盖多种核心服务;但无 Coverage 数据、真实 E2E 不完整,且存在可清空真实库的隔离风险。 | +| 安全性 | 4/10 | `.env` 被忽略、路径删除有部分保护;但原始密钥读取、默认无写鉴权、DOM XSS、路径穿越/本地文件响应构成高价值问题。 | +| 性能与资源 | 7/10 | 当前单机规模没有明确系统性瓶颈;扣分来自高频轮询、全量排期扫描、完整 PCM/大文件内存和重复 AI fallback。 | +| 可观测性 | 6/10 | 有任务日志、进度、checkpoint、Scheduler/Worker health 和 NEED_REVIEW;但 `/health` 过浅,子进程 stderr 被丢弃,静默 fallback 多。 | +| 文档与可维护性 | 7/10 | README、架构、流程、开发日志和 CI 文档丰富;但历史段落与当前行为混排,少量模块说明已过时。 | +| **总分** | **59/100** | **可用 V1:正常路径可用,异常和并发边界尚不足以称为稳定 V1。** | + +## 4. SonarQube 客观指标 + +### 4.1 扫描信息 + +- Server:SonarQube Community Build `26.8.0.126808` +- Scanner:SonarScanner CLI `8.0.1.6346`(官方 Docker 镜像) +- Project key:`niuma-studio-local-audit` +- 扫描范围:`app/`、`scripts/`,测试目录为 `tests/`;vendor、图片、手工真实 Provider 脚本和缓存已排除。 +- 有效代码:`38,963` ncloc。 +- 分析任务:成功;服务器处理耗时约 17 秒,首次完整 Scanner 运行约 15 分 55 秒。 +- 本地 Dashboard:`http://127.0.0.1:9000/dashboard?id=niuma-studio-local-audit` +- 审计环境提示:Sonar 端口只映射到 `127.0.0.1`,但默认管理员凭据仍有效;扫描用临时 Token 已在查询结束后撤销。应单独修改 Sonar 管理员密码,这不属于 NiuMa 生产代码整改。 + +### 4.2 指标 + +| 指标 | 实测 | 解读 | +| --- | ---: | --- | +| Bugs | 29 | 其中大量是 HTML 语义容器/label 规则;真正优先核对的是 Scheduler 后台 Task 引用和少数 Python 规则。 | +| Vulnerabilities | 2 | 一项是自定义临时目录安全,一项是允许人工填 `http://` 平台链接;都需上下文审查,不等于已被攻击。 | +| Security Hotspots | 0 | Sonar 未报 Hotspot,但源码 Review 仍发现密钥响应、DOM XSS、路径和可选鉴权问题。 | +| Code Smells | 479 | 主要由 FastAPI 未声明响应码 128 项、CSS 属性/兼容规则、复杂度和重复字符串构成。 | +| Duplication | 0.2% | 总体很低;局部最高是 `transcript_service.py` 3.6%、`task_query_service.py` 3.3%。 | +| Coverage | 0.0% | **无 `coverage.xml` 输入**;不能作为真实覆盖率。JUnit 已导入 500 项测试且 100% 成功。 | +| Cognitive Complexity | 7,307 | 聚合值;说明复杂度集中明显,不代表每个文件都差。 | +| Cyclomatic Complexity | 7,608 | 聚合值;需结合文件/函数热点使用。 | +| Maintainability Rating | A | 技术债比率 0.3%;规则估算对本项目偏乐观。 | +| Reliability Rating | C | 与 29 个 Bug 指标对应。 | +| Security Rating | D | 与 2 个 Vulnerability 指标对应。 | +| Technical Debt | 3,499 分钟 | 约 58 小时 19 分;这是规则修复估算,不是整改项目工期。 | +| Quality Gate | OK | 当前 Gate 返回 `conditions=[]`,因此“OK”没有实质门禁含义。 | + +问题严重度总数:4 Blocker、134 Critical、316 Major、56 Minor,共 510 项。这里的严重度是 Sonar 规则严重度,不直接等同本报告的 P0/P1。 + +### 4.3 复杂度热点 + +| 文件 | ncloc | Cognitive | Cyclomatic | 判断 | +| --- | ---: | ---: | ---: | --- | +| `app/services/publish_service.py` | 4,245 | 965 | 1,001 | 全项目最大后端热点;同时承担多种领域职责。 | +| `app/static/js/publish-center.js` | 2,104 | 931 | 796 | 页面状态、轮询和事件高度集中。 | +| `app/static/js/app.js` | 2,298 | 585 | 935 | 多页面全局脚本,鉴权和错误处理口径不一。 | +| `app/services/publish_scheduler.py` | 1,340 | 295 | 281 | 调度、claim、恢复和终态集中。 | +| `app/services/subtitle_data_service.py` | 1,176 | 288 | 270 | track/revision/cue/导入导出/波形职责过宽。 | +| `app/services/transcript_service.py` | 1,187 | 280 | 251 | FFmpeg、Provider、分块、进度、解析集中。 | +| `app/services/ai/ai_clip_analyzer.py` | 760 | 267 | 211 | Prompt、解析、修复、归一化集中。 | +| `app/db/database.py` | 1,563 | 141 | 147 | 行数极大但复杂度不是首位;主要风险是迁移/一致性而非分支数。 | + +Sonar 单函数最高确认项包括: + +- `publish_service.py:2067`:Cognitive Complexity 68。 +- `subtitle_data_service.py:950`:47。 +- `auto_publish_service.py:22`:41。 +- `subtitle_data_service.py:871`:41。 +- `long_live_talk_analyzer.py:83`:39。 +- `job_worker.py:173`:32。 + +### 4.4 值得修与低价值规则 + +值得修: + +- `publish_scheduler.py:1403` 创建后台 Task 后不保存引用:属于真实生命周期风险,应和运行架构一起修。 +- Cognitive Complexity 超过 30 且位于发布、Job、字幕、AI 主路径的函数:先用 characterization test 锁定行为,再小步拆分。 +- 密钥、路径、状态、并发和数据一致性问题:即使 Sonar 没报,也应优先于格式/响应文档规则。 +- CSS 对比度和缺少 label:影响可访问性,可在 P2/P3 独立处理。 + +低价值或需人工确认: + +- 128 个 `python:S8415` 要求在 FastAPI `responses` 中声明每个异常码,主要改善 OpenAPI 文档,不能排在状态一致性之前。 +- 大量重复“任务不存在”“BEGIN IMMEDIATE”字符串,不应为了规则分数强行抽象成全局常量。 +- `publish_scheduler.py:961` 允许人工确认链接使用 HTTP:建议收紧为 HTTPS,但它不是当前最主要的系统安全风险。 +- `storage_service.py:79` 的临时目录规则需要结合 Windows ACL 和实际 E 盘目录判断,不能仅凭规则就改动存储布局。 +- HTML `dt/li` 容器和 label 问题应修,但不应把 29 个 Bugs 机械解释为 29 个核心业务缺陷。 + +Sonar 官方说明:Python Coverage 需要先由外部测试工具生成报告,并通过 `sonar.python.coverage.reportPaths` 导入;Scanner 本身不会计算覆盖率。参考 [SonarScanner CLI](https://docs.sonarsource.com/sonarqube-server/analyzing-source-code/scanners/sonarscanner) 与 [Python test coverage](https://docs.sonarsource.com/sonarqube-server/analyzing-source-code/test-coverage/python-test-coverage)。 + +## 5. 问题优先级 + +### P0:先消除数据损坏和不可逆风险 + +#### P0-1 测试可在错误环境下清空真实数据库 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `tests/conftest.py:11-14`;`tests/test_task_query_service.py:175-195` | +| 模块 | 测试体系 / Persistence | +| 来源 | Codemap + Code Overhaul 共同发现 | +| 原因 | `conftest.py` 使用 `os.environ.setdefault`,外部已有 `DATABASE_PATH` 时不会强制切换到测试库;该测试的 autouse fixture 每个用例前后无条件删除 8 张核心表。 | +| 实际影响 | 如果 pytest 继承活动库路径,真实任务、候选、Job、切片、字幕和发布索引可能被直接清空。 | +| 发生概率 | 日常 CI 低;开发者终端已设置生产 `DATABASE_PATH` 后手工跑测试时中等。 | +| 修改收益 / 成本 / 风险 | 收益极高;成本低;修改测试基础设施风险低。 | +| Blast Radius | 整个活动 SQLite 数据库。 | +| 推荐方案 | 测试启动时强制创建唯一临时目录和数据库;若路径不是 pytest 临时目录或文件名命中活动库则 fail closed;给清理 fixture 加防误删断言。 | + +本轮 500 项测试使用了显式临时 `test_workflow.sqlite3`,所以此次测试没有以活动库为目标;这不降低代码本身的风险等级。 + +#### P0-2 活动 SQLite 已存在 17 条外键不一致 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | 运行态 `data/workflow.sqlite3` 的只读 `PRAGMA foreign_key_check`;Schema 位于 `app/db/database.py` | +| 模块 | Persistence / Publish / Subtitle | +| 来源 | Code Overhaul + 真实数据库只读核验 | +| 原因 | 历史删除/迁移/兼容流程允许发布和字幕记录继续引用已不存在的 `output_clip`。 | +| 实际影响 | 16 条 `publish_jobs → output_clip` 和 1 条 `subtitle_jobs → output_clip` 违规;其中发布记录含 14 条 `CANCELLED`、2 条 `NEED_REVIEW`。复核、详情联查和未来约束迁移可能失败。 | +| 发生概率 | 已经发生,不是推测。 | +| 修改收益 / 成本 / 风险 | 收益极高;成本中;直接修数据风险高,必须先确定保留历史还是恢复父记录。 | +| Blast Radius | 发布历史、人工复核、字幕记录和后续 Schema 迁移。 | +| 推荐方案 | 先做 WAL-aware 备份和 dry-run 报表;逐条判定“恢复父记录 / 将引用置空 / 归档历史”;修复后要求 `foreign_key_check` 为 0,再加回归检查。 | + +补充:`PRAGMA quick_check` 返回 `ok`,说明数据库文件页结构正常;“文件没坏”与“关系一致”是两件事。 + +#### P0-3 永久删除把文件系统操作放在数据库提交之前 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/services/task_lifecycle_service.py:299-372`;`app/services/storage_service.py:533-554` | +| 模块 | Media Storage / Task Lifecycle / Persistence | +| 来源 | Codemap + Code Overhaul 共同发现 | +| 原因 | `BEGIN IMMEDIATE` 后先执行 `shutil.rmtree()`,随后才更新数据库并 commit;SQLite rollback 无法恢复已删除目录。 | +| 实际影响 | 文件删除成功但数据库更新/提交失败时,数据库记录回滚,托管切片、字幕和发布包却已经消失。外部唯一原片通常会保留,但派生产物仍会丢失。 | +| 发生概率 | 低,但磁盘权限、SQLite 锁、异常退出时成立。 | +| 修改收益 / 成本 / 风险 | 收益高;成本中;直接改变删除顺序有兼容风险。 | +| Blast Radius | 单个任务的全部项目托管产物。 | +| 推荐方案 | 改为“两阶段可恢复删除”:先把目标移动到项目内隔离区并记录 manifest,数据库提交后再异步清理;失败时可移回。 | + +### P1:核心安全、并发和恢复边界 + +#### P1-1 配置读取接口返回原始 Secret + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/services/publish_service.py:564-599`、`:846-956`;`app/routers/publish.py:34-76`;`app/services/ai_config_service.py:318-335`;`app/routers/settings.py:10-16` | +| 模块 | Publish Center / AI Config / API Runtime | +| 来源 | Codemap + Code Overhaul 共同发现;Sonar 未发现 | +| 原因 | `_normalize_config/_normalize_account` 从完整 DB row 开始,仅新增 masked 字段,没有移除 `client_secret/access_token/refresh_token`;AI context 也返回完整 `values`。 | +| 实际影响 | 浏览器 DOM、开发者工具、本地调用方或代理日志可读取 OAuth/API Secret。 | +| 发生概率 | 调用配置/账号接口即可触发,中高。 | +| 修改收益 / 成本 / 风险 | 收益极高;成本低;风险低,但要保证保存页面不依赖回显原值。 | +| Blast Radius | 所有 AI/ASR 配置、平台 Client Secret、全部发布账号 Token。 | +| 推荐方案 | 建立只读响应 DTO,只返回 `configured` 和 masked 值;写接口使用“空值表示不变”的专用字段;增加接口级“原始值绝不出现”测试。 | + +#### P1-2 默认写鉴权关闭,部署边界依赖启动方式 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/main.py:91-106`;`app/core/config.py:80,247`;`docker-compose.yml:14-16,34` | +| 模块 | API Runtime / Ops | +| 来源 | Codemap + Code Overhaul 共同发现 | +| 原因 | 只有 `LOCAL_ADMIN_TOKEN` 非空才验证写请求;默认值为空。Docker 内 Uvicorn 监听 `0.0.0.0`,Worker Token 也可为空。 | +| 实际影响 | 如果端口被映射到局域网,其他访问者可改任务、排期、账号和配置。个人本机 `127.0.0.1` 风险较低。 | +| 发生概率 | 当前原生服务低;Docker/LAN 场景中等。 | +| 修改收益 / 成本 / 风险 | 收益高;成本低到中;风险是启动体验改变。 | +| Blast Radius | 全部写 API 与发布操作。 | +| 推荐方案 | 原生模式继续 loopback;Docker 非 loopback 时要求 Token 或明确开发开关,启动日志给出强提醒;不要引入多用户 RBAC。 | + +#### P1-3 任务目录和媒体响应的允许根边界不闭合 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/services/storage_service.py:174-179,218-230,298-315,557-565`;`app/routers/media.py:58-77` | +| 模块 | Media Storage / API Runtime | +| 来源 | Codemap 独立审计 + Code Review | +| 原因 | `task_dir_name` 的 parts 未统一走安全相对路径校验;已存在媒体路径直接返回;媒体响应只检查 exists/is_file;目录名分配是查询后返回而非原子占位。 | +| 实际影响 | 构造异常目录名可能越出任务根;异常 DB 路径可能让媒体接口返回本地其他文件;并发任务可能获得同名目录并覆盖。 | +| 发生概率 | 正常 UI 低;导入旧数据、恶意本地请求或并发重试时中等。 | +| 修改收益 / 成本 / 风险 | 收益高;成本中;风险是旧路径兼容需盘点。 | +| Blast Radius | 任务素材、切片、字幕、发布 Worker 可见文件。 | +| 推荐方案 | 所有路径入口统一 `resolve + relative_to(allowed_root)`;目录名只允许受控片段;使用原子目录占位或 DB unique;为旧 `/workspace/tasks` 映射保留显式迁移层。 | + +#### P1-4 Workflow Job 租约没有 fencing + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/services/job_service.py:246-279,329,400-474`;`app/services/job_worker.py:38,173-237` | +| 模块 | Pipeline & Queue | +| 来源 | Codemap + Code Overhaul 共同发现 | +| 原因 | claim 时记录 `lease_owner` 和 attempt,但 progress/checkpoint/completed/failed/cancelled 更新只按 job id;旧子进程也不核对当前 owner。 | +| 实际影响 | 租约过期并被新 Worker 接管后,旧 Worker 恢复可覆盖新 attempt 的进度、checkpoint 和终态,造成 split-brain、重复 AI/FFmpeg 或错误完成。 | +| 发生概率 | 正常短任务低;休眠、长任务、卡顿、超时和进程恢复时中等。 | +| 修改收益 / 成本 / 风险 | 收益极高;成本中;风险中,需要迁移兼容现有 Job。 | +| Blast Radius | 单个 Job 及其任务、切片、字幕、AI 调用和下游发布草稿。 | +| 推荐方案 | 使用单调递增 `attempt_token`;所有 heartbeat、checkpoint、终态更新带 `WHERE id=? AND lease_owner=? AND attempt_token=? AND status='running'`;旧 Worker 更新失败后立即停止。 | + +#### P1-5 Scheduler 后台 Task 未保存引用,Worker 请求也缺少执行幂等 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/services/publish_scheduler.py:1399-1404`(Sonar `python:S7502`);`scripts/publish_host_worker.py:262-294` | +| 模块 | Publish Scheduler / Publisher Worker | +| 来源 | SonarQube + Code Overhaul 交叉发现 | +| 原因 | `asyncio.create_task()` 返回值不保存;Worker 收到相同 `execution_id` 时不先返回已完成 journal,而是再次执行发布。 | +| 实际影响 | Scheduler task 理论上可能被过早回收;网络超时或调用重试可能重复投稿,增加账号风控风险。 | +| 发生概率 | Scheduler 回收低到中;重复请求在网络异常时中等。 | +| 修改收益 / 成本 / 风险 | 收益高;成本低到中;风险低。 | +| Blast Radius | 全部排期扫描;重复执行影响单条平台内容和账号。 | +| 推荐方案 | App lifespan 显式保存/取消 scheduler task;Worker 以 execution id 建立幂等状态机,已完成直接返回,进行中返回冲突/同一结果。 | + +#### P1-6 状态可任意跳跃,切片结果可部分提交 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/services/task_lifecycle_service.py:152`;`app/routers/tasks.py:174-179,390-399`;`app/services/video_cut_workflow_service.py:18-45,248-301` | +| 模块 | Task / Review / Cut | +| 来源 | Codemap + Code Overhaul 共同发现 | +| 原因 | 状态 API 只验证枚举,不验证合法转移/前置产物;同步切片无统一 Job 去重;run number 的 MAX 与 INSERT 分离,每条 output clip 独立提交。 | +| 实际影响 | 空任务可被标记完成;重复点击可能产生并发 run;中途异常会留下部分 output clip、processing run 和已完成主状态。 | +| 发生概率 | 正常单用户低;重试、重复请求、异常写入时中等。 | +| 修改收益 / 成本 / 风险 | 收益高;成本中到高;风险中,需兼容历史状态。 | +| Blast Radius | 单任务状态、切片版本、字幕来源和发布草稿。 | +| 推荐方案 | 先建立合法转移表和 characterization tests;同步接口统一进入持久化 Job;run number 与 run 创建同事务并加 unique;整批 output clip 与 active 切换采用阶段提交。 | + +#### P1-7 转写会误复用旧结果,取消也可能变成失败 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/services/transcription_checkpoint_service.py:22-70`;`app/services/transcript_service.py:260-284`;`app/services/transcript_workflow_service.py:318-396` | +| 模块 | Transcription | +| 来源 | Codemap 独立审计 + Sonar 复杂度证据 | +| 原因 | 大文件指纹只覆盖大小和首尾各 1 MiB;`transcript.md` 存在即可判完成;宽泛异常包装会吞掉取消异常类型。 | +| 实际影响 | 中部被替换的视频可能复用旧转写;强制重转失败后旧文件仍被当当前结果;用户取消显示为 failed。 | +| 发生概率 | 文件替换/重试低到中;取消路径中等。 | +| 修改收益 / 成本 / 风险 | 收益高;成本中;风险中,重新计算全量 hash 会有大文件 IO 成本。 | +| Blast Radius | 单任务转写、AI 选片、字幕和后续切片。 | +| 推荐方案 | 使用可解释的源版本标识(文件元数据 + 分块 hash/显式导入版本);完成标记必须绑定 run;取消异常单独透传;旧结果作为历史而非隐式 current。 | + +#### P1-8 AI 部分窗口失败和协议 fallback 可能产生不完整结果及重复成本 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/services/ai/ai_clip_analyzer.py:90-158`;`app/services/ai/local_model_provider.py:19-35`;`app/services/ai/variety_comedy_analyzer.py:513-528` | +| 模块 | AI Selection | +| 来源 | Codemap + Code Overhaul 共同发现 | +| 原因 | 通用 profile 只要部分窗口产生候选就返回;全局评审失败可降级;任意 `AIProviderError` 都可能触发第二协议调用。 | +| 实际影响 | 看似成功的候选只覆盖部分素材;429/认证/超时也可能重复调用,增加 Token/费用并掩盖故障。 | +| 发生概率 | 模型格式或网络波动时中等。 | +| 修改收益 / 成本 / 风险 | 收益高;成本中;风险中,需要明确不同 profile 的产品门槛。 | +| Blast Radius | 单次 AI 任务的候选质量、费用和发布时间。 | +| 推荐方案 | 为每个 profile 定义最低窗口覆盖率和 partial 状态;只对幂等且可重试错误退避;协议 fallback 仅用于明确协议不匹配;UI 展示失败窗口与费用次数。 | + +#### P1-9 前端存在 DOM XSS,且部分写请求绕过统一鉴权封装 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/static/js/app.js:1-12,211,1729`;`app/static/js/publish-center.js:2152` | +| 模块 | Frontend UI / API Runtime | +| 来源 | Codemap + 手工源码 Review;Sonar 未发现 | +| 原因 | 外部消息/任务标题进入 `innerHTML`;许多写请求直接 `fetch`,没有经过注入 Bearer Token 的 `apiFetch`。 | +| 实际影响 | 用户输入或 API 消息可破坏页面/执行脚本;启用 `LOCAL_ADMIN_TOKEN` 后部分正常操作反而 401。 | +| 发生概率 | XSS 低到中;开启 Token 后鉴权失败高。 | +| 修改收益 / 成本 / 风险 | 收益高;成本低到中;风险低。 | +| Blast Radius | 任务详情、审核、字幕和发送中心页面。 | +| 推荐方案 | 动态文本统一 `textContent/createElement`;需要 HTML 的内容只接受白名单模板;所有 API 请求走同一封装并加超时/错误规范化。 | + +#### P1-10 启动迁移无版本账本,唯一索引创建失败被静默吞掉 + +| 字段 | 内容 | +| --- | --- | +| 文件 / 位置 | `app/db/database.py:31-532,601-648,654-965` | +| 模块 | Persistence | +| 来源 | Code Overhaul + Codemap | +| 原因 | 启动时通过表/列探测执行迁移,没有 `user_version`/ledger;部分 `executescript` 会改变事务语义;活动发布唯一索引先删除再创建,所有 `sqlite3.Error` 被 `pass`。 | +| 实际影响 | 升级失败可留下半迁移;重复历史数据导致唯一索引重建失败时,应用仍启动且不告警,后续可继续产生重复活动发布任务。 | +| 发生概率 | 新库低;历史库/异常数据升级时中等。 | +| 修改收益 / 成本 / 风险 | 收益高;成本中到高;风险高,不能直接改生产 Schema。 | +| Blast Radius | 整个数据库和应用启动。 | +| 推荐方案 | 先只增加可审计 migration ledger 和 preflight;每个版本独立 backup/verify;索引失败必须阻止相关功能或显式 degraded,不能静默继续。 | + +### P2:有限范围逐步偿还 + +| 问题 | 文件 / 模块 / 来源 | 原因与实际影响 | 概率 / Blast Radius | 收益 / 成本 / 风险 | 推荐方案 | +| --- | --- | --- | --- | --- | --- | +| 发布中心 God Service | `publish_service.py`;Codemap + Overhaul + Sonar | 文案、封面、账号、历史、兼容 Publisher 和页面上下文共处,Cognitive 965 | 修改发布功能时高;影响全部发布路径 | 高 / 高 / 中高 | 先按只读查询、Secret DTO、内容准备、Job 创建、Legacy adapter 分 4-5 轮抽取,不改外部 API。 | +| 前端 God Scripts | `app.js`、`publish-center.js`;Codemap + Sonar | 全局状态、轮询、事件和请求口径集中 | UI 改动时中;多个页面 | 中高 / 中 / 中 | 先抽统一 request/polling/store,再按页面拆文件;不引入 React/Vue。 | +| 多 Service 直接 DB | 约 20 个文件;Codemap + Overhaul | Repository 边界只在发布域局部存在,事务跨 Service 难组合 | 新功能时高;跨域 | 高 / 高 / 高 | 只为需要原子操作的聚合增加小型 Repository/Unit of Work,不做全量 ORM 重写。 | +| 字幕批量部分成功 | `subtitle_auto_workflow_service.py:87-117`;Codemap | clip 逐项批准后才建 Job,中途失败留混合状态 | 中;单任务所有字幕 | 高 / 中 / 中 | 预检全量、单事务记录批次,再由 Job 执行;保留 revision 历史。 | +| 发布排期全量扫描与 5 秒轮询 | `publish_scheduler.py:260-281`、`publish-center.js:2227`;Overhaul | 每轮读取全部 SCHEDULED,页面重复请求 jobs/accounts/health | 当前低,规模增长后确定;发布中心 | 中 / 低 / 低 | SQL 直接筛选到期 + 合适索引;前端合并状态端点或退避。 | +| 超长媒体内存峰值 | `transcript_service.py:675`、`subtitle_data_service.py:761`;Overhaul/Codemap | Base64/PCM 完整载入内存后处理 | 长视频时中;单请求/进程 | 中 / 中 / 低 | 流式上传/分块编码;波形边读边降采样。 | +| 文件输出并非统一原子替换 | 上传、切片、analysis/metadata 写入;Overhaul | 崩溃/磁盘满可留下截断文件 | 低到中;单产物 | 高 / 中 / 中 | 统一 `.part` + fsync + replace + manifest;先覆盖最关键 source/clip/analysis。 | +| 可观测性不足 | `/health`、`job_worker.py:175`、多处 silent fallback;Codemap/Overhaul | 健康只看进程,子进程 stderr 丢弃,损坏数据常被当默认值 | 故障时高;诊断全链路 | 高 / 中 / 低 | 分离 liveness/readiness;保留受控 stderr 摘要和错误码;fallback 必须记录来源。 | +| 无覆盖率和类型检查 | `requirements-dev.txt`、CI;Sonar + Operator | 有 500 测试但无 line/branch coverage,mypy/pyright/tsc 不存在 | 持续;全项目 | 中高 / 中 / 低 | 先加 coverage 数据,仅设核心模块“不得下降”;Python 类型检查从新/高风险模块渐进启用。 | +| 文档当前态与历史态混排 | `docs/TASK_FLOW.md:3-14,114`、`NEXT_STEPS.md:288-309`;Overhaul | 顶部已是字幕审核恢复,旧段仍说自动模式跳过字幕 | 新人阅读时高;运维/验收 | 中 / 低 / 低 | 保留历史但显式标“历史行为”,新增唯一 Current State 页面。 | +| 环境版本漂移 | `.venv` 与 `requirements-dev.txt`;Operator | 实际 pytest 9.0.3 / Ruff 0.15.16,声明 9.1.1 / 0.16.1 | 本机与 CI 差异时中 | 中 / 低 / 低 | 重建受控 dev venv 或在 CI/本机打印并校验工具版本。 | + +### P3:低优先级与代码洁癖 + +| 问题 | 依据 | 处理建议 | +| --- | --- | --- | +| 128 个 FastAPI 响应码未写入 OpenAPI | Sonar `python:S8415` | API 对外稳定后按 Router 批量补,不影响 P0/P1。 | +| 重复字符串常量 | Sonar `python:S1192` | 只抽真正表达领域含义的常量;“任务不存在”不必全局设计模式化。 | +| HTML 语义容器、label、对比度 | Sonar Bugs/Web/CSS | 作为独立可访问性轮次修,不和状态/事务改动混合。 | +| 105 个文件不符合 Ruff format | `ruff format app tests --check` | 不在整改前批量格式化,避免淹没真实 diff;按触碰文件渐进格式化。 | +| Pydantic V1 `@validator` 警告 | 7 条测试警告 | 等 Pydantic 升级轮次集中处理。 | +| CSS 未定义别名与旧属性 | Codemap + Sonar CSS | 先修真实视觉失效的变量,忽略纯兼容规则。 | + +## 6. 专项检查 + +### 6.1 数据与数据库 + +#### 真实库只读结果 + +- 文件:`data/workflow.sqlite3`,处于 WAL 模式,审计时存在活跃 `-wal/-shm`,所以没有复制、迁移或写入。 +- `PRAGMA quick_check`:`ok`。 +- `PRAGMA foreign_key_check`:17 条违规,详见 P0-2。 +- `PRAGMA user_version`:`0`,没有可追踪的 Schema 版本号。 +- 关键规模:35 tasks、232 candidates、335 output clips、589 publish jobs、837 publish events。 +- `tasks.status` 同时存在大写自动状态和小写手动状态;这是代码定义的一部分,不是简单大小写脏数据,但会增加查询、迁移和恢复复杂度。 + +#### Schema 判断 + +| 检查项 | 判断 | 风险 | +| --- | --- | --- | +| Primary Key | 主要使用文本 UUID,适合本地分布式生成 | 低 | +| Foreign Key | 连接时启用 FK,但历史数据已有违规,部分 FK 无明确删除策略 | 高 | +| Unique Constraint | 有发布活动任务等局部唯一索引;重建失败会被吞 | 高 | +| Index | 关键状态/时间有部分索引;失败不可见,全量排期扫描仍存在 | 中 | +| 状态字段 | 字符串为主,无 DB CHECK;四组状态并行 | 高 | +| 时间字段 | 多为 ISO 字符串;跨午夜排期有专门逻辑 | 中 | +| 删除策略 | 任务行保留历史、托管文件删除;语义接近“隐藏并清理媒体” | 高(命名和原子性) | +| 更新策略 | 多 Service 各自连接和 commit;跨文件/跨表事务难组合 | 高 | +| 回滚策略 | 数据库可 rollback,文件系统和外部平台不可自动 rollback | 高 | +| Migration | 启动探测式迁移,部分有备份,但无统一版本账本 | 高 | + +### 6.2 稳定性故障矩阵 + +| 场景 | 当前行为 | 恢复判断 | 主要证据 / 缺口 | +| --- | --- | --- | --- | +| API 超时 / 网络断开 | AI/发布通常转异常或失败;旧 API 发布可能无法判断平台是否已接收 | 部分可恢复 | 超时后不总是进入 `NEED_REVIEW`;重试可能重复请求 | +| 第三方 500 / 429 | 转成 Provider error,缺统一指数退避和状态码分类 | 不稳定 | 短暂故障可能直接失败;fallback 可能重复费用 | +| AI 空内容 | 多数路径判无候选/失败 | 较明确 | 某些 Provider/归一化会用默认值继续 | +| AI 错误 JSON / Markdown | 支持 fence、修复、trailing comma、`literal_eval` | 可用但过宽 | 提高兼容性,也可能把坏结果包装成合法结果 | +| 字段缺失 / null | 候选和文案大量默认值 | 部分成功 | 数据可继续流动,但质量退化可能不醒目 | +| 上传失败 | 普通异常路径会清理任务目录 | 较明确 | 进程突然退出时直接写最终路径可留截断文件 | +| 文件损坏 | 多处有 FFprobe;部分读取静默 fallback | 部分可恢复 | transcript/progress/analysis 损坏可能被当“不存在” | +| 数据库异常 | busy timeout/WAL 有帮助 | 不稳定 | 多处静默 `except sqlite3.Error`,可能走错误 fallback | +| Worker 异常退出 | lease/heartbeat 可重新领取 | 部分可恢复 | 无 fencing,旧 Worker 恢复后仍可写 | +| Task 中途失败 | 有失败状态和日志 | 部分可恢复 | step 副作用 checkpoint 不完整,summary 二次失败可遮蔽原错 | +| 重复执行 | Job 创建有基础去重,Publish claim 有条件更新 | 部分可防 | 同步切片、execution id、旧 Worker 缺幂等 | +| 程序重启 | 持久化 Job/文件产物帮助续跑 | 有条件恢复 | 进程内转写锁、取消集合和 Pipeline context 丢失 | +| 并发请求 | SQLite `BEGIN IMMEDIATE` 覆盖部分关键动作 | 不完整 | 目录命名、cut run、字幕 revision/track 存在 check-then-act 竞态 | + +总体回答:**系统失败后通常能留下一个状态,但不总能保证该状态唯一、最新且与文件/外部副作用一致。** 这正是从“可用 V1”到“稳定 V1”的主要差距。 + +### 6.3 测试体系 + +| 类型 | 当前情况 | 本轮结果 | 关键缺口 | +| --- | --- | --- | --- | +| Unit Test | Provider 解析、状态 helper、规则、路径等较多 | 纳入 500 项,全过 | monkeypatch 较多,真实资源边界不足 | +| Integration Test | SQLite、WAL/FK、Job、字幕、发布调度较丰富 | 全过 | 文件 + DB + 进程崩溃的一致性不足 | +| API Test | Tasks、Publish、Settings 等有覆盖 | 全过 | Secret 不回显、Token 开启、路径越界和并发写缺口 | +| Browser / E2E | 发送中心有 Playwright,字幕多为页面/静态检查 | 环境存在时纳入;可被 `importorskip` | 无真实“上传→转写→AI→切片→字幕→发布”闭环 | +| Smoke | CI 有 Windows/Docker 入口;当前真实本机服务/Worker 健康 | 两个 health 均 `ok` | `/health` 不检查 DB/Job/Scheduler/FFmpeg;未重建 Docker 镜像 | +| Coverage | 未配置 | Sonar 显示 0%,仅代表无报告 | 无法知道核心行/分支实际覆盖 | +| Typecheck | 未配置 mypy/pyright/tsc | 工具不存在 | `Any` 和动态 dict 边界只能靠运行时测试 | + +本轮实际命令结果: + +- `pytest --collect-only -q`:500 项。 +- 完整 pytest:`500 passed, 9 warnings`,约 90 秒;JUnit 再跑 `500 passed`。 +- Ruff lint:通过。 +- Ruff format check:失败,105 个文件会被重排;没有执行格式化。 +- `python -m compileall -q app tests scripts`:通过。 +- 导入 `app.main`:通过。 +- 全部前端 JS `node --check`:通过。 +- 20 个 PowerShell 脚本 Parser:通过。 +- 基础/dev/demo 三种 Docker Compose 配置:通过。 +- `pip check`:通过。 + +目前仍完全或主要依赖人工点击的核心验证: + +1. 真实长视频端到端质量和耗时。 +2. 火山/本地转写真实失败恢复。 +3. 真实 AI Provider 的 429、超时、坏 JSON、计费重试。 +4. 字幕批量烧录的真实 FFmpeg/NVENC 失败恢复。 +5. Chrome 登录态、平台 UI 变化、投稿后不确定结果。 +6. 程序休眠/重启、旧 Worker 复活、同 execution id 重放。 + +### 6.4 安全 + +#### 部署语境 + +当前设计适合 **个人本机、loopback、受信任操作者**。它不是内部多用户系统,也不具备对外 SaaS 所需的身份、授权、租户隔离、审计和限流边界。审计不建议为当前阶段引入企业级 RBAC;建议把“只允许本机”变成明确且可验证的默认条件。 + +| 检查项 | 结果 | 优先级 | +| --- | --- | --- | +| API Key / Secret / `.env` | 当前只跟踪 `.env.example`;`.env`、浏览器数据、DB 被忽略;但读取接口会回显原始 Secret | P1 | +| Git 历史 Secret | 文件名扫描未发现历史 `.env`/私钥/DB;内容模式命中经脱敏核验为 `data-task-*` 属性误报 | 已核验,无确认泄漏 | +| Authentication | 可选 Bearer Token,默认空 | P1(非 loopback) | +| Authorization / 用户隔离 | 无,多用户不在当前范围 | 本地可接受,SaaS 不可接受 | +| Input Validation | Pydantic/扩展名/部分路径有校验;状态、目录片段和 Worker ID 边界不足 | P1 | +| SQL Injection | 未发现高置信度用户输入直接拼 SQL;大多数参数化 | 低 | +| XSS | 两处动态 `innerHTML` 高置信度 | P1 | +| CSRF | 不使用 Cookie 会降低经典 CSRF;Token 为空时写 API 本身无认证 | 被鉴权问题覆盖 | +| 任意文件上传 | 有扩展名/预检/允许根;突然退出的原子性不足 | P2 | +| Path Traversal / 本地文件读取 | `task_dir_name` 和媒体已存在路径边界不闭合 | P1 | +| 敏感日志 / Token 日志 | Worker/Provider 整体有隐藏意图;原始响应 DTO 和 URL 错误消息仍可能泄漏 | P1/P2 | +| CORS | 没有发现对所有来源开放写请求;本地静态/媒体 GET 有专门处理 | 低 | +| Rate Limit | 无 | 本地低;对外不可接受 | + +Sonar 的 2 个 Vulnerability: + +1. `storage_service.py:79`:把自定义上传目录设为系统临时目录。需检查实际 Windows ACL;当前不能仅凭该规则判定目录“publicly writable”。 +2. `publish_scheduler.py:961`:人工确认作品链接允许 HTTP。建议改 HTTPS,但其影响低于 Secret DTO、路径和鉴权。 + +Sonar 没有发现原始 Token 响应、DOM XSS 和目录拼接,说明静态扫描必须和业务 Review 组合使用。 + +### 6.5 性能与资源 + +有明确证据、值得逐步处理的性能问题: + +- 发送中心打开时每 5 秒重复刷新 jobs、accounts 和 Scheduler health;后端 jobs 查询还会重复读账号/Worker 健康。 +- Scheduler 每轮读取全部 `SCHEDULED` 后在 Python 解析/筛选到期时间;当前 589 条 publish jobs 尚小,未来规模增长会线性放大。 +- 旧 API multipart 发布用 `read_bytes()` + `b''.join()`,大视频产生约双份内存。 +- 字幕 waveform 把完整 PCM 读入内存后再降采样。 +- 火山转写 chunk 完整 Base64 入内存。 +- 本地模型协议 fallback 对所有 ProviderError 尝试第二协议,可能重复模型调用和 Token 消耗。 +- 通用 AI 分段失败后仍可能返回部分候选;如果人工重跑整任务,会重复已成功窗口的成本。 +- `publish_service.py` 批量上下文存在账号查询 N+1;本地小规模影响暂低。 + +没有证据支持现在引入缓存服务、分布式队列或复杂前端构建系统。先减少重复请求、修 SQL 筛选、流式处理大文件和精确 Provider 重试即可。 + +### 6.6 Dead Code / Legacy / Mock 清单 + +本轮仅列出,没有删除。 + +| 候选 | 证据 | 初步判断 | +| --- | --- | --- | +| `app/services/ai_clip_service.py` | 只含硬编码三条候选的 `generate_candidate_clips_placeholder`,全仓未发现调用 | fake output,优先删除候选 | +| `publish_repository.update_execution_phase()` | 全仓未发现生产调用;测试还断言 Worker 不含该旧行为 | 可能安全删除,但需再做动态调用确认 | +| `execute_opencli_send_job()` / `execute_api_publish_job()` | 仍被 compat Publisher 调用 | Legacy,不能直接删 | +| `publishers/opencli_compat.py`、`api_compat.py` | Registry 仍注册;旧任务模式可能引用 | 需数据盘点后删除 | +| `publishers/page_scripts.py` | 通过 wrapper 回调 `publish_service` 私有函数 | 丑但仍是 Worker 兼容胶水,暂不删 | +| `job_service` 的 `ai_analysis` / `publish` Job 类型 | execute_job 没有对应分支 | stub/未来预留,需确认产品方向 | +| `transcript_service.allow_fallback` / `TRANSCRIPTION_FALLBACK_PROVIDER` | 未发现 `allow_fallback=True` 生产调用 | 需要确认后删除 | +| `/workspace/tasks` 旧路径映射 | `storage_service.py:298-340` | Docker/历史数据兼容,暂不删 | +| 旧 AI_REMOTE 配置别名 | 运行时仍同步新旧字段 | 需配置迁移后删除 | +| 旧小写/新大写发布状态与字段别名 | DB 和服务并存 | 必须先迁移真实历史数据 | +| 历史文档段落 | 与当前字幕行为并存 | 应归档/标历史,不是直接删除证据 | + +### 6.7 当前已有且应保留的工程资产 + +- SQLite WAL、busy timeout、连接级 FK。 +- `create_or_get_active_job()` 的事务性基础去重。 +- 发布 Scheduler 的条件 claim 和上传后 `NEED_REVIEW` 原则。 +- Windows Worker 的 execution journal 和账号锁。 +- 长直播 checkpoint 和 90% 覆盖率门槛。 +- 字幕 track/revision/cue 历史和发布前验证门禁。 +- FFmpeg/FFprobe、备份恢复、启动健康检查脚本。 +- CI 的 Windows + Docker smoke 入口、敏感文件检查和独立 demo DB。 + +## 7. 技术债 Top 10 + +排序依据不是“代码难看”,而是 **发生风险 × 业务影响 × 未来维护成本 × 修复收益**。 + +| 排名 | 技术债 | 风险 / 影响 | 未来成本 | 修复收益 | 为什么排在这里 | +| --: | --- | --- | --- | --- | --- | +| 1 | 测试数据库 fail-open,可执行整表删除 | 极高 / 全库 | 每次手工测试都可能触发 | 极高 | 修改成本最低,却能消除最不可接受的数据损坏入口。 | +| 2 | 活动库已有 17 条 FK 违规,迁移无版本账本 | 已发生 / 发布与字幕历史 | 每次迁移都会放大 | 极高 | 先恢复数据可信度,其他重构才有可靠基线。 | +| 3 | 原始 API/OAuth Secret 经读取接口回显 | 高 / 全部凭据 | 接口越多越难清理 | 极高 | 本地项目也不应把原始 Token 发到浏览器。 | +| 4 | Workflow/Publish execution 缺 lease fencing | 高 / 重复执行与错终态 | 新 Job 类型会复制缺陷 | 极高 | 是“平时能跑、异常顺序就错”的典型核心债。 | +| 5 | 存储路径允许根、目录名和媒体响应边界不统一 | 高 / 本地文件与任务产物 | 旧路径越多越难迁移 | 高 | 安全和数据覆盖同时受影响。 | +| 6 | 永久删除的文件系统与 DB 事务不可回滚 | 低概率高损失 / 单任务全部产物 | 删除类型增加会放大 | 高 | 必须在继续扩展清理功能前改成可恢复两阶段。 | +| 7 | 状态任意跳跃、切片/字幕批量结果可部分提交 | 中高 / 下游全链路 | 状态越多越难收敛 | 高 | 当前四套状态的核心一致性问题。 | +| 8 | 转写旧产物复用、取消传播和并发元数据不可靠 | 中高 / AI 与字幕输入 | 长视频越多越贵 | 高 | 错转写会悄悄污染所有下游结果。 | +| 9 | AI 部分窗口/fallback 缺明确成本和质量门槛 | 中 / 质量与费用 | Provider/profile 增加会放大 | 高 | 需要产品语义,不是简单加 retry。 | +| 10 | `publish_service.py` 与前端大脚本成为发布域 God Components | 中 / 发布全域 | 每个新功能持续提高回归成本 | 高 | 应在安全和状态边界稳定后渐进拆分,不能先大重构。 | + +## 8. 删除候选清单 + +### 可以安全删除(仍应放在独立清理 PR 中验证) + +| 候选 | 依据 | 删除前最低验证 | +| --- | --- | --- | +| `app/services/ai_clip_service.py` | 仅返回三条硬编码 fake candidates,全仓静态引用为 0;架构文档对它的描述已过时 | 再做动态导入搜索;跑 AI/自动流水线测试;同步修 `docs/ARCHITECTURE.md` | +| `publish_repository.update_execution_phase()` | 全仓未发现生产调用,测试明确要求 Worker 不依赖它 | 跑 Publisher/Scheduler 全套测试 | +| `publish_service.py` 中被后续定义覆盖的首组 `STATUS_LABELS/STATUS_TONES` | 同文件后续重新定义,前值不会生效 | 用导入后对象断言 + 发布历史/页面测试确认 | + +“可以安全删除”是静态审计结论,不代表本轮已删除;本轮没有改动这些文件。 + +### 需要确认后删除 + +| 候选 | 需要确认什么 | +| --- | --- | +| `opencli_compat.py`、`api_compat.py` 及两个 legacy execute 函数 | 真实 DB 是否仍有对应 `publish_mode/provider` 记录;是否还需要历史重试 | +| `job_service` 的 `ai_analysis` / `publish` 类型 | 是未来计划还是未完成 stub;若保留必须接线,若无计划应删定义和 UI | +| `TRANSCRIPTION_FALLBACK_PROVIDER` / `allow_fallback` | 是否有项目外脚本或用户工作流依赖 | +| AI_REMOTE_* 旧配置别名 | 活动 `.env` 和持久化设置是否仍使用旧名;不得读取/输出 Secret 值,只盘点键名 | +| `/workspace/tasks` 旧路径映射 | Docker 历史记录是否仍保存该路径 | +| task_service 末尾查询兼容 wrappers | 所有 Router/模板是否已迁到 task_query_service | +| 历史文档中旧流程段落 | 先决定迁入 `docs/history/` 还是加“历史行为”标记 | + +### 暂时不要删除 + +| 代码 / 数据 | 原因 | +| --- | --- | +| B站后端 Publisher、历史记录和字段 | 当前前端主推抖音不等于后端历史可删除;删除前必须迁移真实记录。 | +| `manual_export` | 是真实发布不可用时的安全、可验证降级,不是 fake success。 | +| `page_scripts.py` 与浏览器兼容胶水 | 虽然耦合丑,但真实 Worker 当前仍依赖。 | +| `NEED_REVIEW`、execution journal、Worker artifacts | 它们是防止不确定结果被误判成功的核心安全证据。 | +| `database_backup_service.py`、WAL-aware backup/runtime wrapper | 这是数据库整改前的安全基础;应整理而非先删。 | +| 旧数据库列/状态别名 | 真实库已有历史数据,必须迁移并验收后才能删除。 | +| 活动 `.db-wal/.db-shm` | 表示存在写连接,不能当缓存随意删除。 | + +## 9. 暂时不要动的地方 + +1. **不要把 SQLite 改成 PostgreSQL,也不要引入 ORM 大迁移。** 当前规模下 SQLite 合理,问题是约束、迁移账本和事务边界,而不是数据库品牌。 +2. **不要把单体拆微服务。** 当前故障多来自跨状态/跨文件原子性;拆服务只会增加网络和分布式一致性问题。 +3. **不要为了 Sonar A 评级先拆 `database.py`。** 先修真实数据和 migration ledger,再按版本迁移小步拆分。 +4. **不要改掉 `NEED_REVIEW` 和上传后不自动重试策略。** 这是平台发布最重要的防重复边界之一。 +5. **不要重写已经工作的字幕 revision 模型。** 它保留历史、可审核、可回滚;只需加强批量事务和 owner/version 条件。 +6. **不要直接统一删除大写/小写状态。** 真实数据、查询和 UI 均依赖;先建立映射、数据统计和迁移测试。 +7. **不要先批量格式化 105 个文件。** 大 diff 会掩盖 P0/P1 修复,也增加合并冲突。 +8. **不要先清理所有 Legacy Publisher。** 必须先盘点 DB 模式和历史任务,确保没有可恢复任务被切断。 +9. **不要大改 E 盘任务目录布局。** 文件路径已被数据库、字幕、发布包和 Worker 引用;路径安全可以在保持布局的前提下修。 +10. **不要因为 `styles.css/app.js` 很大就先重写前端。** 先修 `innerHTML`、统一 API 请求和轮询,再按页面渐进拆分;不引入 React/Vue。 + +## 10. Impact × Effort 矩阵 + +| | 低成本 | 中成本 | 高成本 | +| --- | --- | --- | --- | +| **高影响** | 测试库 fail-closed;Secret DTO;保存 scheduler task;URL hostname 校验;统一前端鉴权 | 路径允许根;lease fencing;AI partial 门槛;转写 run 绑定 | FK 历史修复;状态/切片原子化;migration ledger | +| **中影响** | CSS 变量;文档当前态;工具版本校验 | 覆盖率/核心 E2E;日志和 readiness;轮询/SQL due | `publish_service` 渐进拆分;DB 访问边界收敛 | +| **低影响** | OpenAPI responses;重复字符串;小型命名 | 可访问性批次;格式化触碰文件 | 全前端重写、ORM、微服务——当前不做 | + +## 11. 整改路线图 + +每一轮都限制范围、可单独测试、可单独回滚。**不要合并为“全面重构”。** + +| 轮次 | 有限范围 | 独立验收 | 回滚方式 | +| --- | --- | --- | --- | +| P0.1 测试数据库保护 | 仅 `tests/conftest.py` 和危险 fixture;强制临时库、危险路径拒绝 | 在故意设置活动库路径时 pytest 应在收集前拒绝;正常临时库 500 项全过 | 回滚测试基础设施提交,无生产数据迁移 | +| P0.2 数据一致性盘点与修复 | WAL-aware 备份、17 条孤儿 dry-run、逐类处理策略 | backup hash/quick_check/FK check;修复后 0 违规;应用只读页面回归 | 整库备份恢复;保留修复 manifest | +| P0.3 可恢复删除 | 单个任务删除改为隔离区 + manifest + DB commit + 延迟清理 | 模拟 DB commit 失败、文件移动失败、进程中断;均能恢复 | 把隔离目录移回;回滚代码 | +| P1.1 Secret DTO 与本地鉴权 | AI/Publish 读取 DTO;非 loopback 启动门禁;不做多用户系统 | API 响应扫描不含原值;Token on/off、原生/Docker 组合测试 | 兼容写入字段保留;回滚 API DTO 提交 | +| P1.2 路径和 Worker 标识边界 | `task_dir_name`、media FileResponse、execution/account id;不改目录布局 | traversal、绝对路径、symlink、异常 DB 路径、重复 execution 测试 | 兼容旧合法路径白名单;回滚验证层 | +| P1.3 Workflow lease fencing | 增 attempt token;所有更新带 owner/token/status;旧 worker fail closed | 两 Worker、租约过期、休眠恢复、旧终态覆盖测试 | Schema 先兼容可空列;回滚代码保留列 | +| P1.4 Publish execution 幂等与 Scheduler 生命周期 | 保存/await task;execution journal 幂等;repair 条件锁 | 同 execution 重放、旧 execution 写回、并发 repair、优雅关停 | 回滚应用层状态机,journal 数据仍兼容 | +| P1.5 Task/Cut/Subtitle 状态与批次原子性 | 先合法转移表,再切片 run 和字幕批准批次;不改 UI 主流程 | 非法跳跃拒绝、并发 run、批次中途失败、旧 active 保留 | 每项独立提交;特性开关保留旧入口短期回退 | +| P1.6 Transcription 正确性 | run/source 绑定、取消透传、损坏产物显式状态 | 大文件中部变化、旧文件、取消、进度 JSON 损坏、重启测试 | 旧 transcript 只降为历史,不删除 | +| P1.7 AI 质量与成本门禁 | profile 覆盖率、partial 状态、错误分类、有限退避 | 429/500/超时/坏 JSON/部分窗口/重复调用次数测试 | 配置化门槛,可回滚到旧行为但保留日志 | +| P1.8 Migration ledger 与索引不变量 | 只为未来迁移加版本账本/preflight;当前 Schema 不大改 | 旧库升级、半迁移、重复数据、索引失败应明确阻断 | 每版前备份;一个 migration 一次提交 | +| P2.1 覆盖率与真实故障测试 | 加 Coverage;先锁 P0/P1 核心模块,不追漂亮总百分比 | CI 产 `coverage.xml`,Sonar 能导入;核心门槛不得下降 | 移除阈值不影响生产代码 | +| P2.2 可观测性 | readiness、Job/Worker 错误码、受控 stderr、fallback 日志 | DB/Worker/FFmpeg 故障时状态明确且不泄密 | 回滚观测层,无数据迁移 | +| P2.3 Publish Service 渐进拆分 | 先 Secret DTO/query,再内容准备,再 job,最后 legacy adapter | 每轮 API contract、发布状态机和浏览器测试全过 | 一轮一个提交,旧 facade 保持接口 | +| P2.4 原生前端渐进拆分 | 先 request + escape,再 polling,再按页面模块化 | Token 开启、XSS、轮询、字幕/发送中心 Playwright | 保留原函数 facade,不换框架 | +| P2.5 Dead/Legacy 清理 | 每次只处理一个已确认无引用候选 | `rg`/动态导入盘点 + 全测试 + 历史 DB 模式统计 | 单候选单提交,直接 revert | +| P3.1 文档和格式 | Current State 文档、历史标记、触碰文件格式化、可访问性 | 文档链接、Ruff、页面 smoke | 不与业务改动混合,单独回滚 | + +## 12. 最终判断 + +### 当前为什么能跑 + +因为项目选择了合适的本地单体架构,并且已经建立了 SQLite WAL、持久化 Job、文件产物、版本化字幕/切片和发布人工复核这些实用保护;500 项测试也覆盖了大量正常路径和若干失败路径。 + +### 哪些是可靠的 + +单机正常顺序下的核心业务闭环、基础 Job 去重、发布 claim、上传失败清理、字幕 revision 历史、发布不确定状态和 Windows Worker 隔离是可信资产。 + +### 哪些只是侥幸没出问题 + +测试是否永远指向测试库、旧 Worker 是否永远不会复活、目录名是否永远来自正常 UI、文件删除后数据库是否永远能提交、AI 是否总有足够窗口成功、配置接口是否只被可信浏览器访问——这些都依赖环境或顺序,并未被代码强制保证。 + +### 应该先改什么 + +先保护测试和真实数据,再封 Secret/路径/lease/删除/状态边界;随后补覆盖率和故障可观测性;最后才拆 God Service、清 Legacy 和整理格式。 + +### 哪些不要碰 + +不要换架构、不要改掉人工复核、不要先删除历史兼容、不要直接迁移状态和目录、不要用大规模格式化或前端重写掩盖真正风险。 + +本报告完成后应停止审计阶段。下一步应由用户先确认 P0/P1 顺序,再逐轮整改。 diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..54014cf --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,14 @@ +sonar.projectKey=niuma-studio-local-audit +sonar.projectName=NiuMa Studio +sonar.projectVersion=2.1.0 +sonar.sourceEncoding=UTF-8 + +sonar.sources=app,scripts +sonar.tests=tests +sonar.test.inclusions=tests/**/*.py +sonar.exclusions=app/static/vendor/**,app/static/img/**,scripts/test_*.py,**/__pycache__/** + +sonar.python.version=3.12 +sonar.python.coverage.reportPaths=coverage.xml +sonar.python.xunit.reportPath=test-results.xml +sonar.working.directory=.scannerwork