chore: 一键检查、发布脚本与开发环境约定

- deno task test:all(scripts/test-all.sh):逐包跑完再汇总,连不上测试库直接失败,
  web 用 Node 跑 vitest;deno task ci 改用它
- deno task release(scripts/release.sh):工作区不干净、CHANGELOG 无本版条目即拒绝;
  打 tag、构建、只替换 app、核对健康接口版本
- deno task dev:up:起开发库并迁移到最新;开发 API 端口改 4390(4380 留给本机部署版)
- CLAUDE.md:命令、提交与发布约定、文档放哪

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-02 16:08:09 +08:00
1 parent ceb66f8c77
commit 11ca2f01ff
200 files changed
+191 -23061

No files matched your search

-227
View File
@@ -1,227 +0,0 @@
# 首个可用版本(v0.1)工程规划
> 本文保留最初任务书与用户追加要求的历史记录。后续段落里的分支状态、未做清单和粗估不作为当前排期;继续开发前请先看 [当前开发情况基线](13-development-baseline.md)(2026-10-01,v0.3.7)。
编制:2026-09-27。需求基线:`../../media_automation_requirements_architecture_v0.7.md`(下称 REQ)。本文件是所有开发子任务的统一任务书;与 REQ 冲突时以 REQ 的产品要求为准,技术选型以本文件为准。
## 1. 技术决策(ADR 摘要,详见 docs/adr/)
| 项 | 决定 | 说明 |
| --- | --- | --- |
| 运行时 | **Deno 2.9.7(锁定)+ TypeScript 7.0.2**,deno.json workspace(`nodeModulesDir: auto`,npm 依赖走 `npm:` 说明符,deno.lock 锁定) | 用户 2026-09-27 确认:按 REQ §9 采用最新稳定版 Deno;之前 W0 用 Node 24 + pnpm 搭的底座在 W1 结束后统一迁移(ADR-004 取代 ADR-001)。前端仍用 Vite 7 + React Router 7 构建,由 Deno 执行。 |
| 前端 | React 19 + React Router 7(SPA, `ssr:false`)+ Vite 7 + Tailwind v4 + shadcn(new-york, radix-ui 统一包)+ TanStack Query 5 + zod 4 + sonner + lucide | 直接拷贝 uhd_web 的 `app/app.css` 设计 token、`app/components/ui/*`、`app/lib/api.ts`、`app/lib/utils.ts`、`scripts/gen-design-tokens.mjs`、`scripts/check-design-tokens.sh`、`docs/features/{design-tokens,interface-materials,ui-controls}.md`。**不引入 uhd_web 的播放器/WASM 相关内容。** |
| 后端 HTTP | Hono 4(`Deno.serve`),REST + SSE | OpenAPI 由 zod schema 生成(`@hono/zod-openapi`)。 |
| 数据库 | PostgreSQL 16;Kysely + pg;SQL 迁移文件 + Kysely Migrator;`kysely-codegen` 或手写 DB 类型 | 开发用 docker-compose 起 `postgres:16-alpine`,端口 55433。 |
| 持久任务引擎 | 优先 `[email protected]`(peer: postgres.js);**须先通过 spike 验证**(重启恢复/等待信号/取消/重试)。未通过则用 `packages/tasks` 内的 PG 租约式任务表(lease + attempts + dead-letter)。 | 结论写 ADR-002。 |
| Agent 内核 | `@earendil-works/[email protected]` + `@earendil-works/[email protected]`,锁版本;通过自有 `AgentRuntimePort` 封装,显式工具白名单,不开放 bash/文件系统/任意网络 | 测试用脚本化 fake 模型 provider;真实上游由设置页配置。 |
| 校验/测试 | vitest 5(`deno run -A npm:vitest run`,`deno task test` 逐成员执行);类型门禁两道:tsc 7.0.2(`npm:[email protected]`,Go 原生实现,入口 `tsc`)+ `deno check`(Deno 内置 TS 6.0.3);lint/format 用 biome 2.5.14(开启 `useImportExtensions`,相对 import 必须带扩展名);web 沿用 token 守卫脚本 | 当前 `deno task ci` = typecheck(tsc 7.0.2 + deno check)+ lint + test + check:tokens + check:copy + build(web 构建 + `deno compile` 单文件 server)+ check:binary。W1.5 实测(2026-09-27)连跑 3 次全绿,约 75 秒/次(v0.1 验收时已涨到约 135 秒/次,server 与 tasks 测试是主要耗时,见 04);测试数 server 24、adapters 214(+2 skip)、agent 27、contracts 361、db 11、domain 230、tasks 15、testkit 1、web 2 |
| 端口 | web dev 5180,API 4380,PG 55433 | 本机 3000/5175/5176/55432 已被占用,不要用 |
| 参考源码 | MoviePilot v3: `/opt/moviepilot-plus/upstream/MoviePilot`(提交 e6820a8,GPLv3,只做行为对照与 TS 重写,不复制文件)。**数据来源适配(站点 spider、馒头、RSS、TMDB、qB)照抄 MP v3 实现迁移**(用户 2026-09-27 明确):`app/modules/indexer/spider/__init__.py`、`spider/mtorrent.py`、`app/application/rss.py`、`app/modules/themoviedb/tmdbapi.py`、`app/modules/qbittorrent/qbittorrent.py`;站点定义按 `app/application/site/sites.pyi` 的 schema 手写(MP 的定义文件 `user.sites.v3.bin` 是加密的,无法取用)。**用户 2026-09-27 追加:MP 开源的站点框架全部移植**——所有专用 spider(haidan/hddolby/rousi/sunnypt/tnode/torrentleech/yema/mtorrent)与所有站点类型用户信息解析器(nexus_php/nexus_audiences/nexus_hhanclub/nexus_project/nexus_rabbit/gazelle/unit3d/discuz/ipt/file_list/small_horse/zhixing/bitpt/…),文件与函数一一对应,并维护 `docs/porting/mp-sync-ledger.md` + `scripts/mp-sync` 差异脚本,以后跟随 MP 提交增量移植;MovieClaw 若本机不存在则按 REQ 附录链接对照 | 迁移对照记录写 `docs/porting/` |
## 2. 仓库结构(Deno workspace)
```
apps/web React Router 7 SPA 控制台(复用 uhd_web 设计系统)
apps/server Node 服务:HTTP API + SSE + worker 入口(单进程可跑,也可 `--role api|worker` 分开)
packages/contracts zod schema + 类型:所有 API/事件/能力(capability)输入输出的唯一定义,web 与 server 共用
packages/db Kysely 类型、迁移、仓储辅助
packages/domain 纯业务逻辑(无 IO):标题解析/身份匹配/Slot 满足度/路径绑定/整理计划/策略评估
packages/adapters 外部系统适配:qBittorrent、RSS、站点(观众 NexusPHP / 馒头 API)、TMDB、Emby 兼容、文件系统
packages/tasks 持久任务定义与执行器(OpenWorkflow 或自研租约表),outbox
packages/agent Pi 接入:AgentRuntimePort、能力注册中心桥接、agent_run 持久化、fake provider
packages/testkit mock 服务:mock qB WebUI、mock NexusPHP 站点、mock M-Team API、mock RSS、mock TMDB、临时目录夹具
docs/adr 架构决策记录
docs/plan 本规划与里程碑验收清单
docs/porting MoviePilot/MovieClaw 迁移对照账本(REQ §14.1.2 格式)
docs/spikes 兼容性验证记录
docs/runbook 启动/备份/恢复/配置说明
scripts dev/ci 辅助脚本
```
约定:
- 所有共享类型只在 `packages/contracts` 定义;web 不得手写与 server 重复的 DTO。
- `packages/domain` 不 import 任何 IO 库,100% 可单元测试。
- 每个包有 `src/index.ts` 作为唯一公开入口;跨包只通过入口引用。
- 命名:表名 snake_case 与 REQ §12 实体名一致;TS 类型 PascalCase;能力 ID 用 `域.动作`(如 `download.submit`)。
- 提交:conventional commits(`feat(server): ...`),每个子任务结束前 `deno task ci` 必须绿再提交。
- 凭证一律 secret reference(`secret_ref` 列 + 加密存储),日志/错误/模型上下文脱敏;含 passkey 的 URL 不得进入日志。
## 3. v0.1 范围(= REQ M0 + M1 纵切,可在无真实 PT 账号下用 mock 全链路验证)
### 3.1 必做
1. **底座**:monorepo、配置(env + settings 表)、迁移、API Key 鉴权(scope:read/organize.read/organize.execute/admin)、审计日志 `audit_event`、SSE 事件流(带序号可补读)、全局"自动化总开关"。
2. **数据模型**(REQ §12 子集,表名照抄):media_entity、content_unit、external_identity、identity_revision、title_alias、media_asset、asset_location、path_binding、library_root、site_account、feed_source、observation、source_release、subscription、policy_revision、acquisition_slot、preference_tag、preference_suppression、acquisition_plan、download_task、asset_operation、decision、approval、automation_grant、agent_run、artifact、audit_event、outbox_event、reconcile_run、server_mapping、api_key、organization_record、organization_file_result、downloader_instance、media_server_instance、settings。
3. **来源**:多 RSS 抓取(ETag/Last-Modified、重叠窗口、GUID+摘要去重、退避/熔断、passkey 脱敏)、观众(NexusPHP 列表/详情/下载,参考 MovieClaw audiences.yaml 结构)与馒头(API Key)两类站点适配器、站点快照缓存 + 实时补查 + 站点启停 + 站点级限流。
4. **身份与元数据**:发布标题解析(对照 MP MetaInfo 行为:标题/年份/季集/分辨率/来源/编码/发布组/版本)、TMDB 查询与缓存、本地实体优先 + external_identity 映射(provider+type+id 唯一)、title_alias 模糊召回(pg_trgm)、临时 content_unit、待识别池。
5. **规则与选片**:subscription(单次/持续追踪)、acquisition_slot(硬条件/软偏好/升级阈值/完成条件,状态机 empty→reserved→downloading→verifying→satisfied/upgrade_pending/attention)、确定性 evaluateAcquisition(不含 Agent 主观选片,只留接口)、每日预算与并发限制、优先级(即时>追更>新作品>升级)。
6. **下载**:qB WebUI 适配(多实例、管理标签、提交/接管/查询/暂停恢复)、缓存卷硬阈值与恢复水位(暂停范围仅限纳管任务)、download_task 状态机、无进展只记录不换源。
7. **入库**:verifyAndImport → Copy/Move(临时文件+校验+提交;跨卷 Copy→核验→删源;EXDEV 不静默降级)、路径绑定固定(已绑定目录不因译名变化改变)、organization_record + 文件级结果、**公开整理记录 API**(API Key、游标增量)。
8. **持久任务**:ingestFeed、resolveObservation、evaluateAcquisition、submitDownload、verifyAndImport、refreshMetadata、reconcileLibrary(资产存在性)、cleanupDownloadCache、resolveDecision;outbox;重启恢复;幂等键;取消传播。
9. **Agent**:Pi AgentSession 封装;能力注册中心(ID/版本/schema/权限/风险/幂等/预览/处理器),首批能力:catalog.lookup、release.search、release.inspect、identity.resolve、download.plan、download.submit、organize.plan、organize.execute、task.inspect、task.retry、task.cancel、policy.preview、policy.apply、inventory.query;授权校验在服务端;agent_run 持久化 + SSE 增量;后台事件触发有界 Agent 任务(识别歧义、下载失败诊断);设置页可配上游/模型/思考档位/上下文预算/压缩/子代理开关。
10. **控制台页面**(REQ §13):首页六区块(自动化概览/当前工作/需要我处理/缓存与保留/来源健康/最近产出)、媒体库(列表+详情:路径/身份映射/元数据来源/Slot/候选拒绝理由/下载历史)、订阅与收录规则、偏好标签(基础 CRUD+抑制记录)、PT 站点工作台(整理视图:列表/搜索/详情/库存叠加/统一下载入口;原站视图首版只留占位与说明)、来源/Feed/快照、下载与任务(含业务时间线)、整理记录、待处理中心、身份与元数据、系统设置(实例/路径映射/API Key/Pi 上游与预算/自动化开关)、Agent 会话页 + 侧栏、**工作流引擎面板**(用户 2026-09-27 明确:把 OpenWorkflow 自带 Dashboard 的功能用本项目组件复刻并集成进控制台,走管理员登录;runs 列表/筛选、run 详情(步骤/attempts/输入输出/错误/时间线)、workers 与队列统计、cancel/retry 操作;数据经服务端 API 直接查 `openworkflow` schema;参照 `docs/design/openworkflow-dashboard-reference.md`;注意:官方面板包 @openworkflow/dashboard 是 Proprietary,不能复制源码,只参考信息架构并直查同一 schema 用本项目组件重写;0.10.1 无 rerun API,重跑通过我们自己的 enqueue 实现)。
11. **可选 Emby 兼容查询**:多实例,查有无/季集/版本,未知≠缺失。
12. **运维**:`scripts/backup.sh`(pg_dump + 映射导出)、恢复后只读对账模式开关、docker 打包:**除 PostgreSQL 外全部塞进一个容器**(单镜像内跑 API + worker + 托管 web 静态产物,单端口对外;compose 只有 app + pg 两个服务)、runbook。(用户 2026-09-27 明确)
14. **管理员登录门禁**(用户 2026-09-27 明确):单管理员账号密码登录(首次启动初始化/设置向导),会话 cookie(HttpOnly、SameSite)+ 登录页 + 未登录跳转;密码哈希(argon2/scrypt)、登录限速与失败锁定、会话吊销、可选 TOTP/Passkey 二次验证;控制台走会话鉴权,外部程序走 API Key。实践参考 `/opt/uhd_doc` 项目(见 `docs/design/auth-reference.md` 调研结论)。
13. **测试**:domain 单元测试;adapters 对 mock 服务的集成测试;端到端:mock RSS → 观察 → 识别 → 计划 → mock qB 下载完成 → Copy 入库 → 整理记录 API 可读;Agent e2e:fake 模型按脚本调用能力完成"把 X 电影 1080p 收进来"闭环;崩溃恢复注入(worker 中断后续跑)。
### 3.2 v0.1 明确不做(留接口/占位)
国产综艺分段建模与正片覆盖(M2)、双版本 require_distinct_asset 组合校验(M2,但 Slot 多目标数据结构先建好)、浏览器/camofox 接入与原站视图(M2,BrowserProvider 接口定义先落)、HHClub/CHD 适配(M2)、旧库分范围接管预览(M2)、Agent 主观选片与规则学习回放(M2)、媒体级主动整理 UI(M2,organize.plan 能力可用)、多用户。
## 4. 里程碑与派工波次
- **W0 底座**(并行):A 脚手架与设计系统迁入;B OpenWorkflow+Pi 兼容性 spike(结论进 ADR-002);C MoviePilot 迁移对照账本(识别/刮削 + 转移入库)。
- **W1 领域与适配**(并行):domain(解析/身份/Slot/路径/整理计划)、adapters(qB/RSS/站点/TMDB/Emby)+ testkit mocks、db schema 全量迁移 + contracts 全量 schema、tasks 引擎接入。
- **W2 服务编排**:server API 全量路由 + 任务处理器串起纵切 + Agent 能力注册与 Pi 封装。
- **W3 控制台**(按页面分组并行)+ e2e。
- **W4 集成验收与修复**:跑 §3.1 第 13 条全部;补 runbook;打 tag v0.1.0。
每波结束的验收由独立审查子代理执行:`deno task ci` 绿 + 对照本文件逐条勾选 + 抽查 REQ §2.1 五原则未被破坏。
## 5. W2 服务编排拆分(2026-09-27 定稿)
服务端模块布局:`apps/server/src/modules/<area>/{routes.ts,service.ts,handlers/*.ts}`,每个模块导出 `registerRoutes(app)` 与任务处理器注册函数;`http/app.ts` 与 `worker/index.ts` 各维护一个模块列表,每个子任务只追加自己的一行。跨模块只通过 DB 表 + `outbox_event`(事件类型在 contracts/events.ts)+ contracts 类型耦合,不互相 import service。
| 子任务 | 范围 | 关键交付 |
| --- | --- | --- |
| W2-A 认证/设置/实例/引擎面板 | 管理员登录门禁(照 docs/design/auth-reference.md:argon2id、session 表、setup token、限速落表、改密吊销)、settings、api_key 管理、secret 加密服务、downloader/media_server 实例与 library_root/path_mapping CRUD、审计查询、SSE 总线改 PG 通知(多进程)、引擎面板只读 API(openworkflow schema)、自动化总开关 | 登录/会话中间件供其他模块用 |
| W2-B 来源/身份/元数据 | site、site_account、feed_source CRUD 与启停;站点快照缓存与实时补查;ingestFeed/resolveObservation/refreshMetadata 处理器;observation/source_release 写入与多层去重;identity 解析(TMDB + title_alias 召回 + domain.resolveIdentity)→ media_entity/content_unit/external_identity;待识别池与 decision API;人工纠错映射 API;站点工作台数据 API(列表/搜索/详情 + 库存叠加) | 发出 `release.resolved`、`decision.created` 事件 |
| W2-C 规则/下载/入库/整理记录 | subscription、acquisition_slot、policy_revision、preference_tag(+suppression)CRUD;evaluateAcquisition/submitDownload/verifyAndImport/cleanupDownloadCache/reconcileLibrary 处理器;download_task 与 qB 同步轮询、标签接管、缓存卷硬阈值与恢复水位;Copy/Move 执行(adapters/fs + domain/organize)、path_binding 固定;organization_record 写入与公开增量 API(API Key);Emby 库存查询接入;媒体库列表/详情 API;首页六区块聚合 API | 消费 `release.resolved`;发出 download.*/organization.* 事件 |
| W2-D Agent 装配 + e2e | 14 个能力的 handler 接到 A/B/C 的服务;authorize(grant/approval)与 audit 注入;Agent 会话 API + SSE 增量;后台触发(识别歧义、下载失败诊断→有界 agent 任务);resolveDecision 处理器;fake 模型 e2e:"把 X 电影 1080p 收进来"闭环;RSS→入库 e2e;崩溃恢复注入 e2e | W2-A/B/C 完成后开始 |
## 6. v0.1 追加范围(用户 2026-09-27 晚间确认)
- **订阅业务逻辑对标 MP 并增强**:补 `docs/porting/moviepilot-subscribe.md` 迁移账本 + `docs/plan/05-subscription-gap.md` 差距清单,按清单补做(缺集计算、洗版、状态机、规则组、自动停止等),REQ §6 的增强点须落地。
- **文件管理页** `/console/files`:对标 MP 文件管理全部功能(目录浏览、重命名/移动/删除/新建目录、识别、整理、刮削、探测)并更好用(批量整理向导、dryRun 预览、拖拽、虚拟滚动),规格 `docs/plan/06-file-manager-spec.md`;安全:路径守卫、回收站、授权、审计。
- **Agent 会话页体验**参考 https://www.agentui.pro/(`docs/design/agent-ui-reference.md`)。
- **转移语义补齐**(来自 `docs/porting/moviepilot-rust-transfer.md`):挂死隔离、partial 清理、EEXIST 误报修复、onExisting 接线、TIMEOUT 错误码;空目录清理/hardlink/蓝光原盘留 W5。
- **MP Rust 加速函数的产品逻辑差分核对**(`docs/porting/mp-rust-logic-parity.md`)后按漏迁/bug 清单补迁。
## 7. v0.2 方向(用户 2026-09-28)
- Pi 上游全覆盖:pi-ai 全部 provider + 订阅式 OAuth 登录(Codex/ChatGPT、OpenCode Go、Claude、Copilot、Google 等),多上游共存与默认上游,服务端 token 刷新;方案 `docs/design/pi-providers.md`。
- 控制台信息架构重构:参考 MoviePilot 前端("发现→订阅→下载→整理→设定"流程;设定按接入对象分区),方案 `docs/design/console-ia.md`,用户审阅后实施(nav/路由重定向、设置页重构 + settings 迁移、页面合并)。
- **Pi 子代理**(用户 2026-09-28):SDK 无子代理,自研 `agent.spawn` 能力:父 run 派生有界子 run(同 runAgent 任务、独立会话文件),继承父授权上限、共享总预算与取消状态、并发上限(settings.pi.subagents{enabled,maxConcurrent,maxDepth}),子结果回填父会话为工具输出;UI 在会话页显示子会话树。
- **定时任务看板**(用户 2026-09-28,参考 MP「服务」页):`scheduled_job` 表 + 注册表统一管理所有周期任务(feed 轮询、站点快照、订阅搜索、qB 同步、对账、缓存清理、回收站清理、Pi token 刷新、备份、待识别复查)与「Agent 巡检」(目标模板、能力白名单、预算、触发条件);每项:间隔/cron、开关、立即执行、上次/下次运行、最近结果与耗时、失败原因、历史;各模块的 `start()` 计时器改为向注册表登记;页面做成看板(卡片分组:采集/下载/整理/维护/Agent 巡检)。
## 8. v0.2.1 方向(用户 2026-09-28 上午)
- **站点中心一处搞定**:资源 → 站点页 = 添加站点 + 凭证管理(账密 / Cookie / API Key,馒头 API Key)+ 站点卡片/列表(上次登录时间、上传/下载、分享率、用户等级、VIP、账号状态,数据来自 `sites/user-parsers` 的 `refreshSiteUserData`)+ 手动"检测健康"与"用凭证刷新登录状态";账密登录走 NexusPHP takelogin(验证码/二次验证失败 → 待处理并提示改用 Cookie);设置里只留全局默认(限流、快照周期)。参考 MP 站点管理页与 MovieClaw 站点卡片。
- **概览/仪表盘对照 MP**:保留 REQ 六区块,补 MP 仪表盘有价值的区块(媒体库统计、存储用量、下载器实时状态、最近入库、定时任务状态),砍重复。
- **订阅重做**(用户 2026-09-28):参考 MP 订阅页再增强——总览按状态(追更中/待搜索/已完成/暂停/待定)与上次更新时间分组;每条订阅显示库内已有 vs 订阅要求的对比(季/集覆盖、版本目标);多版本目标逐个显示缺失并可配置"自动补全其他版本";日历/即将播出可选。规格 `docs/design/subscriptions.md`。
- **收录策略 + 偏好标签二合一**:参考 MP 规则组(过滤规则字符串 + 优先级 + 订阅默认规则),做成一个"规则"页:硬条件/软偏好/优先级阶梯/排除,订阅与 Agent 引用;偏好标签作为规则的展示形态保留抑制记录语义。
- **整理命名模板语法**:对标 MP 重命名格式(Jinja 风格变量:title/year/season/episode/资源信息…),实现模板引擎(安全子集)+ 设置页表单(变量清单、示例、实时预览)+ 库根/作品级覆盖;整理与文件管理按模板生成路径。规格并入 `docs/design/subscriptions.md` 或单独 `naming-template.md`。
- **导航定稿(IA-3,用户 2026-09-28 两次意见)**:概览(首页、Agent 会话)/ 资源(站点、RSS 订阅)/ 订阅(订阅、规则)/ 整理(下载面板、整理记录[已整理/队列中/成功/失败/重名拦截,参考 MP 历史记录]、文件管理、媒体概览[原媒体库:本地账本=实体/外部映射/字段锁/资产与路径绑定/版本目标状态;筛选含待识别;详情含身份映射/历史])/ 系统(设置、定时任务、引擎面板、审计)。待处理不作分类:顶栏收件箱(角标)+ 首页区块 + 页面内联;"身份与元数据"取消;"业务任务"并入引擎面板;下载计划/缓存水位并入下载面板;整理记录公开接口不变。一级导航 19 → 13 项。
- **文件管理再做一轮**:对照 MP 文件管理逐功能比,按我们的逻辑重整前端(规格 06 基础上补 `docs/design/file-manager-v2.md`)。
- **全局搜索入口**(用户 2026-09-28,对标 MP 顶栏搜索 + 资源搜索页):顶栏搜索框 + `Ctrl/⌘+K` 命令面板;`GET /search?q=&scope=`聚合:本地作品(title_alias trgm)、TMDB 候选(可订阅/查看)、跨站资源(已启用站点搜索,限流 + 15s 超时 + 并发合并 + 短缓存,库存叠加,可下载/委托 Agent)、页面与操作跳转;结果页 `/console/search?q=` 支持筛选(站点/分辨率/来源/大小/免费/HR)与排序,资源行操作同站点工作台。
- **系统组收敛(用户 2026-09-28 讨论)**:设置 / 定时任务 / 运行日志(Tab:任务运行=引擎 runs 补业务字段与触发来源、审计、进程与健康=api/io worker/agent worker 心跳、事件循环延迟、SSE 连接数、DB、磁盘与挂载根状态)。定时任务卡片历史跳运行日志对应 run。最终一级导航 12 项。
- **IA-3 最终定稿(用户 2026-09-28 认可)**:概览(首页、任务、Agent 会话)/ 资源(站点、RSS 订阅)/ 订阅(订阅、规则)/ 整理(下载面板、整理记录、文件管理、媒体概览)/ 系统(设置、审计、运行状态)。统一"任务"概念:每次运行不分来源(定时/事件/手动/Agent)都是一条任务,带业务含义与时间线,引擎 step/attempt 只是详情折叠区;引擎面板与业务任务页取消;任务页 Tab"计划"承载定时任务配置(看板迁入),运行历史即任务流;首页增加"计划"带(下一次运行/启用数/快速开关)与正在进行/最近完成失败/待处理区块;待处理为顶栏收件箱。系统:设置、审计(任务详情链到相关审计)、运行状态(进程与健康)。共 12 项。
## 9. v0.2.1 收尾(用户 2026-09-28:遗留项先做完)
A 订阅收尾(日历、重置、keywords/库根/保存路径接入搜索与下载提交、MP 分辨率/来源/特效字段映射);B Agent/账户收尾(await 用 waitForFact 不占槽、TOTP 二维码、Passkey、主密钥轮换);C 文件管理契约 G6–G8 + DataTable groupBy + QA U6/U11(标点规范定稿并全站执行);D 性能与清理(inventory/media-servers 并行、识别远端查询入队、开发库测试残留 schema 清理脚本 dry-run);E 浏览器:camofox BrowserProvider 完整实现 + 站点工作台原站视图(截图流/点击输入/接管租约),mock 验证,真实实例待用户提供。
- **Agent 交互改为自动化优先(用户 2026-09-28)**:取消 plan_only/execute 模式;每会话默认执行;`agent.plan` 工具产出结构化计划 → 会话页 TodoList 实时更新;需授权动作在步骤处内联审批卡;`agent.propose_options` 让模型给 2–3 个方案供用户选择;"先给方案"作为会话内选项。**工具与 MCP 可视化**:设置 → Agent → 工具与 MCP:能力清单(id/作用域/风险/幂等/重放/当前授权状态/7 天调用与失败率)、我们对外的 MCP 服务端点(补实现:同一能力集,API Key 鉴权,REQ R13)与状态;会话页工具卡片可看 schema 与本次 IO。C1-B 提交后实施。
- **缓存与限流(用户 2026-09-28 讨论定稿)**:不引入 Redis/Deno KV。站点限流改跨进程:PG 令牌桶表(`SELECT … FOR UPDATE` 扣令牌),api/worker/worker:agent 共用;搜索结果、TMDB 响应等短期缓存进 PG `UNLOGGED` 表 + TTL + 定时清理;站点快照保持普通表;进程内只留小 LRU 热点缓存。TMDB 图片可用 Web Cache API 落盘。
- **TMDB 缓存与图片(对照 MP 定稿)**:不用 Web Cache API。TMDB 原始响应(搜索/详情/季集)进 PG `UNLOGGED` 表 + 24h TTL(未命中 30 分钟),三进程共享;识别结果本身是账本(external_identity/identity_candidate)。海报显示 + 图片代理(`/img/*`,参考 MP application/image.py):`DATA_DIR/cache/images` 按 URL 哈希落盘、ETag、大小上限与清理任务。
## 10. 需求覆盖与后续计划(2026-09-28)
逐条对照见 `11-requirements-coverage.md`。当前 main 相当于 REQ 的 M0 大部分 + M1 全部(仅 mock 环境)+ M2 约三分之一:R01–R32 已做 18、部分 11、未做 0、待真实环境验证 3;AT01–AT80 完整测试 44、部分测试 26、仅手工 2、未覆盖 8。不能称为 M2 首版候选。
### 10.1 v0.3(M2)建议范围
按 `11` §6 的优先级,分三组:
1. **核心差异化(必做)**:综艺分段与正片覆盖(节目级附加内容策略、完整版与分段的替换规则);`require_distinct_asset` 接进 Slot 落定;旧库分范围接管(首次扫描 → 登记 → 预览影响 → 按库根 / 作品开启,先修 `scanIncremental`)。
2. **真实环境核验(拿到环境即做)**:四站账号核验并补观众 H&R 选择器;camofox 按 `docs/runbook/browser.md` §6 核验同会话接管;一个真实 Pi 上游跑 e2 纵切;真实 Emby 验证后让已确认库存参与收录决策。
3. **自动化与可靠性**:Agent 偏好选片(独立预算、理由落计划);规则学习与样本回放;委托换源的替换计划;备份恢复演练自动化;测试补缺(AT18、AT29、AT42、AT73、流程版本升级)。
NFO / 图片投影、身份巡检与挂载根因聚合、HTTP 失败自动切浏览器、调查整个 qB 实例放在 v0.3 后半或 v0.3.x;Transmission / Plex、Skill 管理、业务 CLI 仍属 M3。c2a(跨进程限流与 PG 缓存)合入后再开 v0.3 的真实站点核验,避免真实站点在多进程下超限。
### 10.2 需要用户提供才能推进
| 项 | 用途 |
| --- | --- |
| 观众、馒头、HHClub、CHD 各一个可用账号(Cookie / 账密 / 馒头 API Key) | 四站真实核验、真实样本集、H&R 字段 |
| HHWEB 所指站点的准确域名;另外 1–2 个要接的站点 | 确认是否就是 HHClub,补站点定义 |
| qB WebUI 地址、账号、管理标签名;下载目录在容器内外的路径 | 下载器实例与路径映射 |
| 媒体库根路径与共享挂载方式(NFS / FUSE / mergerfs);现有目录命名样例 | 库根登记、旧库接管、命名模板默认值 |
| Emby / Jellyfin 地址、API Key、版本 | 真实库存查询与分页 / 认证验证 |
| camofox-browser 的仓库或 fork、版本、服务地址、访问凭据 | 原站视图与同会话接管核验 |
| Pi 上游:至少一个 API Key 上游,可选一种订阅登录 | 真实模型纵切、思考档位与压缩验证 |
| 综艺节目清单与 3–5 个节目的真实发布标题 | 分段覆盖规则与评估集 |
| 电影 / 剧集 / 综艺各自的版本目标(Slot)范围、Copy / Move 默认、H&R 与保留天数、缓存阈值 | 默认策略,避免把示例值当授权 |
| 升级后旧版本保留多久;合并版出现后是否替换分段 | REQ §17.2 未定的两项默认值 |
- **camofox 随部署提供(用户 2026-09-28 晚:没有自有实例,要求加上)**:compose 默认第三个服务 `camofox`(锁版本、内网端口、数据卷、健康检查),app 弱依赖;`BROWSER_PROVIDER_URL` 可覆盖为自有地址,可通过环境变量关闭;真机核验 REQ §4.7.4 六项,通过项才置 `verified`。
- **原站视图改用 Webtop 4.0(用户 2026-09-29)**:基于 linuxserver Webtop 4.0(Wayland + Selkies)提供真正的远程桌面画面,桌面内以有头模式跑同一个 camofox(Agent 仍经 camofox API 驱动,满足"同会话");完整中日文字体(Noto CJK)、中文/日文输入法(fcitx5 拼音 + mozc)、剪贴板双向;画面经我们的登录门禁反代嵌入,不暴露 Selkies 端口;方案 `docs/design/webtop-remote-browser.md`,不满足时保留截图模式回退。
- 追加(用户 2026-09-29):对比 Webtop 与浏览器镜像合一 vs 与本体镜像合一(协调者倾向前者:同会话、生命周期隔离、安全面、镜像大小);**视觉自动化闭环**:Agent 截图(多模态喂视觉模型)→ 坐标点击/输入 → 再截图,有界步数与预算,用于登录/验证码/交互式挑战;二次验证与本人设备确认不自动过;每步证据入待处理,失败转人工接管。能力:`browser.screenshot`(图片内容)、`browser.click_at`、`browser.type_at`、`browser.solve_challenge`(有界子流程)。
- **上游模型列表一键获取(用户 2026-09-29)**:`POST /pi/upstreams/{id}/models/refresh` 按 provider 调远程模型列表接口(OpenAI 兼容 /models、Anthropic /v1/models 等),存库带时间,UI 可搜索选择 + 手填,失败回退静态表。用户已接入 OpenCode Go 订阅,Agent 能力测试统一用 DeepSeek V4.1 Flash(视觉部分需视觉模型)。
- **AG-3 Agent 质量(来自 AGENT-REAL 真实上游验证,2026-09-29)**:补只读能力 subscription.list / decision.list / download.inspect(task.inspect 不再接收 download_task id);`download.plan` 的 requiresApproval 按授权范围判定;测试补全带 sessionId(OpenCode 需 x-opencode-session);等待期间 token/费用显示;超预算 run 在任务页标"超预算";同根因超预算不重复建待处理;汇报 Markdown 渲染;系统提示:禁止空/单字探查查询、参数类型(externalId 字符串)、子会话预算不超父剩余、中文思考与汇报、被中断时输出中文小结;重跑 AGENT-REAL 七场景。
- 进度(2026-09-29):除真机重跑外已完成——38 个能力(新增 3 个只读摘要,已进后台调查 / 巡检白名单);`coveringGrant` 与授权器共用;预算将尽提醒、被打断时丢弃中间过程文字;等待期间用量写回;任务页按 `business.outcome` 显示超出预算;超预算待处理按根因合并;汇报 Markdown 安全子集渲染;fake 模型 e2e 见 `apps/server/test/e2e/e15-agent-quality.test.ts`。开发库没有真实上游(只有 fake),AGENT-REAL a/e/g 真机重跑留给协调者重建用户栈后做。
## 11. 状态(2026-09-29 凌晨)与下一步
- 已交付:v0.2.4(v0.2.3 = Webtop 同会话桌面 + 视觉闭环 + Agent 质量 + 模型列表刷新 + Brave 搜索;v0.2.4 = 真机验收修复)。用户栈:app + 浏览器桌面(umt-browser-desktop:1.17.0)+ caddy(自签 HTTPS 443)+ postgres + 代理中继。
- 真机已验证:TMDB(搜索/识别/订阅季集/海报/缓存)、camofox 桌面模式(同会话/中日文/剪贴板/接管口令)、视觉闭环(example.com 2 步)、Agent 四场景(OpenCode Go / deepseek-v4.1-flash)。
- 待用户提供后真机验证:馒头 API Key、观众 Cookie、qB 地址、Emby;真实输入法(本机 IME 经 Selkies)与容器内 fcitx5 组字;四站选择器实测。
- 遗留:子会话思考仍英文;`agent.spawn` 重复调用;camofox 空闲 5 分钟回收标签页(接管期间需心跳);地址栏越界与 `about:` 页限制;桌面单窗口跨站点可见性;§10 未做清单(综艺分段、旧库接管、双版本组合校验)。
- **站点请求克制(用户 2026-09-29,憨憨接入)**:对真实站点的验证请求总量 ≤15 次/间隔 ≥10 秒;产品默认:快照 ≥30 分钟只抓首页、健康检测 6 小时、用户数据 6 小时、搜索并发 1、站点限流 1 次/3 秒突发 2、详情结果缓存 24 小时;站点页显示最近 24 小时请求次数。资源列表格式对照 MP 重做(主行标题+标签+促销/HR 徽标,副行副标题/分类/大小/做种/时间/库存叠加)。
- **站点内添加 RSS 简化(用户 2026-09-29)**:站点详情里添加 RSS 只填地址与用途,站点/账号/解析器/轮询默认自动带出;来源页的任意 RSS 保留完整表单并按域名建议关联站点;提交后立即抓一次并显示条目数;passkey 脱敏显示。
- **待处理泛滥治理(DEC-1,用户 2026-09-29)**:原因是每条 RSS 条目都查 TMDB,no_match 即建待处理事项(短剧类 66 条)。对齐 MP:资源只在匹配订阅或用户搜索时识别;改为惰性识别(本地解析+召回,TMDB 仅在命中订阅别名/新作品规则前置过滤/用户点开/搜索时查),未匹配进待识别池(可搜、有界复查、30 天归档),待处理只收相关且歧义的并按作品/来源合并;存量迁移入池;`identity.eagerTmdbLookup` 默认关。
- **短剧等 TMDB 缺失作品的自助补全(META-1,用户 2026-09-29)**:能力 `metadata.enrich`(propose):Brave 搜索 + 受限抓取(平台页/百科类)→ 模型抽字段(标题/别名/年份/平台/集数/简介/海报 URL)带来源与证据等级 → 写本地临时实体与字段级元数据(source=web/agent,可锁可改,不冒充 TMDB);触发:池条目"补全"、订阅/规则命中但 TMDB 无结果、Agent 委托;按归一化标题缓存、每日预算;后续 TMDB 收录由身份巡检补映射;不改路径不下载。DEC-1 之后实施。
- **Agent 快捷面板与会话排版(AGUI-2,用户 2026-09-29)**:抽屉内「当前/历史」Tab,历史列表可搜索筛选并在抽屉内打开(可续跑),"在页面打开"次要;会话页:阅读列宽、同回合分组、工具卡默认一行折叠、步骤清单改右侧栏/顶部折叠条不再 sticky 遮挡、预算在右栏底、长汇报折叠、输入区固定底部。
- **凭据统一注入浏览器(BR-2,用户 2026-09-29)**:打开站点浏览器会话时服务端把账号 Cookie(手填或自动登录得到的)注入 camofox profile,截图/桌面模式无需再登录;交还/关闭/定时从浏览器回写 Cookie 到凭据存储,HTTP 抓取/RSS/快照共用;账号显示登录态来源与时间;Cookie 不进日志/模型。
- **站点详情页抓取(用户 2026-09-29,憨憨 details.php)**【INT-2 已做,见 runbook/sites.md「详情页扩展内容」】:`SiteReleaseDetail` 扩展简介/MediaInfo/截图(经图片代理)/IMDb·豆瓣·TMDB ID/文件清单/标签/上传者;只按需抓(点开详情或评估缺字段),kv_cache 24 小时;详情抽屉展示;IMDb/豆瓣 ID 作为强证据进身份解析(TMDB find)。
- **META-1 后续**:真机 4 条短剧在白名单站点均无结果——扩大抓取白名单(短剧平台/百科类/豆瓣搜索快照/Bing 摘要模式),必要时只用搜索摘要抽字段不抓页;记录命中率。【META-2 已做(分支 meta2,2026-09-30):来源分类 + 各来源抽取器 + 置信度合并、豆瓣与聚合站只用摘要、robots.txt、命中率表;Bing 不做(API 已停,抓结果页算绕过)。真机 4 条全部从搜索摘要命中,见 runbook/identity.md「真机记录(META-2)」】
- **身份匹配对照 MP(ID-2,用户 2026-09-29)**:对照 MP recognize_media/tmdbapi 匹配链与 meta 中英名提取,用憨憨真实标题做差分集(MP Python vs 我们),修到持平或更好并固化差分测试;修复憨憨「英文 [中文]」格式、续作/年份权重、副标题中文名、同标题多版本合并;修后重评现有 identity_ambiguous。
- **任务与计划分开展示(FE-6,用户 2026-09-29)**:概览组下"任务"(运行记录/状态,含详情与重跑)与"计划"(定时任务配置)为两个独立页;计划页改为按组的紧凑列表(名称/开关/间隔可就地改/上次结果与耗时/下次运行/立即执行/历史),计划页分两节:「自动任务」(程序确定性周期:紧凑列表)与「Agent 巡检」(Agent 自主决策:卡片,目标模板/能力白名单/预算/上游模型/触发条件/每次费用,历史链到会话);首页"计划带"分别计数并链接。
- **缓存与存储预算(CACHE-2,用户 2026-09-29 讨论,按 4C/8G/100G)**:统一 kv_cache 各 region TTL 延长(TMDB 搜索 7 天/详情 30 天/未命中 1 天,追踪作品按访问续期;站点详情 7 天但促销/HR 下载前复核;网页搜索 1 天);图片缓存改容量 LRU 20GB;PG 内存 shared_buffers 2G/effective_cache_size 5G/work_mem 32M(容器 3G);磁盘分配 PG ≤25G(20G 告警)/图片 20G/备份 20G(14 份)/会话与证据 10G;保留策略定时清理:观察 90 天、SSE 7 天、心跳 24h、任务运行 60 天、Agent 事件 90 天、审计 180 天、能力统计 90 天、待识别池 30 天归档;高流水表 created_at 索引 + autovacuum 调优 + 每周 ANALYZE;运行状态页"存储"一节;设置「自动化 → 存储与保留」。
- **中文名回退链与每集元数据(MD-1,用户 2026-09-29,Severance 无简中名)**:`metadata.languages` 回退链 zh-CN→zh-SG→zh-TW→zh-HK→en-US;translations/alternative_titles 入别名;每集 name/overview/air_date/runtime/still 字段级入库;媒体详情页对照 MP(背景图/海报/评分/类型/主创/季折叠/每集行含剧照与库内状态);`POST /media-entities/{id}/refresh?full=1` 补齐存量。
- **浏览器会话重启自愈(BR-3,用户 2026-09-29 反馈"标签页不存在或已关闭")**:camofox 重启后旧 tab 404 → 服务端自动重建上下文、注入 Cookie、回到原 URL 并重试;`POST /browser/sessions/{id}/reconnect`;前端自动重连一次并提示;容器实例变化时全部会话标 needs_reconnect。
- **VER-3 真机核对(2026-09-29)**:通过——登录态注入、搜索两阶段、待处理 6、Agent 抽屉、快照字段/时区/手机折叠;问题——严重:RSS 标题「[电视剧]」前缀被解析为中文名导致 15 条误映射到《东北有个周东北》(ID-2 修:分类前缀、别名守卫、快照更新重解析、错映射撤销脚本);中:RSS 条目未规范化(分类/大小/标题)、详情页只抓到 MediaInfo/豆瓣/标签(HH-2 修);低:请求计数不持久、「12 Memories」误判 E12、会话页乱码字符、原站视图默认显示「Agent」。
- **数据治理后续(FIX-6 之后)**:全量回刷覆盖季 0(特别篇);TMDB 已确认的季集单元由临时转正式(沙丘:预言 仍为临时);同作品同季同集重复临时单元合并(征途 E09/E10 各 3 条);清理脚本修正后对用户库 dry-run 为 0 删除,未执行 apply。
- DH-1 已做(分支 dh1):`metadata.includeSpecials`(缺省开)full 刷新 / 回刷含季 0;刷新时对上 TMDB 季集结构的临时单元转正式(`content_unit.source`,迁移 0053);重复单元合并(refresh 一步 + `identity-cleanup.ts --merge-duplicates`),懒建加咨询锁防竞态。用户库只读 dry-run:征途 S01E09/E10 各 4 条 → 各保留 1 条,共删 6 条,未 apply。见 runbook metadata.md / identity.md。
- **去 AI 味改造(DEAI-2,用户 2026-09-29 拍板)**:"版本目标"全站改名「收录版本」;ID/hash/token/耗时/trace/原始字段默认折叠进「技术信息」(设置可改默认展开);"Agent 上游"改名「模型服务」;首页只回答"在忙什么/要我处理什么/最近入库了什么",去掉运行状态与计划两块;计划页维护类任务收成一行「数据清理」可展开;"未经核查"只在最终汇报保留一处;错误码不直接显示给用户;"无法确定作品"事项主按钮为「指定作品」;其余按 `docs/design/de-ai-review.md` P0/P1/P2。
- **搜索快速筛选(SEARCH-3,用户 2026-09-29,对照 MP)**:搜索结果与站点资源列表顶部多选筛选条:分辨率、编码、版本类型(原盘/Remux/蓝光压制/WEB-DL/HDTV)、站点、促销、HDR/DV、制作组、季集;选项从当前结果聚合并显示计数,可组合、可清除、记住上次选择。**已实现(分支 search3)**:`qf` 参数与分面口径见 `docs/runbook/search.md`「快速筛选」。
- **计划页与智能体(用户 2026-09-29 追加)**:计划页「Agent 巡检」不应沉底,移到上方;自动任务按分类分组、每组可折叠(并入 DEAI-2)。用户可见的"Agent / Agent 会话"统一改称「智能体」(并入 DEAI-2)。智能体对话整体偏繁琐、无效信息多 → AGUI-3 先做只读易用性审查出方案,DEAI-2 合并后再落地。
- **入库版本可见(LIBV-1,用户 2026-09-29)**:以《人生切割术》为例,作品/季/集只显示"已入库"不够,要显示入库的是哪个版本(分辨率、来源、编码、HDR、制作组、大小、文件位置),同一集多个文件并列;为之后的"多版本收录目标"打地基(数据模型一集可挂多个版本,与「收录版本」目标可比对:已满足/缺哪个版本)。
- 状态(libv1 分支):已实现。模型沿用 media_asset + asset_unit_coverage(一集多资产),规格规范化写 `media_asset.delivery`(`specVersion` 口径,domain `versionSpecOf` = 文件名 + ffprobe + 入库时的资源解析);`GET /library/{id}/versions` 返回每季每集版本、季 / 作品汇总、单目标比对 `matches[]`(satisfied / missing / upgradeable,多目标按数组追加);整理入库时写全规格;旧数据用 `scripts/asset-spec-backfill.ts` 回填(默认 dry-run,`--probe` 只对本地文件跑 ffprobe,`--apply` 写库)。前端:季头汇总行、每集一行规格、「N 个版本」展开、技术信息折叠(待 DEAI-2 TechInfo 合入后统一)、电影「入库版本」面板。
- **智能体对话重做(AGUI-3,审查见 `docs/design/agent-chat-review.md`)**:按报告 P0/P1 落地,DEAI-2 合并后从新 main 开。协调者裁定:"选定方案即批准"只在方案绑定同一 planId+payloadHash 且卡片写明动作要点(对象、大小/数量)时成立;删除、覆盖类动作仍需单独确认;"先看方案再执行"改为单次指令 `/先给方案`。
- 状态(agui3 分支):已实现 P0 + P1。会话流改成「用户消息 → 一行进度(`CAPABILITY_ACTIVITY` 的 verb/noun 人话计数,1 次调用也折叠,展开每步一行,工具名 / ID / 思考进技术信息)→ 结论正文 → 没做:… [批准并重试] → 一行实际结果」;`agent.plan` / `agent.propose_options` 不再作为工具调用出现(旧事件回放时一并跳过);去掉右侧栏与页头目标,步骤 / 子会话 / 模型 / 预算进顶部概况条;方案卡一键选定(推荐项第一位、风险折叠、「都不要…」),审批卡只留摘要 + 批准 / 拒绝;抽屉去 Tab,头部「历史」弹层(最近 20 条,等你处理置顶,子会话收在父会话下)+「新对话」;列表筛选只剩搜索 + 全部 / 等你处理 / 失败;输入区只有输入框 + 发送 / 停止,空态 3 个快捷目标,`/先给方案`、`/重跑`,上下文显示名称标签。
- 选定即批准:`agent.propose_options` 的方案可带 `action`(capabilityId + planId);服务端提出方案时按计划当前 payload hash + revision 绑定并生成动作要点(对象、大小 / 文件数),管理员会话选定时建一份 `scope.via = option` 的已批准审批,执行时 `optionApproval` 核对能力、计划类型 + ID、hash、revision、有效期后放行;API Key 作答只选方案;移动 / 替换文件的整理、发布策略(覆盖)不算批准,执行前照常出审批卡。「批准并重试」:非计划类能力建一条只覆盖该能力、当天只能成功一次、1 小时过期的持续授权再追加消息重试;计划类能力只追加消息,重试时照常出审批卡。
- 旧设置:「先看方案再执行」开关原本只是每次新建时的本地状态,没有存进设置,无需迁移;API 的 `proposeFirst` 与旧会话 `scope.proposeFirst` 照常生效。
- prompt:新增「汇报格式」一节(第一句结论、用名字不写函数名 / 字段名 / UUID、≤10 行、多个资源才用表格),证据规则改成"写名称和关键数值";子会话与视觉子流程(`audience: "agent"`)不加。回归测试 `packages/agent/test/system-prompt.test.ts`。
- 偏离报告:verb/noun 放在 contracts `CAPABILITY_ACTIVITY`(报告写的 `capabilities-metadata.ts` 实际是 metadata.enrich 的处理器);「模型说的(未经核查)」卡删除,"未经核查"只留在实际结果行的技术信息里一处。截图 `apps/web/test/screenshots/agui3/`(before / after,mock 数据,不进 git)。
- 顺手修(协调者追加):e15「超预算」偶发失败是产品竞态——`finalize` 先把会话写成 budget_exceeded、再 upsert 超预算待处理事项,负载下观察者看到终态时事项还指向上一次运行。改为先写事项与任务行、最后写会话终态。
- **SEARCH-3 后续(协调者截图复核)**:新筛选条下方仍保留旧的一行(大小/免费/H&R/排序/清除筛选),"免费"与「促销」维度重复、"清除全部"与"清除筛选"并存 → 并为一行:H&R 进分面,大小与排序放右侧,只留一个"清除";元信息行的耗时进「技术信息」。DEAI-2 合并后处理。
- **LIBV-1 后续(协调者截图复核)**:每集都挂一个"技术信息"开关太碎 → 点整行展开;"2 个版本"展开后第二个版本出现在第一个版本的技术信息里,应并列成同级行;"可升级:分辨率 1080p 不在允许列表 [2160p]"改人话("可换成 2160p");统一改用 DEAI-2 的 TechInfo 组件。
- **下载面板(DL-2,用户 2026-09-29)**:每个种子显示下载/上传速度(及总速度);默认只显示未完成的;已完成的单独一个视图(Tab 或分段)。
- 状态(分支 dl2):`GET /download-tasks/board?state=active|completed`(两组计数、每种子速度/剩余时间/连接数、transfer/info 总速度,实时数据按实例缓存 2 s 并合并并发请求);最近一次整理结果带状态、原因码与未关闭待处理事项。前端分段记在本浏览器,页面可见时 4 s 刷新。**合并 main 后待办**:已完成行的失败原因改用 DEAI-2 的 `reasonLine`(现在只显示非内部码的原因),详情抽屉的实时数据放进 TechInfo 旁。
- **文件管理(FM-2,用户 2026-09-29)**:左侧目录树再往左靠,页面主体做大(文件管理页用宽布局);调研除视频播放外可集成的基础预览,先出调研结论。→ 已做(分支 fm2):文件管理宽布局(目录树贴侧栏、可拖宽/折叠、手机抽屉);首批预览(文本/NFO 编码探测、字幕前 200 条、.torrent、图片尺寸);调研见 `docs/design/file-preview-research.md`。
- **Worker 分道(WK-1,用户 2026-09-29 讨论后认可,排在本轮测试之后)**:不再拆进程;IO Worker 内分三条道——文件(整理/硬链接/ffprobe/校验,并发 2)、网络(站点/RSS/TMDB/搜索,3–4,单站限流另计)、维护(清理/回填/巡检,1,空闲时跑),并发可在设置调。拆进程的触发条件:心跳事件循环延迟在整理期间持续 >200ms,或整理需要贴近存储部署到另一台机器。
- **v0.3.0(2026-09-29 打包)VER-5 只读核对问题 → FIX-7**:中——智能体旧会话汇报直出 UUID/能力名/原因码(新提示词只管新会话,展示层要兜底);进度行"步骤 6/6 全部完成"与展开"已完成 4/6(跳过 2)"不一致;"派出了 5 个子会话"实际 2 个(汇报数字要以系统事实为准);巡检目标原样显示 `{{now}}`、`{{openDecisions}}` 占位符;汇报称"下载器不在系统内"而 qB 已配置(查能力返回)。低——种子详情"未关联本地作品(unidentifiable)"直出码、"解析结果"JSON 默认展开;srt 被标"主文件 是";版本行悬浮按钮盖住大小/时间;作品页"还没有收录版本"与"4 个版本"易混;会话标题直出 ISO 时间与 download_failed。LIBV-1 回填已对用户库执行(5 个资产)。
- 状态(分支 fix7):根因与改法——① 汇报兜底:`components/agent/report-humanize.ts` 展示时把 UUID(按本会话工具结果与子会话配名字,认不出缩成"…")、能力 ID(换能力标题)、原因码(`reasons.ts` 补了能力错误、下载盘判定、站点登录、待处理类型,`cache:` 这类前缀按后半段查)换成人话,原文进「正文原文」折叠区,历史不改;② 概况条把跳过算进分子,步骤清单只数完成——统一为 `stepProgress`:「步骤 4/6 · 完成 4 · 跳过 2」;③ "派出了 5 个子会话"是进度行把 3 次被预算拒绝的 `agent.spawn` 也算进去(产品计数错):失败的调用不计入动作计数,实际结果行加「派出 N 个子会话」(会话树),汇报格式加"数量以工具返回为准";④ 巡检执行时占位符已正确替换(补单测),界面显示的是模板本身:改成名称标签,会话标题里的 ISO 时间转「9月28日 18:53」,`download_failed` 等码转人话;⑤ 该会话运行时 qB 尚未添加(会话 12:06,qB 12:09 建),但根因是智能体根本没有查下载器的能力(`site.list` 不含下载器),新增只读能力 `downloader.list`(下载器与媒体服务器、连通状态、管理的任务数);⑥ 种子详情按识别状态说人话、解析结果 JSON 跟随技术信息默认收起;"主文件"只认视频(整理把字幕登记成独立资产时其位置 is_primary 为真,文件列表接口按扩展名收紧);悬浮操作只在鼠标悬浮时出现,选中行不再常驻遮挡;作品页空收录版本写「没设收录要求」,版本行写「库里有 N 个版本」。追加:文件管理目录树改成与主面板同款圆角卡片(头部「目录」+ 折叠钮与工具栏第一行对齐,拖宽与折叠记忆保留)。截图 `apps/web/test/screenshots/fix7/`。
- **导航与站点类型(IA-2,用户 2026-09-29)**:「任务」归入系统组;站点、订阅、规则合为一组(组名「获取」,用户原话"都算做规则",组名可再改);独立的 RSS 页取消——RSS 属于站点,在站点详情里管理;"站点"增加类型,PT 站之外的下载来源(公开 BT、自定义 RSS 源,以后 Torznab/Jackett/Prowlarr 等)都作为一种站点类型,各类型声明自己支持的获取方式(搜索 / RSS / 详情 / 登录)。
- 状态(ia2 分支):已实现。导航 4 组(概览:首页 / 计划 / 智能体会话;获取:站点 / 订阅 / 规则;整理;系统:任务 / 设置 / 审计 / 运行状态),组名常量 `ACQUIRE_GROUP_TITLE`;`/console/sources*` 只做跳转。站点 `kind`(pt / public_bt / rss_feed / torznab)+ `capabilities`(search / list / rss / detail / login / userinfo,类型 ∩ 站点定义,contracts `SITE_KINDS` / `siteCapabilities`);torznab 只定义 `TorznabSiteConfig`,不能添加。服务端调度 / 搜索 / 登录按 capabilities 判断(`sources-identity/site-kinds.ts`)。迁移 0055:孤立 RSS 按主机归入自定义 RSS 源站点;用户库只读核对孤立 RSS 0 条。详见 `docs/runbook/sites.md`「站点类型与获取方式」。
- 偏离与补充:获取方式比任务单多一项 `list`(列表抓取 / 快照,调度需要区分);"计划"仍在概览组(用户只提了任务);公开 BT 站目前没有站点定义,实际只有 RSS(有定义后自动获得搜索 / 列表);`POST /feed-sources` 不带站点时自动归入同主机的自定义 RSS 源(兼容旧调用方,而不是 400)。截图 `apps/web/test/screenshots/ia2/`(不进 git)。
- **浏览器 UA 与指纹(BR-4,用户 2026-09-30:不着急,先研究明白)**:现状 HTTP 默认 Windows Chrome 140 UA、浏览器是 camofox(Firefox 内核)自生成 UA,同一站点账号 Cookie 两边互注 → UA 不一致。方向:每个站点账号一套固定 UA(跟随 camofox 的 Firefox)+ 固定指纹,浏览器与 HTTP 共用,不轮换,浏览器升级时同步。先做只读调研(camofox 能否按会话指定/持久化指纹、各站会话与 UA 绑定情况、MP 做法),出报告再定实现。
- 调研见 `docs/design/browser-ua-research.md`。用户决定(2026-09-30):对外伪装 **Windows**;全实例**一套固定指纹**(插件 `browser:launching` 首次生成落盘、之后复用);浏览器与 HTTP **按站点统一出口 IP**;站点适配器 / RSS 目前发 `Deno/2.9.7` UA 的问题**和 BR-4 一起做**(不单独先修)。cf_clearance 等 CF Cookie 回写时标"仅浏览器"。平滑迁移:下次重新登录 / 浏览器回写 Cookie 时原子切换 Cookie+UA。
- **已实现(br4 分支,待合并)**:固定 Windows 身份(插件落盘 `/root/.camofox/umt-identity.json`、`GET /umt/identity`)、站点请求 UA 全链路跟随浏览器(优先级 手动 > 待切换 > 全局 > 浏览器)、迁移 0056 待切换、CF Cookie 仅浏览器、出口按站点(浏览器按上下文代理,不再用容器环境变量代理)。运维与限制见 `docs/runbook/browser.md` §11;上线需重建 app + camofox。
- **下载计划并入下载面板(DL-3,用户 2026-09-30 同意)**:acquisition_plan 后台保留(批准绑定、预算/空间预占、防重复、升级/换源追踪);界面去掉「下载计划」页签——等批准/等空间/等预算的计划作为「进行中」行显示(需操作的同时进待处理);已提交的计划并入对应下载任务,详情加「为什么下这个」;失败/过期进下载历史,细节进技术信息;用户可见处不再出现"下载计划",统一说"下载"。
- **文件预览不加 ffmpeg(用户 2026-09-30)**:视频缩略图 / 截图条不做,镜像不引入 ffmpeg(+141MB);`docs/design/file-preview-research.md` 对应项标为不做。
- 用户 2026-09-30:BR-4 直接做完上线;《征途》重复单元不再处理(刷新时自动合并即可)。
- **用户 2026-09-30 决定**:短剧补全(META-1 后续)继续做;文件预览的音频 / 压缩包 / 原盘目录不做;修完零碎问题后接其他站点真机测试。
- **每集简介英文(MD-2,用户以《心动的信号》为例)**:协调者核对——TMDB 匹配正确(103147),每期标题为 zh-CN;但 TMDB 上这些集没有中文简介,只有英文"简介"(实为标题英译),MD-1 的语言回退链把 en-US 填进了简介。改法:简介的回退链只到中文变体(zh-CN→zh-SG→zh-TW→zh-HK),不回退到英文,没有就留空并显示"暂无简介";标题仍可回退英文;存量数据按新规则重算(只读 dry-run 后应用)。**状态:已实现(分支 md2,待合并)**——adapters `overviewLanguages`(简介链)+ 设置 `metadata.overviewNonChineseFallback`(缺省关)+ 详情页「暂无简介」+ 存量重算 `scripts/overview-recompute.ts`(只读 kv_cache,缺省 dry-run);见 runbook metadata.md「存量简介重算(MD-2)」。
- **零碎问题(2026-09-30 讨论)**:跨进程限流已在迁移 0041 做完,CHANGELOG「已知限制」过期条目删除;子会话思考英文 → 子会话系统提示补中文要求 + 测试;`agent.spawn` 重复调用 → 同目标去重(返回已有子会话);camofox 空闲 5 分钟回收 → 桌面接管 / 智能体会话打开期间由 app 定期保活,关闭后照常回收;憨憨 UA"待切换"→ 站点页「刷新登录」即可切换,不另做。
- **智能体提示词与 Skill 工程化(AG-5,用户 2026-09-30)**:用户贴出一份汇报反例(`docs/design/agent-report-sample-bad.md`),要求"结构化地编写提示词和 Skill,充分考虑我们的情况做工程适配"。方向:系统提示分层(角色 / 环境事实 / 能力地图按意图 / 行为规则 / 汇报规则);按任务类型做 Skill(巡检、收录一部作品、入库问题排查、订阅追更、数据清理…),用 Pi agent-core 的 harness Skill 机制按意图加载;汇报规则:结论先行、人话单位、不直出内部字段、不让用户提供自己能查的 ID、授权缺失一句话 + 按钮;用固定提示集做前后对比评测(fake 模型 + 少量 DeepSeek V4.1 Flash 真跑)。
- **E6 偶发(2026-09-30)**:合并 AG-4/AG-5 后全量 ci 一次 `e6-auto-recover` 两例"等待超时:qB recheck 调用",单跑与第二次全量均过(机器负载 4.8 时出现)→ 记为时序抖动,后续若再现再排查(疑似等待预算偏紧)。
- **计划页智能体巡检卡片统一尺寸(FIX-9,用户 2026-09-30)**:卡片等高等宽,多出来的内容省略(目标一行截断、汇总固定行数),点开看详情。
- **健康检查误判(HC-1,用户 2026-09-30 接观众站)**:"测试"登录成功但健康检测报"未登录"——健康检查抓 index.php 找 logout 链接,与用户数据解析两套判定打架;改为以用户数据解析成功为准并细化失败原因。
- **观众详情页适配核对(DET-2)**:用真实详情页(≤3 次请求)逐项核对并修,脱敏样本进差分测试。
- **Cloudflare 验证处理(CF-1,用户 2026-09-30)**:搜索入口有时随机出 CF 验证。先用浏览器复现并研究:HTTP 端遇 CF 挑战时的处理策略(浏览器过挑战后同步 cf_clearance+UA 重试 / 该请求改由浏览器执行并解析 / 站点级"走浏览器"标记与时效),对照 MP(FlareSolverr / CloakBrowser)与 BR-4 的固定身份,出方案后实现。
- DET-2 结果:观众详情页修了促销/到期、上传时间、做种数、hash、副标题、标签、上传者、简介清洗、截图过滤;站点没有 TMDB 与单种 H&R。**后续**:资源增加豆瓣 / IMDb 评分字段(页面有,本工具没字段);观众文件清单是点开后另加载的端点,待核对(需 1–2 次请求)。
**CF-2 已实现(分支 cf2,待合并)**:出口统一(站点请求明确直连 / 站点代理)、挑战不算凭据错误、浏览器过挑战级联(自动 / 视觉子流程 / 转人工)、
cf_clearance 有条件给 HTTP 重试、浏览器代抓与 6 小时浏览器代理期、站点页展示;见 `docs/runbook/sites.md`「Cloudflare(CF-2)」。
- **刮削(SCR-1,用户 2026-09-30 问"刮削测试过了吗")**:协调者核对——**没做**:文件管理里的 scrape 只是把元数据刷进本工具的库,不往媒体目录写 NFO / 海报 / 剧照(runbook files.md 已注明"NFO/图片投影仍未做");示例库里只有 mkv/srt。照 MP 的刮削实现(tvshow.nfo / season.nfo / 每集 nfo / movie.nfo,poster / fanart / season-poster / 剧照,Emby/Jellyfin/Kodi 兼容),入库后自动刮削 + 手动刮削 + 覆盖策略,再在示例库上验证。
- **搜索筛选对齐 MP(SEARCH-4,用户 2026-09-30)**:筛选维度太少,对齐 MP 搜索结果页的全部筛选(站点、季集、促销、分辨率、质量/来源、特效 HDR/DV、视频编码、音频编码、制作组/字幕组、字幕/语言、分类、年份、大小/做种等,以 MP 前端为准逐项核对);筛选弹层半透明看不清 → 改不透明实底。
**状态(分支 scr1)**:已实现——任务 `scrapeMedia`(文件道)、设置「刮削」、作品详情刮削状态与「重新刮削」、文件管理「刮削」;NFO 与 MP `TmdbScraper` 差分(登记差异见 `apps/server/test/scrape/nfo-deviations.json`);测试库 + 临时目录验证《人生切割术》S01 两集 + 《星际穿越》自动刮削。见 `docs/runbook/scrape.md`。
**SEARCH-4 已实现(分支 search4,待合并)**:新增音频、字幕、配音、特性、年份、分类、大小、做种 8 个分面(解析器补配音语言),筛选条按 MP 顺序、其余进「更多筛选」;
全站悬浮层改不透明实底。MP 对照表与不做的项(特效字幕、官种、3D、音乐音质)见 `docs/runbook/search.md`「与 MP 的对照」。
- **刮削照 MP 简化(SCR-2,用户 2026-09-30)**:MP 在文件管理里对目录/文件直接"刮削"——按路径与文件名识别作品(可选 TMDB 匹配),就地在该目录生成 NFO 与图片,不要求先是本工具登记的资产;我们现在"仅有 active 资产的作品才能刮削"太复杂。改为:文件管理任意目录/文件 → 刮削(识别 → 确认或自动 → 就地写),已登记资产走同一条路;入库后自动刮削沿用。
- **整理记录照 MP 补齐(HIS-1,用户 2026-09-30:"整理记录功能做的又太简单")**:对照 MP 历史记录页逐项补:列表字段(海报/作品/季集/源路径→目标路径/方式 移动·复制·硬链接/状态/时间/来源 下载器·手动·自动)、筛选(状态/类型/时间/关键字)、单条操作(重新整理、删除记录并可选删源文件/目标文件、查看详情)、批量操作、失败原因人话、统计;做差分对照。
- **用详情页 ID 识别(ID-3,用户 2026-09-30:"既然能抓详情页,详情有 TMDB/IMDb/豆瓣,为啥还判断不了是什么资源")**:现状——列表/详情里的显式 ID(`siteExplicitIds`)识别时会优先用,但详情页只在用户打开时才抓;标题识别不出时不会主动去抓详情拿 ID,就直接进待识别/待处理。改为识别升级链:标题 → 不确定时抓详情页取 IMDb/TMDB/豆瓣 ID(限流、缓存、只对有意义的资源:搜索结果、订阅候选、待识别池条目)→ TMDB find/详情 → 确定;豆瓣 ID 经缓存的豆瓣条目(标题+年份)再匹配 TMDB;确定后回填资源与别名,不再对这类资源建"没识别出作品"事项。
-111
View File
@@ -1,111 +0,0 @@
# W1 波末独立审查
审查日期:2026-09-27。基线提交 569bb29,审查后提交:608e567(收尾)、31729f2、9310814、c33034a(阻塞项修复)。
对照依据:任务书 `00-first-version-plan.md` 的 §2 约定、§3.1 第 2-9 条、§5 W2 拆分,以及 REQ v0.7 §2.1 的五条原则。`deno task ci` 全绿。
## 0. 结论摘要
- 数据模型:§3.1 第 2 条列的 36 张表都已建好(db 共 41 张),`src/types.ts` 由迁移反射生成。`schema.test` 和 `contracts/test/db-sync.test` 会拦截类型与迁移之间的漂移,这部分一致性良好。
- domain 没有 IO,也没有深路径 import。adapters、testkit、server、web 都没有跨包深路径 import,web 没有手写 DTO。
- 五条原则总体被遵守。例外有三处:qB 删数据缺少防护,跨卷移动曾只按大小判断目标一致(已修),目录扫描出错时仍报 complete。
- W2 的主要风险有两类:
- **类型映射**:能力描述有三套定义;domain 和 contracts 的状态枚举不一致。
- **装配缺口**:14 个能力都没有注册;server 的 `modules/` 骨架还没有;agent_run 事件没有落库。
## 1. 阻塞 W2
| # | 问题 | 位置 | 修复建议 | 状态 |
|---|---|---|---|---|
| B1 | OutboxRelay 只看 `published_at`,没有用 db 0002 新增的 `status/next_attempt_at/attempts/idempotency_key/last_error`。结果是:投递后状态一直停在 pending;失败后每个 tick 都立即重试,没有退避;行上的幂等键也没用上。测试建的表和 db 的表结构不同 | `packages/tasks/src/outbox-relay.ts` | 按 ADR-002 §2.1 对齐:认领 pending 且到期的行;成功置 dispatched;失败按指数退避重试,达到上限置 dead | **已修** 31729f2 |
| B2 | contracts 没有 `release.resolved` 事件。W2-B 负责发出、W2-C 负责消费,两边并行开发会各自发明一种 payload | `packages/contracts/src/events.ts` | 新增 `ReleaseResolvedPayload`,字段为 sourceReleaseId、observationId、mediaEntityId、contentUnitIds、evidenceLevel | **已修** 9310814 |
| B3 | 能力描述有三套定义,且互相冲突:agent 的 `CapabilityDescriptor`(scope 为 read/preview/propose/execute,risk 为 low…critical);contracts 的 `CapabilityDefinition`(risk 为 read/preview/write/destructive);contracts 的 `CapabilityDescriptor`(kind 含 approve,scope 用 ApiScope)。agent 与 contracts 还有同名的 `AgentRun`/`AgentUsage` | `packages/agent/src/capability/types.ts:11-33`、`packages/contracts/src/capability.ts:28-96`、`packages/agent/src/runtime/port.ts` | 以 contracts 的 `CAPABILITY_CATALOG` + `CAPABILITY_IO` 为唯一来源,删除 `CapabilityDefinition`;agent 改为派生描述,重名类型改名(如 `AgentRunHandle`) | **已修** db5352e |
| B4 | 14 个能力一个都没注册,连 stub 都没有。README 示例里的 `registerAllCapabilities` 并不存在;测试用的是手写 schema | `packages/agent/src/`、`packages/agent/README.md:56`、`packages/agent/test/helpers.ts:36-70` | 在 W2-D 开工前(或 W2-A 期间)补上 `registerAllCapabilities(registry, handlers)`,schema 取自 `CAPABILITY_IO`;加测试断言注册数等于 `CAPABILITY_IDS` | 部分:W2-0 提供 `capabilitySpec(id)`,各模块在 `registerCapabilities` 里注册;注册数断言留给 W2-D |
| B5 | domain 与 contracts 的枚举和字段不一致,server 必须写映射。具体有:下载状态(domain 的 `paused_by_threshold/paused_by_user/error/missing_files` 对 contracts 的 `paused+PauseReason/stalled/failed/missing/submitting/importing/imported`);证据等级(`needs_confirm` 对 `needs_review/manual`,且两边都叫 `EvidenceLevel`);身份结果(`auto/ambiguous/no_match` 对 `resolved/proposed/ambiguous/unidentifiable`);PathBinding 字段名(entityId/scope/season/templateVersion:number/active 对 mediaEntityId/bindingKind/seasonNumber/templateVersion:string/status);媒体类型(`movie/tv` 对 `movie/series/variety/…`) | `packages/domain/src/download-state/index.ts:2`、`domain/src/release-parser/types.ts:48`、`domain/src/identity/match.ts:7,19,158`、`domain/src/path/binding.ts:10-19`、`contracts/src/execution.ts:131`、`contracts/src/identity.ts:12`、`contracts/src/assets.ts:152` | 下载状态以 contracts 为准,domain 改为输出 `status + pauseReason`;domain 的 `EvidenceLevel` 改名为 `MatchLevel`;在 contracts 测试里加一条 domain↔contracts 枚举对照测试;字段映射集中写在 `apps/server/src/mappers/` | **已修** 42ecc93(映射集中在新包 `@umt/mapping`,domain 未改名) |
| B6 | `apps/server/src/modules/` 骨架和模块列表还没有。`http/app.ts` 的路由是硬编码;`worker/index.ts` 只有心跳,没有接入 tasks 引擎,注释"W1 接入"已过时。A/B/C 并行开发时会在 app.ts/worker 上发生冲突 | `apps/server/src/http/app.ts:98-113`、`apps/server/src/worker/index.ts:12` | W2 开工第一件事:先定好 `ServerModule { registerRoutes(app, deps); registerTasks(registry, deps) }`,在 app.ts 和 worker 里各留一个模块数组,并把 TaskEngine 和 OutboxRelay 接进 worker | **已修** 9858b84 |
| B7 | qB `delete(…, { deleteFiles: true })` 没有任何防护。注释里说的 `assertSafeToDeleteData` 在全仓库都找不到。接管任务的 save_path 如果落在媒体库内,会连带删掉库文件,违背 REQ §2.1 第 5 条 | `packages/adapters/src/qbittorrent/client.ts:164,589-593` | 实现 `assertSafeToDeleteData`:检查 content_path 不在任何 library_root 下,已验证入库,且没有被 asset_location 引用。W2-C 的所有删除都只经过一个 `safeDeleteTorrent` 入口 | **已修** fa92210 |
| B8 | mock qB 的 `complete()` 只改进度,不在磁盘上生成文件;带 `deleteFiles` 删除时也不删文件。这样"下载完成→Copy 入库"的 e2e 没有源文件可用 | `packages/testkit/src/qbittorrent/mock-qb.ts:91,161-171,402-405` | 增加 `materializeTo(root)` 或 `complete(hash, { writeFiles: true })`,按 save_path/files 写出临时文件;deleteFiles 为 true 时同步删除 | **已修** fa92210 |
| B9 | agent_run 事件没有落库路径。README 提到的 `agent_run_event` 表不存在,`events()` 只在内存里回放;agent 的事件类型(tool_call_start/end/rejected)与 contracts 的 `AgentToolCallPayload.phase`(started/succeeded/failed/denied)对不上 | `packages/agent/src/pi/runtime.ts:38`、`packages/agent/src/runtime/port.ts:16-45`、`packages/contracts/src/events.ts` | W2-D 新增迁移 `agent_run_event(run_id, seq, type, payload)`,唯一键为 (run_id, seq),并统一 phase 映射 | **已修** db5352e、da8e53b |
## 2. 应修
| # | 问题 | 位置 | 修复建议 |
|---|---|---|---|
| S1 | 跨卷移动时,已存在的目标只按大小比较就判为一致,随后删源(同尺寸、不同内容会丢数据) | `packages/adapters/src/fs/move.ts` | **已修** c33034a:预先存在的目标强制用 sha256 比较,并补了测试 |
| S2 | tasks 自带的三张表(`tasks_run_meta/tasks_fact/tasks_op_ledger`)仍走独立迁移器,没有并入 db,`types.ts` 里也没有这些表 | `packages/tasks/migrations/0001_tasks_tables.ts` | **已修** da8e53b:并入 db `0013_tasks_tables`,`migrateTasksTables` 已删除,tasks 测试用 db 迁移器建表 |
| S3 | `tasks_op_ledger` 与 `asset_operation` 职责重叠,两者都有 attempt_token 和 attempts | `packages/db/migrations/0009_execution.ts`、`packages/tasks/src/op-ledger.ts` | 建议不合并表,按层分工:`tasks_op_ledger` 只做通用 fencing,op_key 等于 `asset_operation.operation_id`(或 `download_task:<id>:submit`);`asset_operation` 是业务时间线和结果的事实来源,在 `withAttemptToken` 的 `commit` 回调里同事务更新。W2-C 开工前写进 ADR-002 |
| S4 | domain 下载状态机的 `ALLOWED` 允许除 removed 外任意两个状态互转,等于没有约束 | `packages/domain/src/download-state/index.ts:97` | 随 B5 一起收紧迁移表 |
| S5 | `organize/plan` 在调用方没传 `existingTargets` 时,按"目标不存在"处理(违背"失败≠不存在") | `packages/domain/src/organize/plan.ts:39,162` | 改为必填,或者不传时输出 hold |
| S6 | `scanIncremental` 在 readdir/lstat 出错时只记录 errors,status 仍为 complete,上层可能把读不到的目录当成缺失 | `packages/adapters/src/fs/scan.ts:81,104,144` | errors 非空时 status 置为 `partial` |
| S7 | TMDB 的 v3 api_key 放在 query 里,网络错误时 `e.message` 会带着完整 URL 拼进错误信息 | `packages/adapters/src/tmdb/client.ts:165,176` | 错误信息先经过 redact,或者只用 Bearer |
| S8 | 凭证接入方式不统一:站点走 `SecretResolver`,qB、TMDB、Emby、RSS 都是明文参数;脱敏实现有三份(adapters/rss、agent、server/logger) | `packages/adapters/src/rss/redact.ts`、`packages/agent/src/redact.ts`、`apps/server/src/logger.ts:11-40` | 在 contracts 或新的共享模块里只保留一份 redact(补上 `pass` 和裸 `c_secure_pass=` 两类);W2-A 的 secret 服务统一对外提供 resolver |
| S9 | adapters 的类型与 contracts 同名但含义不同:`SiteAdapter/SiteHealth`、`SiteKind`(mteam/api 对 mteam_api/generic_rss)、凭证 `apiKey` 对 `api_key`、熔断状态缺 half_open、MediaKind 为 movie/tv/music | `packages/adapters/src/sites/types.ts:109,122,174`、`adapters/src/rss/types.ts:474`、`adapters/src/sites/spider/spider.ts:29` | adapters 里的端口类型加 `Port` 前缀,并在 adapters 提供 `toContract*` 映射函数 |
| S10 | 自动化总开关、audit_event 写入、审计查询在 server 里都还没有;agent 的 authorize 也没读总开关 | `apps/server/src`、`packages/agent/src/capability/registry.ts` | 由 W2-A 实现,并把总开关放进 authorize 的第一道检查 |
| S11 | SSE 总线是进程内的环形缓冲。`--role api\|worker` 分开跑时会丢事件,重启后序号也会断 | `apps/server/src/http/sse.ts:111-141` | 已列入 W2-A:改为 PG NOTIFY + 持久化 seq |
| S12 | 模型可以直接传 `approvalId` | `packages/contracts/src/capabilities-io.ts:95,121` | authorize 要校验 approval 与 plan、payloadHash、expectedRevision 一致,并补一条拒绝用例 |
| S13 | agent 默认幂等键不含任务或计划的上下文,跨会话的相同输入会被去重 | `packages/agent/src/capability/registry.ts:51` | 有副作用的能力改用 planId/taskId 生成幂等键 |
| S14 | RSS 遇到 401/403 统一归为 `http_error`,passkey 失效无法单独识别 | `packages/adapters/src/rss/fetch.ts:192` | 新增 `auth` 错误类别,来源健康状态里单独展示 |
| S15 | agent README 描述的装配方式和实际不一致(见 B4、B9) | `packages/agent/README.md:46-61` | 随 B4/B9 一起更新 |
## 3. 建议
- `hasScope` 中 organize.execute 不包含 read,持这类 key 调 whoami 会得到 403(`apps/server/src/http/auth.ts:82`)。建议对照 REQ 确认 scope 的包含关系。
- web 端把 API Key 存在 localStorage,fetch 没有带 credentials(`apps/web/app/lib/api.ts:6-15`)。W2-A 改用会话 cookie 时要一并调整。
- adapters 和 testkit 的 README 只有三行,testkit 的 README 没提已经导出的 Emby mock。
- mock NexusPHP 没有 RSS 端点,e2e 需要把 mock RSS 的 enclosure 手动串到 download.php。testkit 里有两套 bencode 实现(`sites/torrent.ts`、`qbittorrent/bencode.ts`),可以合并。
- `packages/adapters/src/sites/spiders/testing.ts` 放在 src 下,却 import 了 `@umt/testkit`,建议移到 test 目录。
- domain 的身份匹配要求本地候选也必须带 externalId,"本地优先"只是平局时的规则。建议以 `localEntityId` 作为第一键(原则 1)。
- contracts 的能力目录测试只比对了 input,没有比对 output。"所有 schema 正反例"这条测试偏冒烟。
- qB 没有"接管"的高层接口,W2-C 需要用 list+addTags 自己组合。
## 4. 五原则核对
| 原则 | 现状 |
|---|---|
| 身份≠名字 | 表都用 uuid 主键。`external_identity` 的 (provider, type, id) 在 accepted 状态下部分唯一。media_entity 合并时通过 merged_into_id 跳转。domain 的"本地优先"做得不彻底(见建议) |
| 路径绑定固定 | `path_binding` 表和 `domain/path/binding.ts:94` 都优先使用已有绑定,出现多个历史目录时返回 conflict,测试覆盖了"改译名后仍用原目录" |
| 模型不是事实唯一来源 | registry 在没有 authorize 时拒绝 propose/execute,模型自称"已同意"会被拒绝(有测试)。风险是 approvalId 校验还没实现(S12) |
| 失败≠不存在 | TMDB、Emby、站点、`statSafe` 都能区分错误和不存在(Emby 返回 unknown,站点抛 auth_expired,挂载离线返回 root_offline)。例外:S5、S6 |
| 清理按明确授权 | 缓存清理的授权逻辑还没实现(W2-C)。qB 删数据没有防护(B7);跨卷删源已改为 sha256 核验(S1) |
## 5. 抽查测试(10 个)
| 用例 | 结论 |
|---|---|
| domain slot 状态机 | 有效:覆盖主路径、升级、attention、部分覆盖和 11 条非法迁移 |
| domain 路径绑定固定 | 有效:改译名后返回 existing,多个目录时返回 conflict,停用的绑定被忽略 |
| domain 身份歧义 | 有效:同等级不同身份判为 ambiguous,弱证据和无候选分支都有断言 |
| contracts 能力目录覆盖 14 个 | 部分有效:只比对了 input schema |
| contracts 全 schema 正反例 | 偏冒烟 |
| agent registry 授权拒绝 | 有效:模型自称已同意时被拒,handler 没有执行,审计里记为 denied |
| agent fake provider 闭环 | 有效:断言了事件顺序、seq、JSONL 和审计,但 schema 不是取自 contracts |
| server auth | 有效:覆盖 401、撤销、403 和生产环境禁用 dev key;SSE 鉴权没有覆盖 |
| adapters RSS 退避/熔断/去重 | 有效,并断言了错误信息里不含 passkey |
| adapters fs 跨卷 | 有效:EXDEV 不降级,目标不一致时不删源。同尺寸的情况已在 c33034a 补上;以 root 运行时,"删源失败"这条用例会被跳过 |
| tasks outbox | 修复后覆盖了并发下不重复投递、dispatched 状态、退避和 dead |
## 6. 收尾项说明
任务里说的"sites/mp-sync 有 9 处相对 import 缺扩展名",在基线里已经不存在了:`no-sloppy-imports` 零命中,`deno lint` 报的 9 条都是 `require-await`。所以这次收尾只做了以下几件事:删除 biome override;删除 `--unstable-sloppy-imports`;mp-sync 改为 deno shebang 并加可执行位;把 YAML 改成静态 import;更新 README 里的调用命令。`check.mjs --to e6820a8` 结果为 49 个未变,`movieclaw-import --check` 结果为 0 差异。
## 7. W2-0 处理结果(2026-09-27)
W2 打底,目标是让 W2-A/B/C 在互不冲突的前提下并行开工。`deno task ci` 全绿。
| # | 处理 | 提交 |
|---|---|---|
| B3 | contracts 为唯一定义:`CapabilityDescriptor` 增加 `replay`;`CapabilitySpec<I,O>`(描述符 + 真实 zod schema)与 `capabilitySpec(id)`(CAPABILITY_CATALOG + CAPABILITY_IO 组装);删除 `CapabilityDefinition`/`CapabilityRisk`。agent 删除自己的描述类型,注册中心直接收 `CapabilitySpec`,作用域改称 `kind`/`allowedKinds`,approve 类同样必须授权;agent 的 `AgentRun` 端口改名 `AgentRunHandle`,用量改用 contracts `AgentUsageTotals` | db5352e |
| B4 | 未做 `registerAllCapabilities`;改由各模块 `registerCapabilities` 用 `capabilitySpec(id)` 注册,注册数断言留给 W2-D | — |
| B5 | 新包 `@umt/mapping`(只依赖 domain + contracts):下载状态 + 暂停原因、证据等级、媒体类型、身份解析状态、Slot 状态/规则列/评估结果、ParsedRelease ↔ `source_release.parsed`、SiteRelease ↔ source_release、PathBinding、OrganizePlan → 整理记录/文件结果/organize.plan 输出。映射表为 `Record<联合类型, …>`,任一侧增值都会让 typecheck 或测试失败。domain 的 `EvidenceLevel` 没有改名,mapping 里以 `DomainMatchLevel` 称呼 | 42ecc93 |
| B6 | `apps/server/src/modules/` 骨架:`ServerModule`、`ServerContext`、`SERVER_MODULES`(四个占位模块);`bootstrap()` 装配上下文并连接引擎;worker 启动引擎 worker + OutboxRelay + 模块 `start()`;分工规则写在 apps/server/README.md | 9858b84 |
| B7 | `assertSafeToDeleteData({ contentPath, libraryRoots, importVerified })`;`delete({ deleteFiles: true, safety })` 必须逐 hash 附检查输入,不通过抛 `DownloaderError('unsafe_delete')`,请求不发出 | fa92210 |
| B8 | mock qB `complete(hash, { files, writeFiles })` 在 save_path 下写随机内容文件(默认关闭、有总量上限、防路径越界);`deleteFiles=true` 同步删除 | fa92210 |
| B9 | 迁移 `0014_agent_run_event`(主键 (run_id, seq),删 run 级联);contracts `AgentRunEvent`/`AgentRunEventRow` + 行转换 + SSE 映射(`agentRunEventToBusEvent`,tool phase 统一为 started/succeeded/failed/denied);agent `onEvent` 收 contracts 事件、按 seq 串行投递,`firstSeq` 续接序号 | db5352e、da8e53b |
| S2 | tasks 三张表并入 db `0013_tasks_tables` | da8e53b |
注意事项:
- `apps/server` 启动时连接任务引擎,数据库不可达会直接退出;`--role migrate` 顺带迁移 openworkflow schema,`api`/`worker` 分进程时不再各自迁移。
- `ctx.secrets` 仍是占位(调用即抛错),W2-A 在 `modules/auth-settings/provided.ts` 实现;`ctx.capabilities` 暂无 authorize,propose/approve/execute 一律拒绝,W2-D 在 `modules/agent/capability-registry.ts` 接入。
- 引入 @umt/db 后 pg 的 int8 全局解析为 number,依赖 `count(*)` 字符串结果的测试需改用 `Number()`。
-18
View File
@@ -1,18 +0,0 @@
# W4 待办汇总(来自 W2/W3 各组汇报,2026-09-27)
## 必须在 v0.1 修
- 生产容器:编译后二进制/非 root 用户无写权限 → Agent 会话 JSONL、库根哨兵、Copy/Move 入库都会失败。需要 `DATA_DIR` 卷(agent-sessions、备份导出)与媒体/下载挂载点可写,entrypoint 自检写权限。
- 前端调后端缺口:`POST /acquisition-plans`(手动提交下载,工作台下载入口)、`GET /artifacts/{id}`(dryRun 逐文件预览)、`/tasks` 关联(W2-D 已加 `/tasks`,前端接上)、整理记录合同补 `downloadTaskId`、inventory 补"已收藏/候选理由"、Feed 补疑似缺口字段。
- Agent 页:W3-3 按 mock 写的合同要对 W2-D 真实端点实测(`/agent/runs*`、SSE agent.* 事件),去掉本地镜像 schema 改用 contracts。
- SSE 对 API Key 调用者:EventSource 带不了 Bearer。决定:SSE 仅控制台会话使用;外部程序用 `GET /events?after=` JSON 轮询(若无则加)。文档写明。
- `/media-assets` 等在 api.ts 登记但未实现的端点:实现或从登记移除并加测试。
- 后端重复写同一 Slot 的候选评估记录(W3-2 观察)→ 去重或按 (slot, source_release) upsert。
## 应修
- 偏好标签修订历史端点;订阅/外部身份/别名列表带作品标题;订阅按类型/标题搜索;Slot 按 subscriptionId 过滤;Slot 当前资产带摘要;策略预览带资源摘要。
- decisions/summary、身份修订历史、待识别池 assign/retry 响应进 contracts。
- 账户弹层 TOTP 开关入口;引擎面板 rerun(用我们的 enqueue)。
- 季/集单元字段级元数据刷新;无站点 Feed 的 source_release 建立策略。
## 后置(M2+)
Passkey、主密钥轮换、子代理、原站视图/浏览器、Transmission、TTG 自定义适配、馒头 adult 模式、HHClub/CHD 真实站点核验。
-31
View File
@@ -1,31 +0,0 @@
# 主线程解耦审计(2026-09-27,只读审计结论 → W4-D 修复清单)
> 历史审计:本文记录当时的问题和修复过程。当前 `GET /inventory/media-servers` 已在 `routes/library.ts` 实现多实例并行、单实例并发限制和总超时,末尾「串行 Emby」不再是当前遗留。v0.3.7 的状态与新的工程检查见 [开发情况基线](13-development-baseline.md) 和 [工程检查与调整建议](14-engineering-review.md)。
## 结论
生产容器 API 与 worker 分进程;RSS/快照/刮削/入库主链路走任务引擎 + outbox。漏网三类:
1. HTTP 请求内同步重活:`POST /library/reconcile`(全量 stat)、`POST /download-cache/cleanup`(删种删数据)、`POST /organization`(planImport 递归 walk)、`POST /capabilities/{id}/invoke` 中 release.search/release.inspect/identity.resolve/catalog.lookup(远程站点/TMDB)、`GET /inventory/media-servers`(串行 Emby)。
2. acquisition-organize 后台循环(index.ts)绕过引擎直接调 `submitDownload`、`executeRecord`(Copy/Move)、`cleanupDownloadCache`、`reconcileLibrary`,串行 await;无租约/attemptToken 保护;一次大复制卡住 qB 同步与阈值暂停。
3. worker 并发度固定 4(openworkflow-engine.ts),runAgent 整段会话占 1 槽数分钟,与 verifyAndImport 大文件复制抢同一池。
事件循环丢租约风险低(复制/哈希流式让出;租约 30s/续租 15s);强制 sha256 路径(目标已存在、跨设备恢复)会全量读两遍。
## 修复清单(W4-D)
- P0 后台循环只编排:sweepPendingImports → enqueue verifyAndImport;sweepReservedPlans → enqueue submitDownload;超阈值清理与 6h 对账 → enqueue(幂等键按时间槽);tick 只剩 syncDownloads + 阈值判断,qB 同步独立定时器。
- P0 `/download-cache/cleanup`、`/library/reconcile` 改入队返回 {taskId, runId}(enqueueTracked);前端经任务页/SSE 追踪。
- P1 `WORKER_CONCURRENCY` 配置;worker 分组:`--role worker`(io 组:除 runAgent/resolveDecision 外,并发 4)与 `worker:agent`(并发 1–2),`startWorker` 两次各带 versions;模块 start() 循环只在 io 组;entrypoint 起三进程。
- P1 capabilities invoke 分级:外部 IO 型能力在 HTTP 路径强制 `AbortSignal.timeout(15s)` + 按站点/TMDB 限并发,或改入队返回 taskId;`POST /organization` dryRun 的 walk 加文件数上限。
- P2 fs/copy.ts:highWaterMark 1MiB;强制 sha256 全量比对改子进程 `sha256sum`(仿 ffprobe.ts 的 Deno.Command);executeRecord 打点。
- P2 worker 事件循环延迟探针(setInterval 偏差 >5s warn)。
## W4-D 修复状态(2026-09-27)
| 项 | 状态 | 说明 |
|---|---|---|
| P0 后台循环只编排 | 已完成(fe9693d) | `acquisition-organize/index.ts` 拆成两个自调度定时器:`sync`(syncDownloads + 阈值暂停/恢复)与 `plan`(编排)。超阈值 → 入队 `cleanupDownloadCache`(业务键 `threshold:<5min 槽>`,上一轮 pending/running 时跳过);`sweepReservedPlans` → 入队 `submitDownload`(业务行 `plan:<id>`,引擎键 `…:<5min 槽>`,上次 run 仍活跃则跳过);`sweepPendingImports` → 入队 `verifyAndImport`(业务行 `import:<dtId>`,同上);6h 对账 → 入队 `reconcileLibrary`(`auto:<6h 槽>`)。顺带修正:补入库巡检改按 `completed_at` 选(同步每轮刷新 `updated_at`,原条件永远选不中)。测试 `__tests__/background-loop.test.ts`:巡检只入队、50MB 入库(复制 + sha256 + 2.5s 慢 ffprobe)进行中 mock qB `torrents/info` 请求间隔 < 1s |
| P0 cleanup / reconcile 端点入队 | 已完成(fe9693d) | `POST /download-cache/cleanup`、`POST /library/reconcile` 改 `enqueueTracked` 返回 `{taskId, runId}`(202,幂等键按分钟槽);前端"立即清理"提示任务 id 并跳 `/console/engine/<runId>` |
| P1 worker 分组 + 并发配置 | 已完成(a9cee69、473c5e9、3792868) | `WORKER_CONCURRENCY`(4)/`WORKER_AGENT_CONCURRENCY`(2);`--role worker`(io 组 + relay + 模块循环)、`--role worker:agent`(runAgent/resolveDecision)、`--role all`(两池同进程);`@umt/tasks` 带 versions 的 worker 在认领 SQL 上按任务名过滤(OpenWorkflow 原生认领不看任务名,误认领会按 5s→5m 退避)。entrypoint 三进程互相看护。测试 `packages/tasks/test/worker-groups.test.ts`(去掉过滤即失败) |
| P1 capabilities invoke 分级 | 已完成(473c5e9) | `modules/agent/invoke-limits.ts`:release.search(实时搜索)/release.inspect(refreshDetail)/identity.resolve/catalog.lookup(includeRemote)在 HTTP 路径 15s 超时(502)+ 站点并发 2 / TMDB 并发 4(排队超时 429);许可在底层调用真正结束后才归还 |
| P1 `POST /organization-records` walk 上限 | 已完成(f9c20c6) | `ORGANIZE_WALK_MAX_FILES`(5000);dryRun 超出返回 `truncated: true`(计划只含前 N 个),正式执行 `too_many_files` 失败 |
| P2 copy highWaterMark / sha256 子进程 / 打点 | 已完成(f9c20c6) | 读写流 1 MiB;强制全量 sha256(复制后回读、目标已存在比对、跨卷恢复、计划期同尺寸目标)走 `Deno.Command("sha256sum")`,不可用(未安装 / 无 --allow-run)回退进程内流式,结果记 `hashVia`,`sha256Stats()` 计数;executeRecord 每文件打 `organize.transfer` 日志(耗时/字节/MB/s/hashVia) |
| P2 worker 事件循环延迟探针 | 已完成(473c5e9) | 1s 采样,偏差 > 5s warn + 计数,随心跳事件发 `loopLagWarnings/maxLoopLagMs` |
| 打包遗留 | 已完成(f6457a2、3792868) | 镜像带静态 ffprobe(mwader/static-ffmpeg:9.0.2),编译 `--allow-run=ffprobe,sha256sum`;`settings.agent.sessionDir`(null = DATA_DIR/agent-sessions)进设置页;镜像压缩 107→160.5 MB(ffprobe 层 135 MB 未压缩);umtw4d 临时部署验证 health、三进程、ffprobe/sha256sum 可调用 |
| 未做 | 遗留 | `GET /inventory/media-servers` 串行 Emby 仍在请求内(审计列出但不在 W4-D 清单);invoke 超时后底层调用继续跑完(handler 不全认 signal) |
-249
View File
@@ -1,249 +0,0 @@
# v0.1 独立验收审查
审查日期:2026-09-27。基线提交 `9e6ff33`。审查期间另一子代理在同一工作区修 `03-decoupling-audit.md`(W4-D),工作区里出现了它的未提交改动。本报告的结论只针对 HEAD `9e6ff33`,那些改动不在审查范围内。
对照依据:`00-first-version-plan.md` §3.1 / §3.2,REQ v0.7 §2.1、§15.1-15.2、§16.1(只看与 M1 相关的 AT),以及 `01`、`02`、`03` 三份计划文档、README、runbook 和 ADR。
## 0. 总体结论
**现在还不能打 v0.1.0。** 下面 4 个阻塞项处理完后可以打,都不需要改架构。
代码主体已经达到 M0+M1 纵切的要求。实测结果:
- `deno task ci` 连跑 2 次都通过(退出码 0),耗时 136 秒和 133 秒。两次测试数完全一致:server 136、adapters 236(另有 2 个 skip)、agent 30、contracts 508、db 12、domain 230、mapping 22、tasks 15、testkit 4、web 3,合计 1196 个通过。
- 前端冒烟 `deno task test:web:smoke` 22/22 通过,耗时 49 秒。
- 五条原则都有 schema 或代码层面的保障。
阻塞发布的原因有四个:
1. `03` 清单里的 P0 还没修完(后台循环绕过任务引擎;HTTP 请求里同步执行重活)。
2. 版本号还是 0.0.0。
3. REQ §15.2 要求 M1 必须验证的一项"授权内异常调查**与恢复**"没有实现,也没有在任务书里写明延期。 **已修(86af723):按方案 (a) 实现,见 R3。**
4. `POST /organization-records` 的 `sourcePaths` 可以指向服务器上任意路径,Move 时还会删源,违背原则 5。
补充说明:
- ci 耗时比任务书记录的约 75 秒涨到约 135 秒。其中 server 测试 55 秒、tasks 测试 34 秒。
- 冒烟跑在 `PORT=4392 WEB_ROOT=apps/web/build/client` 上,用单容器同端口托管方式,管理员账号 `w31-admin`。
## 1. 问题清单(按严重度)
### 1.1 阻塞发布
| # | 问题 | 位置 | 修法 |
|---|---|---|---|
| R1 | `03` 的 P0 未落地。`start()` 的 `tick` 仍在直接调 `planImport`/`executeRecord`(`sweepPendingImports`),没有租约保护;`/download-cache/cleanup` 和 `/library/reconcile` 在 HTTP 请求里同步执行 | `apps/server/src/modules/acquisition-organize/index.ts:118-151`,`routes/downloads.ts`、`routes/library.ts` | 按 03 清单 P0 改为 enqueue,并返回 `{taskId, runId}`。W4-D 合入且 ci 通过后,再重跑本报告第 3 节的 e2e |
| R2 | 所有包和 app 的版本都是 `0.0.0`,`/health` 返回的也是 `version:"0.0.0"`;仓库里没有任何 tag | `apps/server/deno.json:3` 等 10 处 | 统一改成 `0.1.0`(server 的 `denoJson.version` 会写进备份清单和 health),再打 `v0.1.0` tag **已修(ceff9cf):所有成员与根 `deno.json` 版本改为 `0.1.0`,新增 `CHANGELOG.md`;tag 由协调者打** |
| R3 | REQ §15.2 要求 M1 验证"授权内异常调查与恢复",AT68 也要求"查证后自动执行获准修正并继续"。现在后台调查固定是 `plan_only`:E5 只把结论写回待处理事项并交还人工,没有自动恢复这条路径 | `apps/server/src/modules/agent/service.ts:323-340`,`test/e2e/e5-auto-investigate.test.ts` | 二选一。(a) 在有 `automation_grant` 时允许调查 run 以 `execute` 模式调用 `task.retry` 或 `identity.resolve`,并加一条 e2e:下载失败 → 调查 → 授权内重试 → 入库。(b) 在 `00` §3.2 写明"授权内恢复延到 M2",并在 README 的范围说明里注明。不能不写就发布 **已修(86af723):按 (a) 实现。`agent.autoInvestigateMode=recover` 且有覆盖恢复动作的持续授权(绑定策略须为 published)时,调查 run 以 execute 模式运行,白名单为只读能力加 `task.retry` / `download.recheck`(新增能力)/ `identity.resolve`,换源、删除、清理不在白名单内。恢复结果按 download_task / task / source_release 的业务状态核实,核实通过后事项 resolved,run 汇报"已恢复(系统核实)"。没有授权时降级为只读调查,并在事项上留下建议。同一根因的尝试次数有上限。新增 e2e `e6-auto-recover`,覆盖 missingFiles → recheck → 核实、无授权降级、换源被拒、去重、尝试上限和预算超限** |
| R4 | `POST /organization-records` 的 `sourcePaths` 没有任何根目录约束。持 `organize.execute` 的 API Key 可以 walk 任意目录(dryRun 时会泄漏文件清单),也可以把任意文件 Move 进媒体库并删掉源文件。这条路径不检查 automation_grant,也不检查 H&R | `apps/server/src/modules/acquisition-organize/services/organize.ts:229-241`,`routes/organization.ts:84-130` | `sourcePaths` 必须落在已登记的下载器路径映射(本机视图)或显式的导入根下,并且不能在任何 library_root 内;`action=move` 且调用方是 api_key 时要求有持续授权。补 403/422 反例测试 **已修(ceff9cf):`sourcePaths` realpath 后必须在已登记的 library_root 或下载器路径映射本机前缀下,拒绝 `..` 与符号链接逃逸(遍历用 lstat 不跟随),dryRun 同样受限 → 400;Move 需管理员会话或 API Key + 覆盖作品的 automation_grant / 与请求一致的一次性 approval(用后 consumed)→ 否则 403;纳管来源 H&R 未满足 → 409 + 待处理,不改 Copy。测试 `__tests__/organize-guard.test.ts`** |
### 1.2 应修(v0.1.x 内,11 项)
| # | 问题 | 位置 | 修法 |
|---|---|---|---|
| S1 | 持 `organize.execute` 的 API Key 可以 `POST /approvals` 再 `PATCH /approvals/{id}` 自己批准,再去调 `download.submit` 或 `organize.execute`,单次审批就形同虚设 | `packages/contracts/src/api.ts`(approvals 的 scope),`modules/agent/routes.ts:~280-311` | 把审批的决定动作(PATCH 改为 approved)的 scope 改成 `admin`,或者用 `requireAdminSession`;并禁止 `decided_by` 与计划发起人是同一个 api_key **已修(f48e1aa):`POST /approvals`、`PATCH /approvals/{id}` 标 `auth:"session"`,只接受管理员会话,API Key 一律 403** |
| S2 | 服务端 logger 的脱敏覆盖面比 agent 和 adapters 的实现窄。实测 `redactString("/rss?rsskey=abc&torrent_pass=dead&authkey=zz&downhash=q1&c_secure_pass=cc")` 只遮住了 passkey。现在一共有 4 份脱敏实现(W1 的 S8 还没解决,反而多了一份) | `apps/server/src/logger.ts:6-8`,`packages/agent/src/redact.ts`,`packages/adapters/src/{rss,browser}/redact.ts` | 在 contracts(或新建的共享包)里保留唯一一份实现,取 agent 版的参数全集;logger 改为复用它;在 `logger.test.ts` 加上述 5 类参数的用例 **已修(f48e1aa):键表与规则收到 `@umt/contracts` 的 `redact.ts`,logger 与 `redactForModel` 共用;adapters 的 rss/browser 两份仍独立,留到 M2 收口** |
| S3 | TMDB 网络错误时,`TmdbError` 的 message 直接拼上 Deno fetch 的错误信息。Deno 的这条信息里带完整 URL,也就带着 `api_key`;`cause` 里同样有(W1 的 S7 没有解决) | `packages/adapters/src/tmdb/client.ts:171-175` | message 先经过 redact,`cause` 不透传;或者强制使用 Bearer(accessToken) **已修(f48e1aa):message 经 `redactUrl`,`cause` 只留脱敏后的 name/message** |
| S4 | 服务端授权器的单次审批分支(payloadHash/revision 变化后失效、过期、用后标为 consumed、approvalId 与计划不符)**没有任何测试**。AT15/AT16 在产品层也没有被验证 | `apps/server/src/modules/agent/authorize.ts:115-137` | 新增 `test/agent/authorize.test.ts`,对真实 PG 覆盖 6 条正反例;再加一条 e2e:审批后改计划 → 被拒 → 重新审批 → 成功且只消费一次 **已修(f48e1aa):`test/agent/approvals.test.ts` 覆盖 8 条拒绝分支与改计划→重批→只消费一次→不可重放;e2e 版留给 E2 系列** |
| S5 | M1 要求的"一部常规剧集"没有端到端链路。E1 和 E2 只覆盖电影;剧集只在识别单测(resolve)和整理单测(organize-move)里分别出现过 | `apps/server/test/e2e/` | 新增 E7:RSS 剧集 S01E01 → 识别出 series 和单元 → Slot → qB → Copy 入库;第二集改译名后仍进旧目录 **已修(ceff9cf):新增 E7 `e7-tv-series`:S01E01/E02 进同一绑定季目录、逐集覆盖、整季包按实际文件覆盖、总集数未知时 Slot 不满足且持续订阅继续;顺带修正剧集 Slot 在临时单元下被误判满足** |
| S6 | AT24(管理标签被移除、qB 任务消失)在服务端没有测试。代码在 `downloads.ts:359-383` 里,只有 domain 层的 `download-state` 单测覆盖 | `__tests__/vertical.test.ts` | 补两个断言:去掉标签后 `managed=false`、历史保留、之后不再暂停也不再清理;任务消失后 `removed`,并且库文件还在 **已修(ceff9cf):新增 E8 `e8-tag-removal`:去标签后 managed=false、完成不入库、清理不触碰,任务消失 → removed 且资产/位置不变、不重下;以 mock qB 调用日志断言** |
| S7 | 前端 Agent 页在生产构建里也能用 `?mock=1` 打开 mock 模式(sessionStorage 会记住),可能把演示数据当成真实运行展示,与 AT78 冲突 | `apps/web/app/api/agent.ts:37-48` | 只在 `import.meta.env.DEV` 或设置了 `VITE_AGENT_MOCK` 时启用 **已修(f48e1aa):只在 DEV 构建或后端 `pi.provider = "fake"` 时生效,生产构建忽略并清掉记住的开关(`app/api/agent-mock.test.ts`)** |
| S8 | 引擎面板只有 cancel,没有 retry/rerun;§3.1-10 要求 "cancel/retry 操作"(02 backlog 也列了这一项) | `apps/web/app/routes/console/engine*.tsx`,`modules/auth-settings/engine-routes.ts` | 加 `POST /engine/runs/{id}/rerun`,用我们自己的 enqueue、带新幂等键实现,并写审计 **已修(ceff9cf):`POST /engine/runs/{id}/retry`(admin,幂等键 `retry:<id>:<序号>`,审计),run 详情页"重跑"按钮;测试 `test/auth-settings/engine-retry.test.ts`** |
| S9 | 账户 TOTP 在控制台没有入口(只有登录页的第二步),02 backlog 里是应修项 | `apps/web/app/routes/console/layout.tsx` | 账户弹层接上 `GET/POST /auth/totp` **已修(W6-B,见 07 S-3):账户弹层"二次验证"入口,生成密钥(显示 base32 密钥与 otpauth 链接,无二维码)→ 验证码确认开启;已开启时凭当前密码 + 验证码关闭** |
| S10 | API 注册表把 `/auth/setup|login|session|logout` 标成 `read`,但实际上前三个免鉴权;`/auth/password`、`/auth/totp` 标成 `admin`,实际要求 admin 会话。按注册表生成的 scope 文档会误导外部集成方 | `packages/contracts/src/api.ts` | EndpointDef 增加 `auth: "public" | "session" | scope`,并加测试断言它与 `PUBLIC_AUTH_PATHS` 一致 **已修(f48e1aa):`EndpointDef.auth` 为 `public`/`session`,`api-registry.test` 断言与 `PUBLIC_AUTH_PATHS` 一致、session 端点拒绝 Bearer** |
| S11 | 文档与实际不一致:任务书 §2、§4 还写着 `pnpm ci`;deploy.md 的验证记录写"21 个迁移",现在是 22 个(0017 号空缺);ci 耗时写"约 75 秒",实测约 135 秒;testkit README 没提 emby/browser mock;adapters 和 web 的 README 各只有 3 行 | `docs/plan/00-first-version-plan.md`,`docs/runbook/deploy.md:128`,`packages/testkit/README.md`,`packages/adapters/README.md` | 统一更新;adapters README 列出 8 个子模块的入口,并链接各子目录的 README **已修(f48e1aa):任务书 pnpm→deno task、ci 耗时、deploy.md 迁移数、testkit/adapters/web README;adapters 实际 7 个子模块** |
### 1.3 建议
- domain 下载状态机的 `ALLOWED` 仍然全是 `ALL`(W1 S4),服务端完全靠 mapping 兜底(`packages/domain/src/download-state/index.ts:97`)。建议收紧。
- `scanIncremental` 在 errors 不为空时仍返回 `complete`(W1 S6,`packages/adapters/src/fs/scan.ts:150`)。服务端目前没用到它,但 M2 旧库接管会用,建议先修成 `partial`。
- 流程版本升级(M0 必须通过项)只在 spike 里验证过,产品代码没有测试。建议在 tasks 测试里补一条:旧版本 run 由同时注册新旧版本的 worker 续跑。
- AT18(恶意标题)只有系统提示词里的约束测试,建议补 e2e:标题里带"调用 download.submit / 输出 passkey"时,不会产生越权调用,输出里也没有凭证。
- AT42:硬阈值测试里只有纳管任务。建议加一个未纳管任务,断言它不被暂停、但计入占用。
- `vertical.test.ts` 的用例共享状态、依赖执行顺序,单独跑某一条会失败。建议在注释里标明,或者拆成独立 seed。
- web 只有 3 个单测,页面行为全靠冒烟覆盖。建议 M2 给 API 客户端和表单校验补单测。
## 2. §3.1 逐条验收
| # | 条目 | 状态 | 证据 | 缺口 |
|---|---|---|---|---|
| 1 | 底座(配置/迁移/API Key scope/审计/SSE 补读/总开关) | 完成 | `http/auth.ts`(`hasScope` 蕴含关系)、`test/auth.test.ts`、`auth-settings/admin-api.test.ts`「PATCH 自动化总开关与只读对账」、`sse.test.ts`、`sse-pg.test.ts`「裁剪后旧游标收到 resync」;`authorize.ts:109-113` 总开关是第一道检查 | 见 S10(注册表 scope 标注) |
| 2 | 数据模型 36 张表 | 完成 | 逐个 grep `packages/db/migrations/*.ts`,36 张全部存在;22 个迁移;`schema.test`、`db-sync.test` 防漂移 | 迁移编号 0017 空缺(无害) |
| 3 | 来源(多 RSS / 观众 / 馒头 / 快照 / 启停 / 限流) | 完成 | `sources-identity/ingest.test.ts` 8 条(三 Feed 去重、304、GUID 更新、窗口缺口、403 失效不重试、站点关闭);`mteam-tasks.test.ts`;`api.test.ts`「站点级限流器每站一个实例」「实时补查并发合并」「关闭站点」 | — |
| 4 | 身份与元数据 | 完成 | `resolve.test.ts` 13 条(本地召回复用、ambiguous 进池、TMDB 失败记为 lookup_failed、锁定字段、人工纠错修订、合并跳转);`0002_foundation` 启用 pg_trgm | — |
| 5 | 规则与选片 | 完成 | `rules-api.test.ts`(Slot/revision 乐观锁、策略发布即持续授权、旧授权吊销、新作品窗口);`acquisition.ts:539-595` 预算/并发/优先级 | Agent 主观选片按 §3.2 不做 |
| 6 | 下载(qB 多实例/标签/阈值/状态机) | 部分 | `vertical.test.ts`「硬阈值……只恢复阈值暂停项」「already_exists 接管只补标签」;`downloads.ts:861` 删数据必经 `assertSafeToDeleteData` | S6(标签移除/任务消失无服务端测试);R1(qB 同步与大文件复制在同一个 tick 里) |
| 7 | 入库(Copy/Move/EXDEV/路径固定/整理记录 API) | 部分 | `organize-move.test.ts`「跨卷 Move kill 后 resumed……不重复复制」「路径绑定固定」;`vertical.test.ts`「changes 游标增量」 | R4(sourcePaths 无约束) |
| 8 | 持久任务 9 个 + outbox + 恢复 + 幂等 + 取消 | 部分 | 引擎注册了 11 个任务(9 个 + snapshotSite + runAgent);`tasks/test/crash-recovery`、`engine.test`「取消经 abortSignal」、`op-ledger`「租约丢失旧 attempt 提交被拒」、`outbox`「两个 relay 并发 60 条只投一次」;E4 | R1:后台循环绕过引擎 |
| 9 | Agent(Pi 封装/14 能力/服务端授权/SSE/后台触发/设置) | 部分 | E2「启动断言 14 个能力全部注册」「没有授权被拒」「已提交≠已入库」;`agent/test/runtime.test.ts` 白名单、脱敏、预算、JSONL 恢复;E5 | R3(调查后不能恢复);S4(审批分支无测试);子代理按 02 后置 |
| 10 | 控制台页面 + 引擎面板 | 部分 | `apps/web/app/routes.ts` 共 20 个路由;冒烟 22/22 通过 | S8(引擎面板没有 retry);S9(TOTP 入口);原站视图只是占位(符合 §3.2) |
| 11 | 可选 Emby | 完成 | `adapters/src/emby/emby.test.ts`「凭据错误 → unknown(auth),而不是不存在」等 10 条;`vertical.test.ts` 调用了 `/inventory/media-servers` | 服务端只测了无实例的情况(建议补 unknown 透传) |
| 12 | 运维(备份/只读对账/单容器/runbook) | 完成 | `scripts/backup.sh`、`restore.sh`(校验和、有活动连接时拒绝、单事务、打开 readOnlyReconcile、映射行数比对);`deploy.md` 验证记录里有完整的 compose 演练;`preflight.test.ts` 7 条 | 恢复没有自动化测试,只有人工演练记录(可以接受) |
| 13 | 测试(domain 单测 / adapters / RSS→入库 e2e / Agent e2e / 崩溃恢复) | 完成 | E1、E2、E4(worker 子进程 `kill -9`)、`agent` 的 JSONL SIGKILL 用例;ci 两次数量一致 | S5(剧集链路) |
| 14 | 管理员登录门禁 | 完成 | `login.test.ts` 8 条(并发初始化只有一个成功、CSRF、同 IP 5 次 / 跨 IP 15 次限速、吊销、改密、TOTP 防重放);实测:已初始化后 setup 返回 409,缺少或伪造 csrf 返回 403,cookie 为 HttpOnly + SameSite=Lax | Passkey 按 02 后置 |
§3.2 不做项核对:综艺分段、双版本组合校验、浏览器原站视图、HHClub/CHD、旧库接管、Agent 主观选片、多用户,都没有越界实现。BrowserProvider 只有接口和 camofox 骨架(`effaa32`),符合"接口先落"。
## 3. REQ 验证项对照
### 3.1 M0 / M1 必须验证项 → 测试
| 验证项 | 测试 | 结论 |
|---|---|---|
| Pi 显式白名单、无隐式扩展 | `agent/test/runtime.test.ts`「白名单:未注册工具(bash)……被拒绝」;E5 `download__submit` 被拒 | 有效 |
| 执行器崩溃恢复 | `tasks/test/crash-recovery.test.ts`;E4;`runtime.test.ts`「JSONL 恢复 SIGKILL」 | 有效 |
| 流程版本升级 | 只有 `docs/spikes/w0b-openworkflow.md` | **缺**(建议项) |
| 提前审批不丢失 | 设计上审批存库、invoke 时再读;`tasks/engine.test`「waitForFact 事实先于等待到达不丢失」 | 引擎层有效,产品层**缺**(S4) |
| 多 Feed 并发不重复提交 | `ingest.test`「三个 Feed……release.observed 只发一次」(顺序执行);`outbox.test` 并发;E6「重复评估只留一条候选」 | 部分(没有并发 ingest 的测试) |
| 改名与映射修订后路径不变 | `organize-move.test`「路径绑定固定」;`resolve.test`「人工纠错映射」 | 有效(两者分开测) |
| 观众 + 馒头站点 | `api.test`(观众 mock)、`mteam-tasks.test`、adapters spiders 与 mteam 测试 | 有效 |
| qB 标签接管 | `vertical`「already_exists」「硬阈值」中 `adopted=2` | 有效 |
| Copy / Move | E1(Copy);`organize-move`(跨卷 Move) | 有效 |
| 一个电影 + 一部常规剧集 | E1/E2 电影 | **剧集缺**(S5) |
| 整理记录 API | `vertical`「changes 游标增量」;E1 | 有效 |
| 备份 / 审计 / 恢复 / 停用开关 | `deploy.md` 演练;`admin-api.test` 总开关;`preflight.test`「映射清单行数比对」 | 有效(恢复靠人工) |
| Agent 自然语言 → 查库存/搜索/选片/提交 → 持续流程入库 → 真实汇报 | E2「有持续授权:……汇报来自整理记录」 | 有效 |
| 授权内异常调查与恢复 | E5 只调查 | **缺**(R3) |
| 关闭聊天不影响后台 | E3「创建后不再请求,任务系统独立推进到 completed」 | 有效 |
### 3.2 指定 AT 场景
| AT | 场景 | 测试 | 结论 |
|---|---|---|---|
| AT01 | 多来源合并 | `ingest.test` 三 Feed | 有效 |
| AT02/AT09 | Slot 隔离 | `rules-api`、E6「按作品选 Slot」;授权按 Slot 独立预留 | 部分:没有"两个 Slot 同时缺失分别执行"的专项测试 |
| AT12/AT55 | 崩溃恢复 | E4、`op-ledger`「租约丢失」 | 有效;E4 的 kill 时机是"账本行出现",没有精确钉在"qB 已收单、DB 还没写" |
| AT13/AT27 | 复制后崩溃 | `organize-move` 跨卷 resumed | 有效(模拟上一次 attempt) |
| AT20/AT56 | 备份恢复 | 人工演练(deploy.md);`auth.test` 撤销后 401 | 部分(没有自动化) |
| AT16 | 授权失效重校验 | `rules-api`「新版本发布(旧授权吊销)」;审批分支**无测试** | 部分(S4) |
| AT23 | 标签纳管 | `vertical` 硬阈值用例 `adopted` + `origin:"adopted"` | 有效 |
| AT24 | 标签移除 / 任务消失 | 无服务端测试 | **缺**(S6) |
| AT25/AT26/AT41 | 缓存硬阈值 | `vertical`「硬阈值」「缓存清理」(库内陷阱任务被拒删) | 有效 |
| AT67 | Agent 一次委托闭环 | E2 | 有效 |
| AT70 | 已提交≠已入库 | E2 `waiting.summary` 含"已提交≠已入库",入库后才 `succeeded` | 有效 |
| AT79 | SSE 断线补读 | `sse.test`、`sse-pg.test`(resync)、E6「/events?after= JSON 补读」、E2 `afterSeq` | 有效 |
## 4. 路由 scope 表
共 185 个端点:read 89、organize.read 3、organize.execute 21、admin 72。所有业务路由都经过三个 helper 之一(`modules/*/http.ts`、`routes/helpers.ts`、`auth-settings/endpoint.ts`),统一加 `requireScope(def.scope)`。`api-registry.test` 保证 OpenAPI 与注册表双向一致。`POST /capabilities/{id}/invoke` 标的是 read,但处理器内部会再按能力描述符的 scope 校验(`agent/routes.ts:325`),例如 download.submit 需要 organize.execute,policy.apply 需要 admin。
**免鉴权清单**:`GET /api/v1/health`、`GET /api/openapi.json`、`GET /api/docs`、`POST /auth/setup`(已初始化后返回 409,token 至少 16 位)、`POST /auth/login`、`GET /auth/session`,以及 `WEB_ROOT` 静态文件。实测 `/events`、`/settings`、`/library`、`/auth/whoami` 无凭证时都返回 401。
| 资源 | read | organize.read | organize.execute | admin |
|---|---|---|---|---|
| `/acquisition-plans` | GET · / {id} | | POST · | |
| `/acquisition-slots` | GET · / {id} | | | POST ·; PATCH {id} |
| `/agent/runs` | GET · / {id} | | POST ·; POST {id}/messages; POST {id}/abort | |
| `/api-keys`、`/secrets` | | | | GET/POST/PATCH/DELETE |
| `/approvals` | GET · / {id} | | POST ·; **PATCH {id}**(S1) | |
| `/artifacts`、`/asset-locations`、`/asset-unit-coverage`、`/media-assets`、`/path-bindings`、`/observations`、`/reconcile-runs`、`/server-mappings` | GET · / {id} | | | |
| `/audit-events` | | | | GET |
| `/auth` | setup/login/session(免鉴权)、logout | | | password、totp(要求 admin 会话) |
| `/automation-grants` | GET · / {id} | | | POST ·; PATCH {id} |
| `/capabilities` | GET ·; POST {id}/invoke(内部再校验) | | | |
| `/content-units` | GET · / {id} | | | PATCH {id} |
| `/dashboard`、`/inventory`、`/inventory/media-servers`、`/site-definitions`、`/events`、`/health` | GET | | | |
| `/decisions` | GET · / {id} / summary | | POST {id}/resolve | |
| `/download-cache` | | | | POST cleanup |
| `/download-tasks` | GET · / {id} | | POST {id}/pause、resume | |
| `/downloader-instances`、`/media-server-instances`、`/library-roots`、`/path-mappings` | GET · / {id} | | | POST/PATCH/DELETE、test|verify |
| `/engine/runs`、`/engine/stats` | | | | GET;POST runs/{id}/cancel |
| `/external-identities` | GET · / {id} / revisions | | | POST ·; PATCH {id} |
| `/feed-sources` | GET · / {id} | | | POST/PATCH、fetch/enable/disable/reset |
| `/identity-pool` | GET | | POST {id}/assign、dismiss、retry | |
| `/library` | GET · / {id} | | | POST reconcile |
| `/media-entities` | GET · / {id} / detail | | POST {id}/identities、refresh;PUT/DELETE {id}/fields/{field} | POST ·; PATCH {id}; POST {id}/merge |
| `/organization-records` | | GET · / {id} / changes | POST ·(R4) | |
| `/policy-revisions` | GET · / {id}; POST {id}/preview | | | POST/PATCH、publish、rollback |
| `/preference-tags`、`/preference-suppressions` | GET · / {id} / revisions | | | POST/PATCH/DELETE |
| `/settings` | GET | | | PATCH |
| `/site-accounts`、`/sites` | GET · / {id} / snapshots / releases | | | POST/PATCH、enable/disable/snapshot、PUT credentials |
| `/source-releases` | GET · / {id} | | POST {id}/identity、unlock | |
| `/subscriptions` | GET · / {id} | | | POST ·; PATCH {id} |
| `/tasks` | GET · / {id} / operations | | POST {id}/cancel、retry | |
| `/title-aliases` | GET · / {id} | | | POST ·; DELETE {id} |
## 5. 安全快查
| 项 | 结果 |
|---|---|
| 凭证进日志 | 访问日志的 URL 经过 `redactString`,但它只认 passkey/token/access_token/api_key/apikey/auth/sign 这几个参数,**漏掉了 rsskey、torrent_pass、authkey、downhash、c_secure_pass**(S2,已实测)。对象字段按键名脱敏。`secrets.test`「SecretError 进日志只有 ref」和 `logger.test` 6 条通过。本次起的服务日志里 grep 不到未脱敏的 passkey/api_key |
| 凭证进模型上下文 | `pi/runtime.ts:157` 的工具输出一律经过 `redactForModel`,错误信息经过 `redactString`;`runtime.test`「工具输出进入模型与事件前已脱敏」。凭证存储为 `secret_ref` + AES(`auth-settings/crypto.ts`),响应里只返回 secretRef(`api.test`「凭证只写」、`admin-api.test`「Pi apiKey 只写」)。风险点:TMDB 错误信息里带 api_key(S3),不过 agent 侧的脱敏能认出 `api_key=` |
| 鉴权绕过 | 免鉴权清单见第 4 节;DEV_ADMIN_KEY 只在 `NODE_ENV=development` 下生效(`auth.test`);控制台管理员从 Web 手动调用算作人工授权(`authorize.ts:114`),API Key 必须有 grant 或 approval。漏洞:S1(自己批准)、R4(sourcePaths) |
| CSRF | cookie 会话的写方法用 HMAC 双提交校验(`auth.ts:170-176`),实测缺少或伪造都返回 403;Bearer 不校验。cookie 为 HttpOnly、SameSite=Lax,`secure` 看直连 https,或者来自私网代理的 `x-forwarded-proto`。login/setup 免 CSRF(可以接受) |
| SSE 鉴权 | `/events` 需要 read(`app.ts:102`),`sse.test`「需要鉴权」;实测用 cookie 连上返回 200 `text/event-stream`。给 API Key 调用方的 `?after=&limit=` JSON 补读已实现(E6)。注意:持 read 的 Key 能看到所有 agent 文本增量 |
| qB 删数据防护 | 仅有两个 `deleteFiles:true` 调用点(`downloads.ts:879`、`:902`),都经过 `removeWithData`,在本机视图和下载器视图上各跑一次 `assertSafeToDeleteData`;候选必须满足:已入库 succeeded、没有失败的文件级结果、不再被 download_cache/hardlink 位置引用、保留期和 H&R 已满足。Move 后删种用的是 `deleteFiles:false`(`organize.ts:1101`)。`vertical`「缓存清理」里的库内陷阱任务被拒删,有效 |
| API Key scope 覆盖率 | 185/185 个端点都有 scope,见第 4 节 |
## 6. 工程规范
- **提交规范**:48 个提交全部符合 `type(scope): 描述`,0 个违规。
- **命令一致性**:README、runbook、各包 README 里出现的 `deno task *` 都能在 deno.json 里找到;全文没有残留 pnpm/npm 用法(任务书除外,见 S11)。
- **包 README 与导出**:agent、db、domain、mapping、tasks 的 README 与导出一致。contracts 有 18 行,尚可。testkit 缺 emby/browser mock 的说明;adapters 和 web 的 README 只有 3 行(S11)。
- **ci 时长与稳定性**:2 次分别 136 秒和 133 秒,测试数与结果一致,没有抖动。server 测试 55 秒、tasks 测试 34 秒是主要耗时。
### 抽查 10 个关键测试(是否断言了真实行为)
| 用例 | 结论 |
|---|---|
| E2「没有授权:download.submit 被拒」 | 有效:断言了 tool_call_end 为 isError、SSE 的 phase=denied、审计 `capability.denied`、download_task 为 0、计划停在 draft、Slot 仍为 empty,汇报标注"未经核查" |
| E2「有持续授权……」 | 有效:断言了调用序列、账本 grant_id、run 先 waiting 再 succeeded、汇报引用整理记录 ID、`afterSeq` 补读、qB 只 add 一次 |
| E4 kill -9 | 有效:qB 只 add 一次、账本只有 1 行、`resumed` 事件、seq 无重复;kill 时机不够精确(见 3.2) |
| `organize-move` 跨卷 resumed | 有效:copy 为 skipped、delete_source 为 done、qB 删种时 deleteFiles=false、download_task 为 imported |
| `vertical` 硬阈值 | 有效:只暂停纳管的下载中任务、手动 resume 返回 412、恢复水位区间保持暂停、只恢复阈值暂停项、读不到磁盘时保守阻止 |
| `vertical` 缓存清理 | 有效:库内陷阱被拒删并给出原因,库副本仍在,asset_operation 已落库 |
| `ingest` 三 Feed 去重 | 有效:1 条 source_release、3 条观察、事件只发 1 次、时间分开保存、游标持久化 |
| `login` CSRF / 限速 | 有效:403/429 正反例齐全,被拦的请求不落表 |
| `sse-pg` 跨进程 | 有效:子进程发布、API 推送,裁剪后收到 resync |
| `agent/registry`「approve 类必须经授权」 | 部分有效:只测了注册中心回调,服务端 `createAuthorizer` 的审批分支没有测试(S4) |
## 7. 五原则核对(每条两处)
| 原则 | 保障 | 违背或风险 |
|---|---|---|
| 身份≠名字 | ① `external_identity_accepted_key`:(provider, entity_type, external_id) 在 accepted 状态下部分唯一(`0005_identity.ts:34`);② 合并时用 `merged_into_id` 跳转,旧 ID 保留(`resolve.test`「合并重复作品」) | domain 的匹配仍要求本地候选带 externalId(W1 建议项,没有修) |
| 路径绑定固定 | ① `path_binding_active_key`:(entity, season) 同时只允许一个 active 绑定(`0007_assets.ts:88`);② `organize-move`「译名变化后新集仍进旧目录」 | R4:任意 sourcePaths 的 Move 可以从别的库根把文件搬走 |
| 模型不是唯一事实来源 | ① `authorize.ts` 在服务端核对 grant/approval,忽略模型参数里的"已同意";② `agent/report.ts` 的汇报取自整理记录,模型文本标为"未经核查"(E2) | S1:外部 Key 可以自己批准 |
| 失败≠不存在 | ① 对账时库根离线只标 unavailable,缺失按 suspected → confirmed 推进(`vertical`「资产对账」);② TMDB 失败记为 `lookup_failed`,不进 no_match(`resolve.test`),Emby 失败返回 unknown | `scanIncremental` 出错仍报 complete(建议项) |
| 清理按明确授权 | ① `assertSafeToDeleteData` + `lockReason`(入库已验证、没有部分入库、没有引用、H&R);② 只读对账或总开关关闭时拒绝一切自动 execute(`authorize.ts:109-113`、`downloads.ts:557`) | R4:手动 Move 删源时不检查 H&R 和授权 |
## 8. 给 M2 的前 5 条建议
1. **合并凭证处理通道**:只保留一份共享的 redact 和 SecretResolver,qB/TMDB/Emby/RSS 统一走 secret_ref(W1 S8)。浏览器接入之后会有 4 类以上凭证流经日志和模型,在 M2 引入 camofox 之前就要收口,并加一条"全仓凭证泄漏"的故障注入测试(REQ §16.2 要求零容忍)。
2. **授权模型产品化**:审批的决定限定为管理员会话;调查型 Agent 在授权内可以执行恢复(R3 的完整版);对 grant/approval/budget 做基于属性的测试(AT15/16/32/52)。
3. **按 03 清单把 IO 与 Agent 分成两个 worker 组**,给 verifyAndImport、runAgent 分配独立并发度,并加事件循环延迟探针。在性能基线(REQ §16.2)里记录文件数、挂载类型和 ci 耗时,防止 ci 从约 135 秒继续膨胀(可以考虑按包并行跑 `-j`)。
4. **e2e 矩阵扩到剧集和部分失败**:剧集追更(S5)、整季包部分已有(AT54)、部分文件入库后不清理(AT29)、多下载器同一 hash(AT73)。这些是 M2 综艺分段建模的前置样本。
5. **把恢复和版本升级纳入 CI**:用 docker compose 跑"备份 → 清库 → 恢复 → 只读对账 → 映射比对"的夜间任务,再加一条 openworkflow 版本升级的回归测试,避免只靠人工演练记录。
## W6 治稳记录(2026-09-28)
起点:W4/W5 全部合入后两次全量 `deno task ci` 各挂不同用例(run1 E8 两例:`download_task` 停在 `importing`、C 没被清理;run2 E1 等 qB 收种超时、E6 预算超限用例汇总文案不符)。
**区分方法**:10 个 e2e 文件逐个串行各跑 3 次,30/30 全绿;失败只在 44 个 server 测试文件并发全量时出现。结论是没有"稳定必现"的回归,但其中两处是并发下真实存在的产品竞态,另一处是负载导致的超时。
### 真实缺陷(并发暴露)与修法
| 现象 | 根因 | 修法 |
|---|---|---|
| E8:入库记录已 succeeded,`download_task` 仍是 `importing`;随后缓存清理不认它(期望 removed 实得 importing) | qB 同步 `observe` 先在事务外读 `download_task` 快照,再整行覆写 `status`;入库事务在两者之间把 `importing→imported` 提交后,同步按旧快照合并出 `importing` 写回(丢失更新)。负载高时窗口变大 | `services/downloads.ts observe`:事务内 `select … for update` 重读该行,按锁住的最新状态合并后写回(`mergeObservedDownloadState` 对 imported 保持不变) |
| E6 预算超限:run 已是 `budget_exceeded`,summary 还是 finalize 的原文,之后才被改写成"未恢复:调查超出预算…" | 授权内恢复的核实结论由 finalize 之后单独一步 `annotateRunSummary` 追加,终态与最终汇总分两次写,观察者(UI/SSE/测试)能读到中间态 | `finalize` 新增 `summaryPrefix`,runAgent 把恢复结论随终态同一次写入并推送;`recover-summary` 步骤保留(已带前缀时幂等跳过),兼容旧 run 的步骤历史 |
| (W5-C 遗留双写) | `organize.ts verifyAndImport` 仍用本地 `slotComplete` 判定并直接把 once 订阅置 completed,与 `applySlotEvent(verify_passed)` 按总集数账本的判定并存;总集数未知/订阅 pending 时两者会给出相反结论 | 删除 organize 本地 `slotComplete` 与订阅完成更新,完成与历史只由 `applySlotEvent` 钩子负责;手动整理指定 Slot 的分支改调 `services/subscriptions.ts slotComplete` |
测试期望未改。
### 不稳定项与治法
- E1 等 qB 收种 60s 超时:全量并发时 44 个文件各起真实 worker/outbox/后台循环抢 CPU 与连接,编排链被拉长。治法:`apps/server/vitest.config.ts` 分两个 project,`unit`(明确列出的纯内存文件)并行,`pg`(其余全部,含 e2e 与 PG 集成,新文件缺省归此)`fileParallelism:false` 且 `groupOrder:1` 排在 unit 之后;`packages/tasks` 的 PG 引擎测试同样文件级串行。
- worker 并发在 e2e 夹具与崩溃恢复子进程里显式配置(io 4 / agent 2),不受环境变量影响;钩子超时 120s、teardown 60s。
- 已有保障复核:每文件独立业务 schema + 引擎 schema(pid+随机后缀),建/删在咨询锁上串行;mock qB/TMDB/RSS/站点端口均为 0(随机);等待均走 `waitFor` 轮询 + 超时。E8/E9 里剩下的 1.5s 固定等待是"一段时间内不应发生"的反向断言,保留。
- 未引入任何重试。
### 3 次全量 ci
| 次 | 结果 | 耗时 | 其中 server vitest |
|---|---|---|---|
| 1 | 全绿(server 44 文件 / 259 用例) | 369s | 227s |
| 2 | 全绿 | 341s | 232s |
| 3 | 全绿 | 337s | 228s |
代价:server 测试从并发约 90s 变为串行约 230s,tasks 约 52s;换来的是时序预算可预期。后续如需提速,可按 e2e 与集成拆成两个串行组分进程跑。
-158
View File
@@ -1,158 +0,0 @@
# 05 订阅业务差距与增强清单(对标 MoviePilot v3 @ e6820a8)
> 上游行为见 `docs/porting/moviepilot-subscribe.md`(下称"账本",§ 号指账本章节)。本文只比对行为,不含代码。
> 我们的实现范围:`packages/domain/src/{slot,acquisition}`、`apps/server/src/modules/acquisition-organize/{services,handlers.ts,routes}`、`packages/contracts/src/policy.ts`、迁移 `0004_content`/`0008_policy`;对照需求 REQ §5.4–5.5、§6,任务书 00 §3.1 第 5 条。
> 核验日期 2026-09-27,基于当前工作区(含未提交改动)。
## 0. 结论摘要
- **架构差异是有意的**:MP 是"订阅行 = 身份+季+过滤参数+下载事实(note)"的单表模型,完成即删除;我们是"订阅(追踪意图)+ Slot(收录目标/条件)+ 内容单元账本 + 资产覆盖"的分层模型,事件驱动(`release.resolved → evaluateAcquisition`)。REQ 要求的分层(多 Slot、升级不覆盖、确定性评估、预算)已具备骨架。
- **可比语义 34 项**(另 2 项不迁移):已覆盖/等价 11 项(32%),部分 7 项(21%),缺失 16 项(47%)。
- **最大风险不在"缺功能",而在两处会导致错误结果的缺陷**:①剧集完成判定固定 `totalKnown: true` 且只按"已见过的单元"算,`once` 订阅会在 TMDB/发布只露出前几集时提前完成(违反 REQ §5.4);②Slot 同时只允许一个在途计划,忙时到达的发布被记 `slot_busy` 后**永不重评**(`next_evaluate_at` 未使用),连续更新的剧集会漏集。
## 1. MP 语义 → 我们现状 → 处置
状态:✅ 相同/等价 · ◐ 部分 · ✗ 缺失 · ↔ 有意不同(REQ 已覆盖)。优先级 P0(错误结果/主链路)> P1(常用能力)> P2(增强)。工作量 S ≤0.5 天,M 1–2 天,L 3 天+。
### 1.1 创建与目标范围
| # | MP 语义(账本 §) | 现状(文件) | 状态 | 处置 |
| --- | --- | --- | --- | --- |
| 1 | 订阅 = (source,id,季) 一行;多季拆多行(§1、§2) | 订阅指向本地实体 + `season_unit_id` + `season_scope`;条件在 Slot(`routes/rules.ts` subscriptions.create,`0008_policy`) | ↔ | REQ 增强已覆盖,不照抄 |
| 2 | 创建时补 TMDB 季集表,算 `total_episode`,插件可只升(§2.3) | 创建不取季集;`content_unit` 只在解析发布时懒建(`sources-identity/service/entities.ts` findOrCreateUnit);TMDB 适配器已有 `seasons[].episodeCount`(`adapters/src/tmdb/client.ts`)但无人消费 | ✗ | **需补 P0 M**:`refreshMetadata` 写季单元 `metadata.episodeTotal`+来源,并按集数预建 provisional 集单元;未知则记 `totalKnown=false` |
| 3 | 同身份同季查重;历史存在则 Follow 不再建(§2.5、§10) | 无查重,同一作品可建多条 active 订阅 | ✗ | 需补 P2 S:创建时同实体+同季 active 订阅返回 409 或幂等返回旧行 |
| 4 | 标题识别失败则创建失败(§2.2) | `titleQuery` 订阅可建,但全链路只按 `media_entity_id` 取订阅,**titleQuery 订阅永远不被处理**(`services/acquisition.ts` evaluateRelease) | ✗ | 需补 P1 M:创建时同步 `catalog.lookup`/`identity.resolve`,需确认的进待处理中心;确认后回填 `media_entity_id` |
| 5 | `start_episode`/`total_episode` 裁剪范围(§3.3) | 只有季粒度(`season_unit_id`);无起始集、无手动总集数锁 | ✗ | 需补 P1 S:`subscription.params.episodeRange {start,end?, locked}`,评估与完成都按范围裁剪 |
| 6 | 默认订阅配置按类型回落(§2.4) | 已发布 acquisition 策略的 `defaultSlots` 自动建 Slot(`routes/rules.ts`、`AcquisitionPolicyRules`) | ✅ | 照现状 |
| 7 | 订阅级下载器/保存路径(§1) | `params.downloaderInstanceId`;保存路径取实例(`services/acquisition.ts` gates) | ✅ | 照现状 |
### 1.2 缺集与匹配
| # | MP 语义 | 现状 | 状态 | 处置 |
| --- | --- | --- | --- | --- |
| 8 | 缺集 = 媒体库缺集 ∩ 订阅范围 − note(含在途)(§3) | `satisfiedUnits`=该 Slot 计划产出资产+当前资产的 `asset_unit_coverage`(`services/acquisition.ts`);在途计划不算单元而是锁整个 Slot | ◐ | 需补 P0(并入 #22):domain 新增 `computeUnitGaps(required, covered, inflight, range)`;在途单元计为"已预留"而非锁 Slot |
| 9 | 标题/年份/别名匹配 + ID 冲突复核(§5 第 3–4 步) | 身份在 `sources-identity` 解析,给证据等级,`needs_confirm` 进待识别池;评估只接 `resolved` 发布 | ↔ | REQ 增强已覆盖(更严格),不照抄 |
| 10 | 季匹配:多季包拒、无季号视为 S1(§5 第 5 步) | `current_season` + `season_unit_id` 时按季号过滤;实体级 Slot 接受任何季;多季包不拒 | ◐ | 需补 P1 S:`current_season` 未设 `season_unit_id` 时取创建时最新季;多季包按 Slot 范围判定覆盖而非直接拒 |
| 11 | 规则组 `>` 分层 → pri_order(§6.1) | Slot `hard`/`soft` + `qualityTier` + `softRank` 确定性排序(`domain/src/slot/evaluate.ts`) | ↔ | REQ §6.1 已覆盖 |
| 12 | include/exclude 正则(标题+副标题+标签)(§5 第 8 步) | 只有 `excludeGroups`;无关键词包含/排除 | ✗ | 需补 P1 S:`SlotHardConditions.include/exclude`(正则白名单校验,匹配 title+subtitle+labels) |
| 13 | 订阅站点范围 ∩ 系统 RSS 站点(§5 第 1 步,`get_sub_sites`) | 无;任何站点的发布都参与 | ✗ | 需补 P1 S:`subscription.params.siteIds`,评估时 `rel.site_id` 不在范围 → skipped `site_out_of_scope` |
| 14 | 体积区间,剧集按每集体积(§6.1 `__match_size`) | `min/maxSizeBytes` 按整包体积(`evaluate.ts` checkHardConditions) | ◐ | 需补 P1 S:增加 `perUnitSize`(体积/覆盖单元数),季包与单集同口径 |
| 15 | 做种数/促销/发布时间规则(§6.1) | 无 | ✗ | 需补 P2 S:hard 增加 `minSeeders`、`freeOnly`;站点字段已在 source_release |
| 16 | 订阅识别词 custom_words(§5 第 2 步) | 无订阅级识别词 | ✗ | 需补 P2 M:放 identity 层(实体级别名/偏移),不放订阅 |
### 1.3 搜索与周期
| # | MP 语义 | 现状 | 状态 | 处置 |
| --- | --- | --- | --- | --- |
| 17 | RSS/首页刷新后逐订阅匹配(§5) | Feed 轮询 + 站点快照 → 识别 → `release.resolved` 事件逐发布评估(`acquisition-organize/index.ts` outboxRoutes) | ✅ | 照现状(事件驱动比批量匹配更及时) |
| 18 | 订阅主动搜索:新订阅 5 分钟、定时 24h(默认关)、手动(§4、§11) | 只在创建/改 Slot 时对**本地已索引**的最近 50 条重评(`kickEvaluate`);`release.search` 能力支持实时补查但无订阅调度 | ✗ | **需补 P0 M**:`searchSubscription` 任务 = 按实体标题/别名调 `snapshotSite(query)` → 走同一识别评估链;新订阅立即一次,缺口 Slot 按间隔(默认 24h,站点限流器兜底),手动按钮 |
| 19 | 忙时/无结果后下次还能再匹配(批量匹配天然重试)(§5) | Slot `reserved/downloading/verifying` 时发布记 `slot_busy` 后不再评估;`next_evaluate_at` 有列无消费 | ✗ | **需补 P0 S**:Slot 离开忙态(verify_passed/failed、release)时入队按 Slot 重评;巡检消费 `next_evaluate_at` |
| 20 | 元数据定时刷新 6h,完成前再刷总集数(§8) | 无订阅相关元数据刷新调度 | ✗ | 需补 P0 M(并入 #2):追踪中的剧集每 6h `refreshMetadata`,完成判定前强制刷新一次 |
### 1.4 洗版/升级
| # | MP 语义 | 现状 | 状态 | 处置 |
| --- | --- | --- | --- | --- |
| 21 | 电影洗版:pri 严格提升,100 即完成(§6.2) | `evaluateCandidate` upgrade:`minTierSteps`、`ceilingTier`、压制组白名单、同档不换 | ✅ | REQ §6.1 已覆盖(更细) |
| 22 | 分集洗版:`episode_priority` 按集记录质量,只下能提升的集(§6.2) | 剧集 Slot 只有一个 `current_asset_id`;升级比较用"最近导入的那个资产";导入后 `current_asset_id = mainAssets[0]`(`services/organize.ts` verifyAndImport) | ✗ | **需补 P1 L**:按 (Slot, content_unit) 记录当前资产与质量档(新表或 coverage 加 slot_id+tier);升级按单元比较,候选只下"能提升的单元" |
| 23 | 全集洗版 / 分集洗版整包优先(整包须严格高于每集)(§6.2) | 无 | ✗ | 需补 P2 M:Slot `packPolicy: per_unit / full_pack / prefer_pack`,门槛照抄 MP |
| 24 | 洗版后旧文件由整理层覆盖/删除 | 新版核验入库后旧资产保留,生成"旧版本待处置" decision(`organize.ts`) | ↔ | REQ §6.2 增强已覆盖 |
### 1.5 状态、完成、回写
| # | MP 语义 | 现状 | 状态 | 处置 |
| --- | --- | --- | --- | --- |
| 25 | N/R/P/S;S 不参与任何动作(§7) | `active/paused/completed/cancelled`;非 active 订阅的 Slot 被跳过;无订阅 Slot 仍追踪 | ✅ | 照现状 |
| 26 | P 待定:可搜索不可完成(§7) | 无对应状态;domain `unitCompletion` 有 `keepTracking/totalKnown` 但服务端未用 | ◐ | 用 #27 的 `totalKnown` 替代 P,不新增状态 |
| 27 | 完成判定按目标范围与 total(§8) | `slotComplete` 固定 `{ totalKnown: true }`,required = 已存在的 main 集单元(懒建,只含见过的集)(`organize.ts` slotComplete) | ◐ | **需补 P0 S**:required 取季单元 `episodeTotal`(或预建单元);total 未知 → `complete=false, keepTracking`;once 订阅只在 `totalKnown && complete` 时完成 |
| 28 | 总集数只升;回退不低于已存在最高集;手动锁(§8) | 无 | ✗ | 需补 P0 S(并入 #2):domain 纯函数 `resolveEpisodeTotal(old, fresh, injected, coveredMax, locked)` |
| 29 | 下载后按集回写 note/episode_priority;整包需开种子核对(§9) | 入库后按实际文件写 `asset_unit_coverage`(domain `unitCompletion` 只认文件清单) | ↔ | REQ §6.2 已覆盖(更严格) |
| 30 | 从整季包按需选集下载(仅 qB/TR)(§9 第 4 步) | qB 适配器有 `setFilePriority`(`adapters/src/qbittorrent/client.ts`)但提交链路未用;季包整包下载 | ✗ | 需补 P1 M:提交后取文件列表,按 `computeUnitGaps` 只勾缺失单元文件;计划记 `selectedUnits` |
| 31 | 同一订阅一轮可下多个资源(batch_download 逐步扣减 lefts)(§9) | 每 Slot 同时一个计划;一个发布只给一个 Slot(`planned` 变量) | ✗ | **需补 P0 M**:允许同 Slot 多个"单元不相交"的在途计划(预留粒度从 Slot 改为 Slot×单元);部分唯一索引相应调整 |
| 32 | 完成 = 写历史 + 删订阅 + outbox 事件(§7) | `status=completed` 保留行,`status_reason` 记原因 | ↔ | 已覆盖(保留行即历史) |
| 33 | 重新订阅 / 重置(清事实,state=R)(§7、§10) | `subscriptions.update` 可把 status 改回 active;无"重置" | ◐ | 需补 P2 S:`POST /subscriptions/{id}/reset` 把 Slot 置回 empty/保留资产并重评 |
| 34 | 优先级:无显式排队(MP 串行 + 随机休眠) | plan.priority 按 即时>追更>新作品>升级 写入,但提交/巡检不按它排序(`services/downloads.ts` sweepReservedPlans) | ◐ | 需补 P2 S:巡检与 deferred 恢复按 `sortByPriority` 出队 |
| — | 分享/Fork/Follow/热门/Seerr、完成否决插件事件(§10) | 无 | — | 不迁移(REQ 未要求;否决由 decision/approval 承担) |
## 2. REQ 对 MP 的增强点:实现情况
| 增强点(REQ) | 期望 | 现状(文件) | 判定 | 缺口 |
| --- | --- | --- | --- | --- |
| Slot 多目标(§6.1) | 每作品多 Slot,独立状态/条件/升级;双版本须不同物理资产 | 多 Slot + 独立状态机 ✅(`domain/slot/state.ts`、`acquisition_slot`);`require_distinct_asset` 只存字段,`checkDistinctAssets` 未接入评估/入库 | ◐ | 评估时排除已被同组其他 Slot 占用的资产;一个发布可同时满足多个 Slot 时按组规则决定是否复用 |
| 升级不覆盖旧版(§6.2) | 新版核验后切换,旧版按保留策略另行处理 | 升级计划 purpose=upgrade;核验通过才切 `current_asset_id`;旧资产只生成 decision(`organize.ts`) | ✅ | 剧集按单元切换依赖 #22 |
| 追更总集数未知不结束(§5.4) | 总集数未知保持追更 | domain `unitCompletion({totalKnown})` 已支持;服务端硬编码 `true`,且无总集数来源 | ✗ | #2、#27、#28(P0) |
| 仅本季/接续后续季(§6.5) | 用户添加默认接续;Agent 推荐默认追完当前季;特别篇/重启不算下一季 | `season_scope` 字段 + 用户创建默认 `following_seasons` ✅;`current_season` 仅在有 `season_unit_id` 时生效;agent 来源未强制默认 `current_season`;特别篇/同名重启无判定 | ◐ | origin=agent 默认 current_season;新季出现时对 following 订阅建季级 Slot 或记录;特别篇(S00/同名重启)进 decision |
| Agent 主观选片接口(§6.5) | 明确条件代码执行;Agent 挑选记证据理由、独立预算、不得放宽硬限制 | `release.search` + `download.plan(slotId, releaseId)` 走同一确定性闸门 ✅(`acquisition-organize/capabilities.ts`);无"Agent 选择理由/证据"字段,无 Agent 独立预算 | ◐ | plan 增加 `selectedBy: agent` + `rationale/evidence`;预算加 `agentBytesPerDay/agentNewTitlesPerDay` |
| 偏好标签与抑制(§6.7) | 标签结构化、可编辑、取消留抑制,Agent 不得据旧证据加回 | CRUD、修订历史、抑制记录、`agent_inferred` 遇抑制拒绝 ✅(`routes/rules.ts`);**标签未参与任何评估**(hard 排除/soft 排序/version 偏好均未映射到 SlotSpec 或新作品收录) | ◐ | 评估前把 active 标签编译进 SlotSpec(soft→`softRank` 加一维,hard→新作品/Slot 硬排除),拒绝理由写标签 id |
| 预算与优先级(§6.4、§6.8) | 每日新增作品/字节/站点比例风险/存储保留/并发;即时>追更>新作品>升级 | 新增作品、字节、并发 ✅(`domain/acquisition/budget.ts`,gates 中执行,预留计入);优先级写入 plan ✅;站点比例风险、存储保留、Agent 独立预算 ✗;队列不按优先级 | ◐ | #34;站点比例 P2(需站点用户数据) |
| 临时集单元(§5.4) | TMDB 未更新可建 provisional 单元继续下载入库 | 解析发布即建 `content_unit(status=provisional)` ✅(`entities.ts`);但完成判定把"已见单元"当全集 | ◐ | 与 #27 联动:provisional 单元参与覆盖,但不代表 total |
| 三模式判定(§6.3) | 新作品看首播日期,追更看账本,升级独立于日期 | `determineMode` ✅(`domain/acquisition/mode.ts`),新作品需已发布策略 + 授权 | ✅ | — |
| 回退条件(§6.1) | 指定等待期后接受次选 | `fallback` 只映射 `unknownPolicy`(`packages/mapping/src/slot.ts`) | ✗ | P2 M:`fallback.after{days, relax}`,依赖 #19 的定时重评 |
## 3. 订阅增强实施清单(按优先级)
### P0(错误结果/主链路,建议 W5 首批)
1. ✅(W5-C)**总集数来源与账本**:`refreshMetadata` 把 TMDB `seasons[].episodeCount` 写入季单元并预建 provisional 集单元,插件/人工值只升、可锁 — `apps/server/src/modules/sources-identity/service/{metadata,entities}.ts`、`packages/adapters/src/tmdb/client.ts`(已有字段)。
2. ✅(W5-C)**总集数纯函数**:`resolveEpisodeTotal(old, fresh, injected, coveredMax, locked)`(只升;回退不低于已覆盖最高集)— `packages/domain/src/slot/`(新 `episode-total.ts`)+ 单测(账本 §13 样本 1、9)。
3. ✅(W5-C)**完成判定修正**:`slotComplete` 用季单元 total 与 `totalKnown`,未知或未齐 → 继续追踪;`once` 订阅仅 `totalKnown && complete` 完成 — `apps/server/src/modules/acquisition-organize/services/organize.ts`、`packages/domain/src/slot/completion.ts`。
4. ✅(W5-C)**缺集纯函数**:`computeUnitGaps(required, covered, inflight, range)`,评估与完成共用 — `packages/domain/src/slot/`(新 `gaps.ts`)、`services/acquisition.ts` satisfiedUnits。
5. ✅(W5-C)**Slot×单元预留**:同 Slot 允许多个单元不相交的在途计划,`BUSY` 从整 Slot 改为按单元冲突;调整部分唯一索引 — `services/acquisition.ts` reserve、`packages/db/migrations`(新迁移)、`domain/slot/state.ts`(剧集态语义)。
6. ✅(W5-C)**忙后重评与定时重评**:Slot 离开忙态入队 `evaluateAcquisition{acquisitionSlotId}`;巡检消费 `next_evaluate_at` — `services/organize.ts`、`services/downloads.ts`、`acquisition-organize/index.ts` 循环。
7. ✅(W5-C)**订阅主动搜索任务**:`searchSubscription`(新订阅立即 + 缺口 Slot 每 24h + 手动),按实体标题/别名调 `snapshotSite({query})` 后走原识别评估链 — `apps/server/src/modules/sources-identity/{tasks,scheduler}.ts`、`acquisition-organize/routes/rules.ts`(手动端点)、`packages/contracts/src/api.ts`。
8. ◐(W5-C)**追踪剧集元数据刷新周期**:active 剧集订阅每 6h `refreshMetadata`,完成判定前强制一次 — `sources-identity/scheduler.ts`。
### P1(常用过滤与季包)
9. ✅(W5-C)**关键词包含/排除**:`SlotHardConditions.include/exclude`(正则白名单校验)— `packages/domain/src/slot/{types,evaluate}.ts`、`packages/contracts/src/policy.ts`、`packages/mapping/src/slot.ts`。
10. ✅(W5-C)**订阅站点范围**:`params.siteIds`,评估跳过范围外站点并记理由 — `services/acquisition.ts`、`packages/contracts/src/policy.ts`。
11. ✅(W5-C)**每集体积口径**:`perUnitSize` 按覆盖单元数折算 — `domain/slot/evaluate.ts`。
12. ✅(W5-C)**集范围**:`params.episodeRange{start,end?,locked}` 裁剪评估与完成 — `services/acquisition.ts`、`services/organize.ts`、contracts。
13. ✅(W5-C)**季包按需选集**:提交后读文件列表,`setFilePriority` 只下缺失单元文件,计划记 `selectedUnits` — `services/downloads.ts`、`packages/adapters/src/qbittorrent/client.ts`。
14. ✅(W5-C)**按单元质量账本(分集升级)**:(Slot, unit) → 当前资产 + 质量档;升级按单元比较,候选只取能提升的单元 — 新迁移、`services/{acquisition,organize}.ts`、`domain/slot/evaluate.ts`。
15. ◐(W5-C 仅状态)**titleQuery 订阅落地**:创建时解析身份,需确认进待处理中心,确认后回填实体并重评 — `routes/rules.ts`、`sources-identity/service/resolve.ts`。
16. ✗ **季范围语义**:`current_season` 无 `season_unit_id` 时固定到创建时最新季;origin=agent 默认 current_season — `routes/rules.ts`、`services/acquisition.ts`。
17. ✗ **偏好标签接入评估**:active 标签编译进 SlotSpec/新作品过滤,理由记标签 id — `services/acquisition.ts`、`packages/mapping/src/slot.ts`。
### P2(增强)
18. ✗ 订阅查重(同实体同季 active 幂等)— `routes/rules.ts`。
19. ◐(W5-C:`upgrade.mode=full_pack` 只收整季包;prefer_pack 未做)全集/整包优先升级模式 `packPolicy` — `domain/slot/{types,evaluate}.ts`、`services/acquisition.ts`。
20. ✅(W5-C)做种数/促销硬条件 — `domain/slot/evaluate.ts`。
21. ✗ `require_distinct_asset` 接入评估与入库 — `services/{acquisition,organize}.ts`、`domain/slot/completion.ts`。
22. ✗ Agent 选片理由/证据字段与独立预算 — `acquisition-organize/capabilities.ts`、`domain/acquisition/budget.ts`、contracts。
23. ✗ 回退等待期 `fallback.after` — `packages/mapping/src/slot.ts`、`domain/slot/evaluate.ts`。
24. ◐(W5-C:完成保留行 + PATCH status=active 重新订阅;无清账重置)订阅重置端点 — `routes/rules.ts`。
25. ✗ 队列按 `sortByPriority` 出队 — `services/downloads.ts`。
26. 实体级识别词/集偏移(替代 MP custom_words)— `sources-identity`。
不迁移:订阅分享/Fork/Follow/热门、Seerr 入口、完成否决插件事件、搜索随机休眠(由站点限流器替代)。
## 4. W5-C 实施记录(2026-09-27)
对应 §3 的状态已在条目前标注(✅ 完成 / ◐ 部分 / ✗ 未做)。实现要点与落点:
| 能力 | 实现 | 落点 |
| --- | --- | --- |
| 总集数账本 | 季单元 `content_unit.episode_total/_source/_locked/_at`(迁移 0025);`resolveEpisodeTotal`:变大采用、注入只升、TMDB 回退取 max(新值, 已覆盖最高集)、锁定不改、未公布保持未知;`refreshMetadata` 写入(只建季单元,不预建集单元);手动 `POST /subscriptions/{id}/episode-total`(不锁只升,锁定可调低) | `domain/slot/episode-total.ts`、`sources-identity/service/metadata-totals.ts`、`acquisition-organize/services/subscriptions.ts` |
| 完成判定 | `slotComplete`:按季 `seasonProgress`(总集数已知且范围内全覆盖才完成;未知 → 继续追踪 + "疑似缺集"待处理 info);pending 订阅不完成;once 订阅完成、continuous+仅本季剧集季齐也完成;完成写 `subscription_history`(行保留,改回 active 即重新订阅) | `services/subscriptions.ts`(经 `applySlotEvent(verify_passed)` 钩入,覆盖 organize 传入的 complete) |
| 缺集/在途 | `computeUnitGaps(units, covered, inflight, range)`;在途 = 该 Slot reserved/submitted 计划的 `unit_keys`(提交即计入,失败/移除释放);季包总集数已知按缺集展开,未知用整季通配 `s{季}*` | `domain/slot/gaps.ts`、`services/acquisition.ts` |
| Slot×单元多在途 | 去掉 `acquisition_plan_slot_active_key` 唯一索引;剧集 Slot 在行锁内校验单元不相交后可再预留;Slot 状态 = 在途计划最靠后阶段(`transitionWithInflight`),最后一个结束才落静止态;电影 Slot 仍单在途 | `domain/slot/inflight.ts`、`services/downloads.ts applySlotEvent`、迁移 0025 |
| 延后与重评 | Slot 忙/单元在途冲突 → `slot_deferral` + 候选评估 `decision=deferred`;计划结束(释放/失败/核验/下载被移除)→ `next_evaluate_at=now`;编排循环 `sweepDueSlots` 入队 `evaluateAcquisition{acquisitionSlotId}`(先按集号评 deferred 再评最近发布);未满足 Slot 按 `slotReevaluateIntervalMs`(缺省 6h)定时重评 | `services/subscriptions.ts`、`index.ts planTick` |
| 主动搜索 | `searchSubscription`(sources-identity 注册):标题/原名/别名 ≤3 个检索词 × 订阅站点范围内已开启且非 failing 的站点,经 `liveSearch`(适配器 search + 共享限流 + 合并/退避 + `site_snapshot kind=live_search`);新结果走 release.observed → resolved → evaluate,旧结果由任务末尾的订阅重评覆盖;用户新建订阅立即一次,之后 `next_search_at`(`params.searchIntervalHours`,缺省 24h,0 关)由编排循环 `sweepSubscriptionSearch` 到期入队;手动 `POST /subscriptions/{id}/search` | `sources-identity/tasks.ts`、`services/subscriptions.ts`、`routes/rules.ts` |
| 元数据周期刷新 | 追踪中剧集(active/pending 且有 TMDB 身份)`last_metadata_refresh_at` 超过 `metadataRefreshIntervalMs`(缺省 6h)→ 入队 `refreshMetadata`;**完成前强制刷新未做**(完成用当前账本) | `services/subscriptions.ts sweepMetadataRefresh` |
| 季包选文件 | 提交后 `files()`(重试 3 次),视频文件按文件名解析集号:需要 = 计划单元(通配 = 未覆盖且不在其它在途计划)→ 其余视频与有集号的附属文件 `setFilePriority(0)`;拿不到清单/有视频认不出集号/包里没有需要的集 → 整包并在 `acquisition_plan.file_selection` 记理由;`unit_keys` 收敛为实际选中集;接管的已有任务不改选择 | `services/downloads.ts selectPackFiles` |
| 分集质量账本 | `slot_unit`(Slot × 内容单元 → 当前资产 + 质量摘要 + 阶梯 pri);核验通过时登记;升级计划只替换计划内单元;全部已覆盖时按 `improvableUnits` 只取能提升的单元建 `purpose=upgrade` 计划 | `services/subscriptions.ts afterSlotVerified`、`services/acquisition.ts` |
| 硬条件扩展 | `excludeSources`、`hdr{anyOf,noneOf,requireSdr}`、`codecs/excludeCodecs`、`min/maxUnitSizeBytes`(每集体积,季包按账本总集数折算)、`include/exclude` 正则(白名单校验:长度、反向引用、后行断言、嵌套量词)、`sites/excludeSites`、`minSeeders`、`freeOnly`;站点事实经 `EvaluationContext` 传入,缺失按 unknownPolicy | `domain/slot/{types,evaluate}.ts` |
| 优先级阶梯 | `soft.tiers[]`(`>` 分层,层内条件同时满足),pri = 100 − 层号,未命中 0(`tiersRequired` 时拒绝);排序先比 pri;`upgrade.basis=priority`(配置阶梯时缺省)要求 pri 严格提升,`targetPri` 顶档;`upgrade.mode` movie / per_unit / full_pack | `domain/slot/evaluate.ts priorityLadder/currentPri/improvableUnits` |
| trace | 所有评估结果带 `trace`(逐条违规中文说明 + 逐层阶梯),`describeViolation` 统一文案;候选评估记录新增 `pri/trace/unitKeys` | `domain/slot/evaluate.ts`、`mapping/slot.ts`、`contracts/policy.ts CandidateEvaluation` |
| 状态 | `subscription.status` 新增 `pending`(未建档,或剧集无已接受 TMDB 身份;可搜索/评估,不可完成;`refreshMetadata` 成功后转 active);暂停不评估不搜索 | 迁移 0025、`routes/rules.ts`、`metadata-totals.ts` |
| API/UI | `GET /subscriptions/{id}/progress`(季总集数 + 每集状态 + Slot 在途/延后)、`POST /subscriptions/{id}/search`、`POST /subscriptions/{id}/episode-total`、`GET /subscription-history`;订阅详情加"立即搜索"、主动搜索时间、追更进度(每集色块 + 设置/锁定总集数) | `contracts/api.ts`、`apps/web/app/routes/console/subscriptions.tsx` |
测试:`packages/domain/test/subscription.test.ts`(账本/缺集/多在途迁移/硬条件/阶梯/洗版,覆盖账本 §13 样本 1、3、6、9、11、12);
`acquisition-organize/__tests__/subscription.test.ts`(多在途 + 延后重评 + 移除释放、季包选文件与整包兜底、总集数未知不结束 + 疑似缺集 + once 完成写历史、pending 不完成/暂停不评估、分集洗版);
`test/sources-identity/subscription-search.test.ts`(mock NexusPHP 主动搜索、站点范围、手动/暂停、季总集数写入与 pending→active);
`test/e2e/e9-continuous-series.test.ts`(连续更新 3 集在 Slot 忙时到达全部入库,另一版本延后不重复下载,once 完成写历史);E7 保持通过。
遗留:#8 完成前强制刷新;#15 titleQuery 订阅建档回填;#16 季范围语义;#17 偏好标签接入评估;P2 #18/#21–#23/#25/#26;
`organize.ts verifyAndImport` 仍保留其本地 `slotComplete` 与"once 订阅完成"更新(W4-E4 改执行侧期间不动),实际完成与历史由 `applySlotEvent` 钩子按总集数账本判定;执行侧稳定后应删掉本地判定,改调 `services/subscriptions.ts`。
-236
View File
@@ -1,236 +0,0 @@
# 06 文件管理页规格(`/console/files`)
> 来源:`00-first-version-plan.md` §6 追加范围;对照 `docs/porting/moviepilot-filemanager.md`(下称"MP 账本",痛点编号 P1–P12)。
> 需求约束:REQ §2.1 五原则、§7.1/§7.1.1(路径绑定固定、媒体级主动整理)、§8.2(扫描与"失败≠不存在")、§8.3(Copy/Move、删除保护)。
> 本文只定规格,不含实现。实现时 contracts 先行(`packages/contracts/src/files.ts`)。
## 1. 目标与边界
**目标**:覆盖 MP 文件管理的全部操作(浏览、存储/根切换、面包屑、目录树、排序过滤、新建目录、重命名、删除、识别、刮削、整理、批量、预览/下载),并在四个方面做得更好:
1. **有业务上下文**:每一行都显示"已登记资产 / 纳管下载中 / 临时文件 / 待识别",不用挨个点识别(P5)。
2. **安全**:只能浏览登记过的根;删除进回收站并做引用检查;改名同步资产与路径绑定(P1/P2/P3)。
3. **一条向导走完整理**:识别 → 逐项确认作品/季/集/Slot → dryRun 预览前后路径 → 执行 → 跳转整理记录(P7/P8)。
4. **大目录与离线可用**:游标分页、截断提示、挂载离线显式报错而不是显示空目录(P6/P12)。
**边界(不做)**:
- 整库迁移、底层分支盘/存储池调度(REQ §7.1.1);网盘存储(115/阿里/Alist/SMB/rclone)——只有本机可达路径(含 NFS/FUSE 挂载)。
- 不提供文件上传、在线编辑、解压;下载只允许非媒体小文件(≤ 20 MB,如 NFO/字幕/图片),视频不经浏览器下载。
- **浏览器无媒体库写权限原则不变**:所有写操作由服务端执行,浏览器只提交意图;服务端做守卫、授权、审计。
- 硬链接/软链接创建、空目录上溯清理、蓝光原盘整目录操作:列为 W5(与 `moviepilot-rust-transfer.md` 的 W5 项一起)。
- 不改元数据自动刷新语义:刷新不改路径;只有本页的"重命名/移动/整理"是显式路径变更。
## 2. 后端 API
### 2.1 模块归属
- 新建子模块 `apps/server/src/modules/acquisition-organize/files/`(路由 `routes/files.ts` + 服务 `services/files*.ts`),复用 `organize-guard.ts`、`planImport`、`volumes.ts`。
- `POST /files/identify` 需要身份候选排序,归 `sources-identity` 模块注册(遵守"模块间不互相 import service",它只读 DB + 调 TMDB 适配器,不落库)。
- **路径守卫下沉**:把 `organize-guard.ts` 里的 `sourceGuard` 的纯 IO 部分抽到 `packages/adapters/src/fs/guard.ts`(输入:根列表;输出:`check(p) → realpath`),两个模块共用;根列表由各自从 DB 读取。
- 前缀统一 `/files`,scope:读 `organize.read`;写 `organize.execute`,另加 §2.4 的授权规则。
### 2.2 读接口
**`GET /files/roots`** → `FileRoot[]`
| 字段 | 说明 |
| --- | --- |
| `id` | `lib:<library_root.id>` 或 `dl:<downloader_instance.id>:<mapping idx>` |
| `kind` | `library` \| `download` |
| `name` / `hostPath` | 显示名 / 本机路径(下载根 = 路径映射的 hostPath;无映射时取 localSavePath) |
| `mediaKind` | 库根的 movie/tv/mixed;下载根为 null |
| `writable` | 库根 `management_mode='managed'` 且进程对目录有 W_OK;`register_only` 恒 false(只读登记) |
| `health` | `healthy/degraded/offline/unknown` + `checkedAt`,来自 `checkRoot`(哨兵)实时探测,≤2 s 超时 |
| `usage` | `diskUsage` 的 total/free,失败为 null |
与 `allowedSourceRoots` 同源,根 `/` 永不出现。
**`GET /files?root=&path=&cursor=&limit=&sort=&order=&q=&filter=&hidden=`** → `{ root, path, entries, nextCursor, total, truncated, scannedAt }`
- `path` 是根内相对路径(`""` = 根);服务端 `join(root.hostPath, path)` 后过守卫。响应里不回显绝对路径以外的东西。
- 目录读取用 `opendir` 流式;单次最多读 `FILES_LIST_MAX_ENTRIES`(默认 20 000)项,超出 `truncated=true`,前端提示"请用搜索或进入子目录"。
- 排序 `name`(自然排序,目录优先)/ `mtime` / `size` / `status`;`order=asc|desc`;`q` 为子串或 glob(大小写不敏感);`filter` 可多选:`video|subtitle|registered|unregistered|downloading|temp|unidentified`;`hidden=false` 默认隐藏点文件和 `.uhd-partial`/`.umt-trash`/哨兵。
- 分页:排序在服务端全量(≤上限)完成后按 `limit`(默认 200,最大 1000)切片,`cursor` 为不透明偏移 + 目录快照 mtime;目录 mtime 变了返回 `stale: true` 让前端重载。
- 每个 entry:
| 字段 | 说明 |
| --- | --- |
| `name` / `type`(`file\|dir\|symlink\|other`)/ `size` / `mtime` | lstat;符号链接不跟随,仅标记,目标在根外时 `escape: true` 并禁用所有操作 |
| `ext` / `fileKind` | video/subtitle/audio/nfo/image/other(同 domain/organize 的分类) |
| `status` | 见下表,只取一个主状态,按优先级 |
| `asset` | 已登记时:`{ assetId, locationId, mediaEntityId, title, year, contentUnit(S01E02), slotId?, slotStatus?, isPrimary }` |
| `download` | 路径落在纳管 `download_task.content_path`(经 hostPathOf 映射)内:`{ taskId, status, progress, hnr: satisfied\|unsatisfied\|unknown }` |
| `media` | 已登记资产取 `media_asset.media_info` 摘要(分辨率/HDR/编码/音轨数);未登记不探测,仅在 `/files/probe` 后缓存 |
| `dirSummary` | 目录项:懒计算,仅列表当前页的目录给 `{ videoCount, registeredCount }`,上限 500 项/目录,超出给 `partial: true` |
`status` 优先级:`temp`(`.uhd-partial`、`.!qB`、`.part`)> `downloading`(纳管任务未完成)> `registered`(有 available 的 asset_location)> `seeding`(纳管任务已完成未入库/或 Copy 后仍做种)> `unidentified`(视频且无资产)> `plain`。
- 资产关联查询:本页 entries 批量 `WHERE library_root_id=? AND relative_path = ANY(?)`,下载关联按任务 content_path 前缀,不逐行查库。
**`GET /files/stat?root=&path=`** → 单项 entry + `{ realPath, inode, device, nlink, sidecars[], bindings[] }`。`bindings` 为命中该目录的 path_binding(作品/季);`nlink>1` 标记"共享 inode,禁止原地改写"(REQ §8.3)。
**`GET /files/download?root=&path=`**:仅 `fileKind ∈ {nfo, subtitle, image}` 且 ≤ 20 MB;`image` 同时作为预览。
**挂载离线**:根哨兵检查失败或 `readdir` 报 EIO/ENOTCONN/ESTALE/ETIMEDOUT → `503 root_unavailable`,不返回空列表(原则 4,P12)。
### 2.3 写接口
所有写请求体都带 `idempotencyKey`(前端生成 UUID);同键重放返回首次结果(存 `task.idempotency_key`)。所有写操作:守卫 → 授权 → 前置检查 → 执行 → `audit_event`(action `files.<op>`,payload 含 root/相对路径/结果)→ SSE `files.changed { rootId, dirs[] }`。
| 端点 | 请求 | 行为 |
| --- | --- | --- |
| `POST /files/mkdir` | `{root, path, name}` | `sanitizeSegment(name)` 后与原值不同则 400;`mkdir` 非递归,已存在 409;只允许 writable 根 |
| `POST /files/rename` | `{root, path, newName, withSidecars=true, dryRun}` | 同目录改名,见 §2.3.1 |
| `POST /files/move` | `{items:[{root,path}], target:{root,path}, mode:"move"\|"copy", dryRun, planId?}` | 见 §2.3.2 |
| `POST /files/delete` | `{items:[{root,path}], mode:"trash"\|"permanent", dryRun}` | 见 §2.3.3 |
| `POST /files/trash/restore` / `GET /files/trash` / `POST /files/trash/purge` | — | 回收站列表/还原/清空 |
| `POST /files/identify` | `{items:[{root,path}], hints?:{mediaKind,year,tmdbId}}` | 见 §2.3.4,只读 |
| `POST /files/organize` | 见 §2.3.5 | 复用 `/organization-records` |
| `POST /files/scrape` | `{mediaEntityId, writeNfo:boolean, images:boolean}` | 见 §2.3.6 |
| `POST /files/probe` | `{items:[{root,path}]}`(≤ 20 个视频) | `ffprobe` 摘要;已登记资产写回 `media_asset.media_info`,未登记只缓存 24 h(DATA_DIR/cache/probe) |
#### 2.3.1 rename
- `newName` 过 `sanitizeSegment`;不得含 `/`;目标已存在 → 409(**不覆盖**,修 P3)。实现用 `link+unlink` 或 `renameat2(RENAME_NOREPLACE)` 语义,不能用会覆盖的裸 rename;Deno 下用"先 lstat 再 rename + 事后 inode 比对"并在 `asset_operation` 记账。
- `withSidecars`:视频改名时同 stem 的字幕/NFO/图片一起改(复用 `domain/organize/sidecars.ts` 的匹配规则),dryRun 返回全部前后名。
- **已登记资产**:同一事务里更新 `asset_location.relative_path`(含 sidecar 的 location),写 `asset_operation` 账本(commit 阶段);发 `organization_record(trigger=manual, action=rename)`(枚举已有 `rename`)让外部程序增量感知。
- **目录改名命中 path_binding**:这是显式路径变更(REQ §7.1.1),插入新绑定并把旧绑定置 `superseded`、`superseded_by_id` 指向新行,`bound_by` = actor;该目录下所有 asset_location 的 relative_path 前缀批量替换。**仅译名刷新不触发**,只有本端点和整理会改。
- 目录内有纳管下载任务(做种中):拒绝 409 —— 改名会让 qB 找不到文件;提示先完成/移交。
- 递归自动改名(MP 的 recursive)不照搬:改为 §3.4 向导里"按模板重命名"的一种模式,先预览全部,再一次执行,单项失败不影响其他项并列出失败清单(修 P9)。
#### 2.3.2 move / copy
- 目标必须是 writable 根下的已存在目录;源 ≠ 目标、目标不在源内。
- **已登记资产或含已登记资产的目录**:不走裸 FS,转成 organize plan(记录 action 用已有的 `relocate`/`copy`):`planImport({sourcePaths, action, targetLibraryRootId, ...})` 的"原样搬运"变体(保持文件名,目标目录由用户指定)——需要在 `ImportRequest` 加 `targetDir`(绝对路径,须在库根下)与 `keepNames: true`。计划照常检查冲突、下载器引用、可用空间、H&R;执行走现有 `verifyAndImport` → 逐文件可恢复、跨卷 Copy→核验→删源、EXDEV 不降级。完成后 asset_location/path_binding 更新由整理流程负责。
- **未登记文件**(如下载根里的散文件、临时整理):同样走 organize plan,但 `registerAssets=false`,只产生 organization_record,不建资产。这样搬运都是可恢复的,不存在第二套搬运代码。
- `dryRun=true` 返回 planId + 前后路径表 + 冲突/空间/H&R 结论;执行必须带 dryRun 返回的 `planId`,服务端校验计划内容哈希与当前请求一致,且源文件 size/mtime 与计划冻结时一致,否则 409 `plan_stale`(dryRun 一致性)。
- `mode=move` 授权同 `/organization-records`:admin 会话,或 API Key + grant/approval;H&R 未满足 → 409 + 待处理,不静默改 Copy。
#### 2.3.3 delete(原则 5)
- 默认 `mode=trash`:同卷移入 `<根>/.umt-trash/<yyyymmdd>/<uuid>/<原相对路径>`(rename,O(1));根不可写或跨卷时移入 `DATA_DIR/trash/` 需 Copy,超过 `FILES_TRASH_CROSS_MAX_BYTES`(默认 2 GiB)拒绝并提示用永久删除。`trash_item` 表记录原根、原相对路径、actor、时间、关联资产快照。
- 保留 `FILES_TRASH_RETENTION_DAYS`(默认 7);清理由持久任务 `purgeTrash` 每日执行,删前复核 `trash_item` 仍存在。还原:原路径被占用 → 409,可选"还原到…"。
- `mode=permanent`:设置项 `files.allowPermanentDelete`(默认 false)开启后才可用,且仅 admin 会话;前端二次确认输入文件名。
- **引用检查(两种模式都做,dryRun 可预览)**:
| 情况 | 处理 |
| --- | --- |
| 命中纳管 download_task 的 content_path(未完成或仍在做种/H&R 未满足) | 拒绝 409,列出任务 |
| 已登记资产(asset_location available) | 允许,但必须勾选"同时退役资产":asset_location→`removed`,media_asset→`retired`,若为 Slot 满足资产则 Slot 退回 `attention` 并建待处理;写 organization_record(action=delete) |
| 目录是 library_root 本身、`path_binding` 绑定目录 | 根本身拒绝;绑定目录需勾选"解除绑定"(绑定置 superseded),否则 409 |
| 共享 inode(nlink>1) | 允许,提示"只删除这一处链接" |
| 根离线/stat 失败 | 拒绝(原则 4),不当作已删除 |
- 删除永不连带 qB 任务;qB 侧清理仍只走 retention 流程。
#### 2.3.4 identify(只读)
- 对每个选中项:`parseReleasePath`(文件路径含父目录上下文)→ 本地实体优先召回(title_alias pg_trgm + external_identity)→ 不足时查 TMDB(走缓存)→ `rankCandidates`。目录项先展开为其中视频(上限 200)。
- 返回每项:`{ parsed(标题/年份/季集/分辨率/来源/发布组), candidates:[{mediaEntityId?, provider, externalId, title, year, kind, level, reasons[]}] (≤5), suggestion:{mediaEntityId?, contentUnit?, slotId?}, existing:{assetId?} }`。
- **不写库**:不建 media_entity、不写 identity_revision、不进待识别池;用户在向导里确认后才由 organize 落库。`resolveRelease` 需要拆出纯排序函数(`rankForPath`)供这里调用。
#### 2.3.5 organize
- 请求:`{ items:[{root, path, mediaEntityId, contentUnitId?, season?, episodes?, slotId?}], action:"copy"|"move", targetLibraryRootId?, dryRun, planId? }`。
- 服务端按 `mediaEntityId` 分组,每组调用一次 `POST /organization-records` 同一实现(`planImport`),因此自动与手动整理产出同格式记录(REQ §7.1.1)。
- **缺口**:现有 `OrganizeRequest` 只接受整批一个 `mediaEntityId/contentUnitId`,没有逐文件季集覆写。需扩 contracts:`fileOverrides: [{path, contentUnitId?, season?, episodes?}]`,planImport 在 facts 生成时优先用覆写值;`slotId` 用于指定满足哪个 Slot(缺省由 Slot 评估选)。
- dryRun 返回每组 `planId`;执行请求携带全部 planId,逐组执行,任一组 plan_stale 只让该组失败。执行返回 `recordIds`,前端跳 `/console/organize?ids=`。
#### 2.3.6 scrape
- 只对**已登记作品**:等价于 `POST /media-entities/{id}/refresh`(字段级、用户锁优先、不动路径),可选 `writeNfo`/`images` 把最终字段投影成 NFO/海报写到绑定目录。
- 写 NFO 规则(REQ §7.3):临时文件 + 原子提交;目标 NFO 若非本系统生成(无 `<!-- umt -->` 标记)且字段已被用户锁定来源为"NFO 导入",不覆盖,返回 `skipped`;不写媒体容器字节。NFO 投影本体目前未实现,列入后端工作量。
- 未登记文件点"刮削" → 前端引导先走识别/整理,不提供"直接按路径刮削写 NFO"(避免对未登记内容写文件,修 P10)。
### 2.4 授权矩阵
| 操作 | admin 会话 | API Key |
| --- | --- | --- |
| roots/list/stat/download/identify/probe(只读缓存) | ✓ | `organize.read` |
| mkdir / rename / copy / organize(copy) | ✓ | `organize.execute` |
| move / organize(move) | ✓ | `organize.execute` + automation_grant 覆盖目标作品,或一次性 approval(绑定 planId 哈希) |
| delete(trash) | ✓ | `organize.execute` + approval(每次) |
| delete(permanent)、trash purge | 仅 admin 会话 | ✗ |
Agent 侧暂不暴露 files.* 能力;若加,只开放 `files.list/identify`(只读)。
## 3. 前端规格 `/console/files`
### 3.1 布局
- 桌面(≥ lg):左栏 `pg-chrome` 贴边 260 px(可拖宽,存 localStorage):根列表(按 库/下载 分组,每根带健康 StatusBadge 和剩余空间)+ 当前根的懒加载目录树;右侧 `pg-slab`:工具栏(面包屑、搜索、筛选、排序、视图、显示隐藏文件开关、新建目录、刷新)+ 列表 + 底部状态栏(项数、选中数/总大小、截断提示)。
- 选中单项时右侧抽屉 `DetailSheet`:文件信息、资产/作品/Slot 链接、下载任务链接、sidecars、ffprobe 摘要、路径绑定;图片/NFO/字幕文本预览。
- 移动端(< md):根和树收进 `Sheet`,列表单列卡片(名称 + 状态徽标 + 大小/时间一行),长按进入多选,操作放底部操作栏;面包屑折叠为"…/父/当前"。
- URL 状态:`?root=&path=&q=&sort=&sel=` 可分享、可回退;刷新保持位置。
### 3.2 列表
- 列:选择框 | 图标 + 名称(已登记显示作品标题副行)| 状态徽标 | 季集 | 大小(目录显示视频数/已登记数)| 修改时间 | 行操作(⋯)。
- 状态徽标(`StatusBadge` + 文字,不只靠颜色):已登记(success)/ 下载中 n%(neutral)/ 做种中·H&R 未满足(warning)/ 临时文件(neutral)/ 待识别(warning)/ 链接逃逸(destructive)。
- 虚拟滚动(新增依赖 `@tanstack/react-virtual`,需登记许可 MIT);服务端分页 200 条,滚到底自动取下一页。
- 快捷筛选 chips:全部 / 视频 / 待识别 / 已登记 / 下载中 / 临时文件;搜索框支持子串与 glob,300 ms 防抖,服务端过滤。
- 空态:空目录(`EmptyState plain`,提供"新建目录")、过滤无结果(提供"清除筛选")、根离线(destructive 提示 + 上次健康时间 + "重试")、截断("只显示前 20 000 项,请搜索")、无权限。
### 3.3 操作
- 入口三处一致:行 ⋯ 菜单、右键菜单(桌面)、多选后的浮动操作栏。菜单按条件禁用并在 tooltip 说明原因(例如"做种中,不能改名")。
- 单项:打开 / 重命名(F2)/ 移动到… / 复制到… / 整理到… / 识别 / 刮削(已登记)/ 探测 / 删除(Del)/ 复制路径(本机路径 + 下载器路径 + 媒体服务器路径三种,取路径映射)/ 在整理记录中查看。
- 多选:整理向导 / 识别 / 移动到 / 复制到 / 删除 / 探测。
- 重命名:行内编辑;右侧"建议名"按钮(按模板渲染,来自 identify 结果);显示将一起改名的 sidecars;已登记/绑定目录时提示"将同步资产与路径绑定"。
- 移动/复制到:目标选择器复用目录树,只列 writable 根;先显示 dryRun 预览(前后路径、冲突、空间、H&R),再确认。
- 删除:对话框列出引用检查结果,默认"移入回收站(7 天后清除)";需要额外勾选的项逐条列出。
- 拖拽:把列表项拖到树节点或面包屑段 = "移动到…",松手后一律打开 dryRun 预览对话框,不直接执行;按住 Alt 为复制。
- 键盘:↑↓ 移动焦点,Enter 进入/打开,Backspace 上一级,Space 勾选,Shift+点击/Shift+↑↓ 区间选,Ctrl/Cmd+A 全选,F2 改名,Del 删除,`/` 聚焦搜索,Esc 清除选择;列表为 `role="grid"`,焦点可见。
- 长任务(整理/移动)提交后 toast 带任务链接,列表通过 SSE `files.changed` 与 `organization.*` 事件自动刷新受影响目录。
### 3.4 批量整理向导(Sheet 全高,4 步)
1. **识别**:对选中项调 `/files/identify`,表格:源文件 | 解析(标题/年/季集/质量)| 候选作品(下拉,显示等级与理由,可搜索改选)| 季/集(可编辑,支持批量"集号 +N 偏移")| Slot(下拉,默认自动)。同一目录的项默认共享作品选择;"应用到同组"按钮。未决项(无候选/低置信)高亮,必须处理或排除才能下一步。
2. **选项**:动作 Copy/Move(Move 在有 H&R 未满足项时禁用并说明)、目标库根(按作品类型过滤)、是否按模板重命名(默认是)、已存在目标处理(跳过/冲突待处理)。
3. **预览**:调用 dryRun,按作品分组显示前后路径对照(差异高亮)、新建目录、将创建/沿用的路径绑定(已绑定显示"沿用 旧目录名",体现原则 2)、冲突、空间、截断警告。任何组失败可返回第 1 步修改。
4. **执行**:提交 planId,显示每组状态;完成后按钮"查看整理记录"跳 `/console/organize?ids=…`。
### 3.5 与 MP 的对比(更好用的具体点)
| MP | 我们 |
| --- | --- |
| 从 `/` 浏览整个文件系统(P1) | 只列登记的库根与下载根,带健康与空间 |
| 列表无业务状态(P5) | 每行显示已登记/下载中/做种/待识别,并链接作品、Slot、任务 |
| 删除直接 rmtree(P2) | 回收站 + 引用检查 + 资产退役联动 |
| 改名不同步历史、可能覆盖(P3) | 不覆盖;同步 asset_location、path_binding 修订、sidecars;发整理记录 |
| 无通用移动/复制(P4) | 移动/复制到 + 拖拽,统一走可恢复的 organize plan |
| 识别单结果只读,与整理分离(P7) | 候选 Top5 可改选,结果直接进入向导 |
| 批量整理只能一组参数(P8) | 逐项作品/季/集/Slot,按作品分组预览 |
| 递归改名失败即中止(P9) | 全量预览,逐项结果,失败不影响其他 |
| 刮削恒覆盖 NFO(P10) | 字段锁 + 仅对已登记作品 + 非本系统 NFO 不覆盖 |
| 无键盘/拖拽/右键/隐藏文件开关(P11) | 全部提供 |
| 离线显示空目录、大目录全量返回(P6/P12) | 503 显式离线;游标分页 + 截断提示 |
### 3.6 设计纪律
遵守 `docs/design/README.md`:只用语义 token;材质:左栏 `pg-chrome`,列表 `pg-slab`,菜单/对话框 `pg-float`,卡片内空态用 `EmptyState plain`;复用 `PageHeader`、`FilterBar`、`DataTable`(虚拟化版本新增 `virtual` 选项而非另造表格)、`DetailSheet`、`ConfirmDialog`、`StatusBadge`、`EmptyState`;新增共享件 `DirTree`、`PathBreadcrumb`、`ContextMenu`(基于 ui/popover)放 `components/console/`。导航:`nav.ts` 加"文件"工作区。`deno task check:tokens` 必须绿。
## 4. 不变量、测试与实施拆分
### 4.1 数据与安全不变量
1. 任何请求解析出的 realpath 必须在某个登记根之下;`..`、绝对 name、符号链接逃逸、根本身写操作一律拒绝;遍历中每个文件复核(TOCTOU:执行阶段再次 realpath)。
2. 浏览器永远不直接写文件;所有写操作有 audit_event 与幂等键。
3. 路径变更只来自显式操作(rename/move/organize),且同事务更新 asset_location 与 path_binding 修订;元数据刷新不改路径。
4. 删除不影响 qB 任务;纳管做种文件不可改名/删除/Move(除非 H&R 满足且有授权)。
5. 失败≠不存在:离线/超时不改变任何资产状态、不显示为空。
6. 执行只接受 dryRun 产出的 planId,且源文件事实未变。
7. 不覆盖已存在文件;replace 只在整理流程的 `replace_with_verify` 策略下发生。
### 4.2 测试要求
- **路径逃逸**:`..` 段、URL 编码、符号链接指向根外(文件与目录)、`name` 含 `/`、根为 `/` 的配置被过滤;执行期把文件换成符号链接(TOCTOU)。
- **删除保护**:纳管做种文件 409;已登记资产未勾选退役 409、勾选后 location=removed/Slot→attention;根本身拒绝;回收站还原到原位、原位被占 409;purge 只删过期项;离线根拒绝。
- **rename 同步**:文件改名 → asset_location 与 sidecar 同步;目录改名命中绑定 → 新绑定 active、旧绑定 superseded 且链接;之后新集入库进入新目录;同名存在 409 且原文件未动;做种目录 409。
- **dryRun 一致性**:dryRun 与执行对同一输入产出相同文件对;dryRun 后改动源(size/mtime)执行返回 plan_stale;planId 与请求体不匹配 403/409;幂等键重放不重复执行。
- **列表**:分页稳定性、超大目录截断(生成 25 000 个空文件)、离线 503、状态徽标优先级、批量资产关联只发常数次查询。
- **前端**:键盘导航、多选区间、向导步骤门禁(未决项不能下一步)、移动端布局快照;e2e:下载根散文件 → 识别 → 向导 Copy 入库 → 整理记录出现 → 列表状态变"已登记"。
### 4.3 实施拆分(两路并行,contracts 先合并)
| 路 | 范围 | 估计 |
| --- | --- | --- |
| 0. contracts(后端路先做,半天) | `files.ts` schema、`OrganizeRequest` 扩 `fileOverrides/targetDir/keepNames/registerAssets`、organization action 加 `delete`(已有 rename/relocate)、`trash_item` 迁移 | 0.5 d |
| A. 后端 | guard 下沉到 adapters;roots/list/stat/download(含资产/下载关联与分页);mkdir/rename(同步资产+绑定);move/copy/organize 适配 planImport;回收站 + purgeTrash 任务 + 引用检查;identify(sources-identity 拆 rankForPath);probe;scrape(refresh 包装 + NFO 投影);授权矩阵、审计、幂等;§4.2 后端测试 | 5–6 d |
| B. 前端 | 路由与布局、根/树/面包屑、虚拟列表与筛选、详情抽屉、行/右键/批量操作、重命名/移动/删除对话框(含 dryRun 预览)、4 步整理向导、拖拽与键盘、移动端、空态/错误态;前端测试 + e2e | 5 d |
B 可在 contracts 合并后用 MSW/mock 先行;联调 1 d。NFO 投影若排期紧可拆到 W5,scrape 先只做字段刷新。
-323
View File
@@ -1,323 +0,0 @@
# v0.1 第二轮独立验收审查(发布前终审)
审查日期:2026-09-28。基线 HEAD `36aa99b`(工作区只有 `deno.lock` 未提交改动)。只读审查,本文件是唯一写入。
对照:`04-v0.1-acceptance.md`(第一轮)、`00` §3.1 与 §6、`02`、`03`、`05`、`06`、`docs/design/agent-ui-reference.md`、`docs/porting/{mp-rust-logic-parity,moviepilot-rust-transfer}.md`、README、runbook、CHANGELOG。
**约束说明**:W6-A 正在独占修 e2e 稳定性,本轮**没有跑全量 `deno task ci`,也没有跑依赖 PG 的测试**。依赖 PG 的测试(server e2e、file-manager、acquisition-organize `__tests__`)只读代码核对断言,没有实际运行。
本轮实测(均通过):
| 项 | 结果 |
|---|---|
| 单包 vitest(不依赖 PG) | domain 510、contracts 589、adapters 264(另有 3 个 skip)、mapping 22、agent 31、web 27;server 里不依赖 PG 的 4 个文件(api-registry / logger / invoke-limits / health)共 56 个 |
| 类型检查 | `apps/server`、`apps/web` 的 `deno task typecheck` 退出码 0 |
| biome | 763 个文件,0 个 error;1 个 warning(`subscription.test.ts:132` 的 `dt2` 未使用),1 个 info(`subscriptions-policy.tsx:420` 有多余的 fragment) |
| 实机 `PORT=4396 --role api`(测完已关) | `/health` 返回 `version:"0.1.0"`;`/files/roots`、`/files`、`/files/trash`、`/engine/runs` 无凭证时 401;`path=../etc` 返回 422;用 DEV_ADMIN_KEY 的 Bearer 调 `POST /approvals` 返回 403 |
| 提交规范 | 82 个提交全部符合 `type(scope): 描述`;还没有任何 tag |
## 0. 结论
**前提是 W6-A 把 e2e 治稳、`deno task ci` 连续两次全绿。在这个前提下,修完 1 个阻塞项(只改文档)就可以打 `v0.1.0`。**
- 第一轮的 4 个阻塞项全部复核落地。11 个应修项里 10 个已验证,S9(控制台 TOTP 入口)仍缺。
- §6 追加范围:
- 文件管理:后端 18 个端点齐全,规格 §4.1 的 7 条不变量都有代码和测试对应。
- 订阅增强:05 清单 P0 的 1–7 条全部完成,第 8 条部分完成。
- Agent 页:agentui 映射表 11 行全部落地。
- 转移语义:5 项全部落地,挂死隔离在生产二进制里生效有容器演练记录。
- 差分补迁:missing 和 our_bug 已清零。
- 阻塞的是**发布说明**:CHANGELOG `[0.1.0]` 完全没写 W4-E4、W4-F、W5-A~E,"已知限制"里还有一条已经过时的表述。
- 安全面没有发现需要阻塞发布的漏洞。有 1 个多源 Move 的授权范围缺口(应修 S-1),危害比第一轮 R4 小:只能在已登记的根之间搬运,而且资产位置会跟着更新。
## 1. 问题清单
### 1.1 阻塞发布(1)
| # | 问题 | 位置 | 修法 |
|---|---|---|---|
| B1 | CHANGELOG `[0.1.0]` 只写到 W4-E3。缺少 W4-E4(转移语义)、W4-F(Agent 会话页)、W5-A/A2/B(文件管理)、W5-C(订阅增强)、W5-D(识别差分补迁)、W5-E(生产 fsworker、`autoInvestigateMode` 设置)。"已知限制"第 3 条("没有来源会把集单元确认……Slot 不会进入 satisfied")在 W5-C 引入总集数账本后已经不成立 | `CHANGELOG.md` | 按波次补 W4-E4 到 W5-E 的条目;把"已知限制"换成本文 §8 的清单(或直接链接本文),删掉过时的第 3 条 |
### 1.2 应修(v0.1.x,10 项)
| # | 问题 | 位置 | 修法 |
|---|---|---|---|
| S-1 | `/files/move` 用 API Key 执行时,只取 `sources[0]` 对应的作品去核对持续授权。只要持有覆盖作品 A 的 grant,就能在同一请求里把作品 B 的已登记文件一起 Move 走 | `file-manager/transfer.ts:236-244` | 对每个源(目录按其中已登记资产)求出作品集合,要求每个作品都被 grant 覆盖;有无法归属作品的源时只接受一次性审批。补一条"两个作品只授权一个 → 403"的测试 |
| S-2 | 注册表的鉴权标注又和实际行为不一致了(第一轮 S10 的回归)。`GET /files/download` 和 `POST /files/trash/purge` 在处理器里只放行管理员会话,但注册表只标了 `scope:"admin"`,没有标 `auth:"session"`,所以 api-registry 的双向测试没有覆盖到它们。另外 `isAdmin` 放行 `dev_admin`,而 `auth:"session"` 的端点会拒绝 DEV key,两处口径不同 | `packages/contracts/src/api.ts:1150,1182`;`file-manager/common.ts isAdmin` | 两个端点标 `auth:"session"`,在 `api-registry.test` 的会话端点列表里加上;确定 DEV key 的口径,两处统一 |
| S-3 | 第一轮 S9 仍未做:`useTotpStatus` / `useTotpAction` 写好了,但界面里没有任何地方调用,账户弹层只有改密和登出 | `apps/web/app/components/console/account-menu.tsx` | 账户弹层加"二次验证"开关,接 `GET/POST /auth/totp`;04 表里 S9 的状态同步更新 |
| S-4 | 脱敏键表还是三份(见 §5.4)。rss 和 browser 两处都漏了 `c_secure_pass`、`pass_key`/`auth_key` 这类带分隔符的写法、`refresh_token`、`password`、`uid_key`;browser 另外漏了 `auth`。adapters README 写的是"键表与规则取 contracts 的 redact.ts",与实际不符 | `packages/adapters/src/rss/redact.ts:9`、`browser/redact.ts:13` | rss/browser 的参数判定改为调用 `@umt/contracts` 的 `isSensitiveKey` 和 `SENSITIVE_QUERY_PARAMS`,各自只保留特有规则(路径式 passkey、cookie 名、a11y 密码框);加一个三处一致性测试 |
| S-5 | 回收站"还原到…"不检查目标根是否可写,可以还原进 `register_only` 库根。父目录不存在时,会先按 `resolve()` 递归 mkdir,再做 realpath 复核;如果中间一段目录是指向根外的符号链接,根外可能已经被建出目录 | `file-manager/trash.ts:478-506` | target 走 `isWritableTarget`;原位还原时先逐级 realpath 已存在的祖先目录,再 mkdir |
| S-6 | `/files/identify` 走 `capabilities.invoke("catalog.lookup")`,绕开了 `invoke-limits` 的 TMDB 并发限制。每项超时 15 秒;目录最多展开 200 个视频,在 HTTP 请求里串行 | `file-manager/identify.ts:150-160` | 复用 `createInvokeLimiter` 的 TMDB 信号量;单次请求内去重后的远端查询设上限(如 20),超出的项给 warning |
| S-7 | 文件管理的重活仍在 API 进程的 HTTP 请求内做:跨卷进回收站最多复制 2 GiB;还原时跨卷 `copyTree` **没有大小上限**;永久删除时递归 `rm`;所有 fs 调用都不经 fsproxy,挂死的挂载会占满 API 进程的 libuv 线程池,而 `probeRoot` 的 2 秒超时只挡住等待,挡不住卡住的系统调用 | `file-manager/trash.ts:390-399,513-516` | 还原沿用 `trashCrossMaxBytes`,或者跨卷的回收站/还原改为入队任务、返回 taskId;根探测与 readdir 走 FsProxy 的快操作。暂时做不到就写进已知限制(已列入 §8) |
| S-8 | 完成判定有两条路径。`organize.ts` 仍保留本地的 `slotComplete`,并直接把 once 订阅置为 `completed/slot_satisfied`;`services/subscriptions.ts` 按总集数账本判定并写 `subscription_history`。两者结论不同时,once 订阅可能不写历史就完成 | `acquisition-organize/services/organize.ts:1458-1492,1548` | 删掉本地判定,统一调用 `services/subscriptions.ts`(05 §4 遗留里已经点名) |
| S-9 | 文档与代码不同步 | 见右列 | `moviepilot-rust-transfer.md` §7 遗留第 1 条("编译二进制回退 in-process")已被 3641ab9 解决,改为已做;contracts README 缺 `files.ts`、`redact.ts`、`browser.ts`、`auth.ts`;adapters README 的 fs 行缺 `FsProxy` 和 `./fsworker` 导出;`apps/server/README.md` 的模块清单缺 `file-manager/`,角色缺 `worker:agent` 和 `fsworker`;04 表 S9 行没有标状态 |
| S-10 | 验收开关 `UMT_FS_TEST_HANG_AFTER_BYTES` 在生产二进制里同样生效。误设以后,所有数据类文件操作都会卡死,直到被 SIGKILL | `packages/adapters/src/fs/fsproxy.ts` | 只在 `NODE_ENV!=="production"`,或者同时设置 `UMT_FS_TEST_HOOKS=1` 时生效;preflight 发现已设置就 warn |
### 1.3 建议
- `POST /files/probe` 标的是 `organize.read`,但会写 `media_asset.media_info`,而且在请求里串行跑最多 20 次 ffprobe,每次 30 秒超时。建议改成 `organize.execute` 或入队。
- 永久删除只在单项时要求 `confirmName`,多项时没有服务端确认(`trash.ts:217`)。
- 测试可以补几处:trash 用例在审批消费后补一条"重放 → 403";download 用例补"admin scope 的 API Key → 403";E6 自动恢复现在只核实到 recheck 后回到 `downloading`,可以再延伸到入库;engine-retry 用的是 mock 的 enqueue,可以加一条真实引擎的用例。
- e2e 文件编号重复(`e6-auto-recover` 和 `e6-w4a-endpoints`),建议改成 E10。迁移 0017 空缺维持现状即可(已知、无害)。
- 04 §1.3 的 7 条建议都还没动(下载状态机 `ALLOWED=ALL`、`scanIncremental` 出错仍返回 complete、流程版本升级没有测试、AT18 e2e、AT42 未纳管、vertical 用例依赖执行顺序、web 单测少),已并入 §8。
- 清掉 biome 的 1 个 warning 和 1 个 info。
## 2. 第一轮问题复核
| # | 第一轮问题 | 结论 | 证据 |
|---|---|---|---|
| R1 | 后台循环绕过引擎;HTTP 内同步重活 | 已验证 | fe9693d:`index.ts planTick` 只调 `enqueueTracked`/`enqueueSweep`,W5-C 新增的 `sweepDueSlots`/`sweepSubscriptionSearch`/`sweepMetadataRefresh` 也只 enqueue;`routes/downloads.ts:100-125` 返回 202 `{taskId,runId}`;`__tests__/background-loop.test.ts` |
| R2 | 版本 0.0.0 | 已验证(tag 待打) | 根与 9 个成员 `deno.json`、`apps/web/package.json` 都是 0.1.0;实机 `/health` 返回 `0.1.0`;`git tag` 为空,由协调者打 |
| R3 | 授权内恢复 | 已验证 | 86af723;`e6-auto-recover` 3 条:无授权降级、已发布策略授权下 `task.inspect→download.recheck`→系统核实→事项 resolved、换源被拒、重复触发只有一个 run、预算超限。3641ab9 把 `autoInvestigateMode` 放进了设置页 |
| R4 | `sourcePaths` 无约束 | 已验证 | ceff9cf;`organize-guard.test.ts` 6 条:根外、`..`、符号链接 → 400,dryRun 同样受限;API Key 做 Move 没有授权 → 403;grant 绑定的策略非 published 不算;审批用后 consumed;H&R → 409 |
| S1 | 自己批准审批 | 已验证 | `api.ts` 中 approvals 两个端点为 `auth:"session"`;`approvals.test`「admin scope 的 Key 同样 403」;实机 DEV key → 403 |
| S2 | logger 脱敏窄 | 部分:logger 与模型上下文已验证,rss/browser 仍缺 | `contracts/redact.ts` 由 logger 与 agent 共用,`logger.test` 通过;rss/browser 见 S-4 |
| S3 | TMDB 错误带 api_key | 已验证 | f48e1aa;`tmdb/client.ts` 的 message 经过 `redactUrl` |
| S4 | 审批分支无测试 | 已验证 | `test/agent/approvals.test.ts` 5 条,断言了"改计划→被拒→重批→只消费一次→不可重放" |
| S5 | 剧集纵切 | 已验证 | `e7-tv-series`;W5-C 又加了 `e9-continuous-series` |
| S6 | AT24 | 已验证 | `e8-tag-removal` 4 条,用 qB 调用日志断言:`torrents/delete` 计数不变、资产与位置不变 |
| S7 | 生产构建能开 Agent mock | 已验证 | `agent-mock.test.ts`;文件页 mock 也按同一套规则只在 DEV 生效(`resolveFilesMock`,`model.test.ts:237`) |
| S8 | 引擎面板重跑 | 已验证 | `engine-retry.test.ts` 2 条(幂等键序号、审计、409/404/403) |
| S9 | 控制台 TOTP 入口 | **仍缺** | 见 S-3 |
| S10 | 注册表鉴权标注 | 已验证,但新端点回归 | `api-registry.test` 4 条通过;新增的 download/purge 没有标注,见 S-2 |
| S11 | 文档不一致 | 已验证,但有新的不同步 | 见 S-9 |
## 3. §6 追加范围
### 3.1 订阅增强(对照 05)
| 05 §3 条目 | 状态 | 证据 | 缺口 |
|---|---|---|---|
| 1 总集数来源与账本 | 完成 | 迁移 0025(`content_unit.episode_total*`);`sources-identity/service/metadata-totals.ts`;`subscription-search.test` 断言季总集数写入、pending→active | 只建季单元,不预建集单元(设计如此) |
| 2 `resolveEpisodeTotal` | 完成 | `domain/slot/episode-total.ts`;`domain/test/subscription.test.ts` 覆盖账本 §13 的样本 1、9 | — |
| 3 完成判定 | 完成(有双轨) | `services/subscriptions.ts slotComplete`;`__tests__/subscription.test.ts:233`「总集数未知不结束、记疑似缺集;就绪后补齐 → once 完成并写历史」 | S-8 |
| 4 `computeUnitGaps` | 完成 | `domain/slot/gaps.ts` + 单测;`services/acquisition.ts` | — |
| 5 Slot×单元预留 | 完成 | 0025 去掉 `acquisition_plan_slot_active_key`;`domain/slot/inflight.ts`;`subscription.test.ts:112`(20 个断言);E9「Slot 忙时 E02/E03 同时在途」 | 电影 Slot 仍然只允许一个在途计划(设计如此) |
| 6/7 忙后重评、主动搜索 | 完成 | `slot_deferral`、`sweepDueSlots`;`searchSubscription` 与 `POST /subscriptions/{id}/search`(admin,入队) | — |
| 8 元数据 6h 刷新 | 部分 | `sweepMetadataRefresh` | 完成前没有强制刷新 |
| P1 9–14 | 完成 | include/exclude 正则白名单、站点范围、每集体积、集范围、季包选文件、`slot_unit` 分集洗版 | — |
| P1 15–17、P2 18/21–23/25/26 | 未做或部分 | 05 §4 遗留 | 见 §8 |
REQ 增强点:"总集数未知不结束"和"临时集单元"从 ✗/◐ 变成 ✅;"Slot 多目标"(`require_distinct_asset` 没有接入)、"偏好标签参与评估"、"Agent 选片理由与独立预算"、"回退等待期"、"队列按优先级出队"仍是 ✗/◐。
### 3.2 文件管理(对照 06)
**端点**:规格里的 16 个全部实现,另外新增 `POST /files/copy` 和 `GET /files/records`。与规格的偏离:
| 端点 | 与规格的偏离 | 性质 |
|---|---|---|
| download | 规格写的是 `organize.read`,实现为仅管理员会话 | 更严,可以接受 |
| 永久删除开关 | 规格写的是设置项 `files.allowPermanentDelete`,实现为环境变量 `FILES_ALLOW_PERMANENT_DELETE` | 需要重启才能生效,可以接受 |
| scrape | `writeNfo/images` 返回 `not_implemented` | 规格允许拆到 W5 之后,已列入 §8 |
| 路径守卫 | 没有下沉到 `adapters/fs/guard.ts`,模块自带 `resolveIn/recheck` | runbook 已写明 |
**§4.1 不变量**:
| 不变量 | 状态 | 代码 | 测试 |
|---|---|---|---|
| 1 realpath 在根内;`..`、符号链接逃逸、根本身写操作拒绝;执行前再 realpath(TOCTOU) | 满足 | `common.ts resolveIn/recheck`;所有写端点都先过 `resolveIn`(mkdir/rename/move/copy/organize/delete/restore/probe/identify/download) | `browse.test:90`(`..`、URL 编码、根外、符号链接指向文件和目录、未登记根);`write.test:280` 的 TOCTOU 用例:预览后源被换成链接 → `path_escape`,根外文件不动 |
| 2 浏览器不直接写;审计与幂等键 | 满足 | `routes.ts idem()` + `ledger.ts`;每个写操作都调 `audit()` | `write.test:196`「幂等重放不重复执行」 |
| 3 路径变更只来自显式操作,同一事务更新位置与绑定 | 满足 | `mutate.ts`、`records.ts relocate` | `write.test:74/144` |
| 4 删除不碰 qB;做种中不能改名、删除、Move | 满足 | `trash.ts checkItem`、`transfer.ts download_active` | `trash.test:38`、`write.test:174` |
| 5 失败≠不存在 | 满足 | `onlineRoot` 返回 503;`fsErrorToFiles` | `browse.test:120`、`trash.test:202`、`w5a2.test:392` |
| 6 只执行 dryRun 产出的 planId,源事实不变 | 满足(move/copy/organize/delete) | `loadPlan` + 指纹 | `write.test:196`、`identify-organize.test:72` |
| 7 不覆盖 | 满足 | `moveSafe`;copy 遇到目标已存在记失败 | `write.test:74`、`w5a2.test:182` |
**前端**:路由 `/console/files` 和 `/console/files/trash`,组件 `DirTree`、`PathBreadcrumb`、`ContextMenu`、`DetailSheet`,4 步向导(未决项挡住下一步)。虚拟滚动是自己写的(没有引入 `@tanstack/react-virtual`,所以不需要登记许可)。键盘支持 `role="grid"`、F2、Del、Backspace、Shift 区间选、Ctrl+A;拖拽后先打开 dryRun 对话框;SSE `files.changed` 触发刷新。测试:`components/files/model.test.ts` 16 条(选择模型、徽标、禁用原因、集号偏移、未决判定、mock 开关),冒烟覆盖两个路由。
**缺口**:规格 §4.2 要求的前端 e2e("下载根散文件 → 识别 → 向导 Copy → 整理记录 → 状态变为已登记")和移动端快照没有做。
### 3.3 Agent 会话页(对照 agent-ui-reference §3 映射表)
| 事件/对象 | 落地 | 位置 |
|---|---|---|
| `run_started` / `text_delta` | `Message` + `StreamingResponse`(增量拼接,加流式光标) | `agent-conversation.tsx` |
| `thinking_delta` | 自写可展开的思考块,保留完整文本 | `:239` |
| `tool_call_start/end` | `ToolResult`,带类别/scope/风险/幂等徽章 | `agentui/tool-result.tsx` |
| `tool_call_rejected` 与授权失败(`unauthorized/forbidden`) | `ToolApproval` 的 `rejected` 醒目态(`role=alert`) | `:130-139` |
| `compaction` | `MessageMarker` | `:451` |
| `run_completed/failed`、超预算 | `MessageBubble` 的 warning/danger 变体 + 超限原因 | — |
| Approval | `ApprovalCard`,状态集对齐 pending/approved/rejected/revoked/expired/consumed;非管理员会话时按钮禁用 | `:626-670` |
| `AgentGoalReport` 声明与核实分开 | `FinalReport`,双栏 + `FileDiff` + `CitationList` | `:684-790` |
| Budget | `BudgetBar`(自写,有超限态) | `:563` |
| RunMode | 头部显示"只要方案 / 授权内执行";plan_only 用 `TodoList` | `:338,491-514` |
| 滚动跟随 | `MessageScroller`(阈值状态机 + "回到底部"按钮) | `agentui/message-scroller.tsx` |
结论:映射表每一行都落地了,许可文件 `LICENSE-agentui.md` 也在。缺口:组件层没有单测,只有 `agent-helpers.test.ts`;幂等键取自审计,最多翻 3 页(文档已写明)。
### 3.4 转移语义(对照 moviepilot-rust-transfer §7)
| 项 | 状态 | 证据 |
|---|---|---|
| 挂死隔离在**生产二进制**里生效 | 已验证 | 3641ab9:`main.ts` 在加载其余模块前分派 `--role fsworker`;`deno.json compile --allow-run=…,/app/umt-server`;`deploy.md` W5-E 容器演练:日志里 `workerKind:"self"`,`/proc` 里能看到 `umt-server --role fsworker`(uid 10001),停滞 3 秒 → SIGTERM → SIGKILL,共 3 次,之后账本记 `TIMEOUT` 并建待处理,只残留 `.uhd-partial`、没有最终名的半成品;`fsproxy.test` 的 10 条中有 3 条挂死用例(屏蔽 SIGTERM、FIFO、快操作) |
| partial 清理 | 已验证 | `copy.ts sweepPartialsOlderThanMs`,`executeRecord` 对每个目标目录也清一次;`organize-fs.test:48`「写前清走超过 24h 的 partial」 |
| replace | 已验证 | `copy.ts replaceCommit`(硬链接留底 → rename 原子替换);3641ab9 起旧文件进 `.umt-trash/<day>/<uuid>/` 并登记 `trash_item`;`organize-fs.test:48` |
| EEXIST 误报 | 已验证 | 对账阶段比对失败按 IO/TIMEOUT 处理,不再报 EEXIST;`organize-fs.test:118`:FIFO 让对账挂死 → 2 次 TIMEOUT 重试 → 第 3 次 failed + `fs_retry_exhausted` |
| TIMEOUT 错误码 | 已验证 | `FsErrorCode.TIMEOUT`,`ETIMEDOUT` 也映射为它;`fsErrorClass` 归为 retry;`fsproxy.test:184` |
| 缺口 | — | `planImport` 的 stat/哈希、`reconcile.ts`、文件管理都不经 fsproxy;没有根级熔断(把根标为 suspect);基准 B1–B3 没跑;失效软链、hardlink、蓝光原盘、organize 的空目录上溯清理 |
### 3.5 差分补迁(对照 mp-rust-logic-parity §10)
| 链路 | 补迁前 | 补迁后 | missing / our_bug |
|---|---|---|---|
| 标题识别(216 条 × 18 字段) | 92.9%;剔除登记项后 97.4% | 94.4%;剔除登记项后 100% | 0 / 0 |
| 显式 ID | 8/11 | 10/11 | 0 / 0 |
| 过滤规则(644 个 case) | 95.7% | 97.8% | 7(每集体积,属 Slot 能力)/ 0 |
| 站点索引解析 | 98.1% | 99.5% | 0 / 0 |
| 繁简转换 | 84.1% | 92.8%;剔除登记项后 100% | 0 / 0 |
| RSS | 95.5% | 未变 | 0 / 0 |
差分测试在 `adapters/test/differential/{indexer,rss}.test.ts` 和 `domain/test/differential/{meta,zhconv}.test.ts`,本轮单包测试全部通过。遗留:字幕搜索还没接到站点适配器;RSS 不带时区的时间按站点时区解释;HTML 命名实体。
## 4. 解耦审计(03)复核
| 项 | 状态 | 复核 |
|---|---|---|
| 后台循环只编排 | 满足 | `planTick` 只有 enqueue;qB 同步有独立定时器;文件管理的 `purgeTrash` 也是入队(`file-manager/index.ts enqueuePurge`) |
| HTTP 内无重活 | 部分 | cleanup/reconcile 返回 202;`/subscriptions/{id}/search` 入队;files 的 move/copy/organize 执行入队 `verifyAndImport`,请求内只等待,最多 20 秒。**遗留**:`GET /inventory/media-servers` 串行查 Emby;files 的回收站/还原跨卷复制、永久删除、identify、probe 仍在请求内(S-6、S-7) |
| worker 分组 | 满足 | `--role worker`(io 组)、`--role worker:agent`、`--role all`;认领时按任务名过滤(`worker-groups.test`);`purgeTrash` 注册在 io 组 |
| 超时与限并发 | 部分 | 4 个外部 IO 能力在 HTTP 路径上有 15 秒超时,站点并发 2、TMDB 并发 4。**`/files/identify` 绕开了限流器**(S-6);超时后底层调用仍会跑完 |
| 探针 | 满足 | 事件循环延迟探针,1 秒采样,偏差超过 5 秒告警,随心跳上报;fsproxy 的 `stats()` 写进 `organize.transfer` 日志 |
## 5. 安全复查
### 5.1 新端点的 scope 与会话要求
| 端点 | 注册表 scope | 处理器内的附加要求 |
|---|---|---|
| `GET /files/roots`、`/files`、`/files/stat`、`/files/trash`、`/files/records`;`POST /files/identify`、`/files/probe` | organize.read | 无(probe 会写 `media_info`,见 §1.3) |
| `POST /files/mkdir`、`/files/rename`、`/files/copy`、`/files/scrape`、`/files/trash/restore` | organize.execute | 目标或源根必须可写,或者是 managed 库根 |
| `POST /files/move`、`/files/organize`(action=move) | organize.execute | API Key 需要 grant 或一次性审批(`authorizeMove`);move 只核对第一个源的作品(S-1) |
| `POST /files/delete` | organize.execute | 进回收站:API Key 每次都要审批(绑定 planId 与内容哈希,用后 consumed);永久删除:必须设置 `FILES_ALLOW_PERMANENT_DELETE`,且只接受管理员会话 |
| `POST /files/trash/purge` | admin | 只接受管理员会话;注册表没有标 `auth:"session"`(S-2) |
| `GET /files/download` | admin | 只接受管理员会话,文件类型白名单,上限 20 MiB;注册表没有标 `auth:"session"`(S-2) |
| `POST /subscriptions/{id}/search`、`/subscriptions/{id}/episode-total` | admin | 无 |
| `GET /subscriptions/{id}/progress`、`/subscription-history` | read | 无 |
| `POST /engine/runs/{id}/retry` | admin | 只允许已结束的 run;幂等键带序号;写审计 |
| `POST /agent/runs`、`/agent/runs/{id}/messages`、`/agent/runs/{id}/abort` | organize.execute | 能力调用在服务端逐次授权 |
| `POST /approvals`、`PATCH /approvals/{id}` | admin + `auth:"session"` | API Key 和 DEV key 一律 403(已实测) |
注册表共 208 个端点:read 89(其中 3 个 public)、organize.read 10、organize.execute 27、admin 79(其中 5 个 session)。
### 5.2 其他项
- **路径守卫覆盖**:13 个 files 写端点和读端点都先过 `resolveIn`,执行前再调 `recheck`。只有 restore 的目标可写性,以及原位还原时的递归 mkdir 这两处有缺口(S-5)。
- **回收站与永久删除**:`purgeTrash` 删除前先复核路径在 `.umt-trash` 或 `DATA_DIR/trash` 下,并且先删行、再删文件。永久删除有双闸(环境变量开关 + 管理员会话)。restore 不需要审批(只把文件放回,风险低,可以接受)。
- **下载上限**:打开文件后按文件句柄 stat,超过 `FILES_DOWNLOAD_MAX_BYTES` 返回 413;SVG 一律作为附件下载;响应带 `nosniff` 和 `CSP sandbox`。`w5a2.test:350` 覆盖了上述行为。
- **审批只认管理员会话**:创建和决定两个端点都是 `auth:"session"`。`consumeApproval` 与 `authorizeMove` 都用"`status=approved` 才更新"来保证只消费一次。
### 5.3 脱敏键表四处对照
| 参数 | contracts(logger + redactForModel) | rss/redact.ts | browser/redact.ts |
|---|---|---|---|
| passkey / authkey / torrent_pass / rsskey / downhash / apikey / api_key / access_token / token / secret / sign / credential / key | ✓ | ✓ | ✓ |
| `pass_key` / `auth_key` 等带分隔符的写法 | ✓ | ✗ | ✗ |
| c_secure_pass(作为 query 参数) | ✓ | ✗ | ✗(只遮 cookie 里的 `c_secure_*`) |
| refresh_token / password / uid_key | ✓ | ✗ | ✗ |
| auth | ✓ | ✓ | ✗ |
| signature | ✓ | ✗ | ✓ |
| 请求头、Bearer、URL userinfo | ✓ | 只有 userinfo | cookie 头、userinfo |
结论:logger 与模型上下文共用同一份实现;rss 和 browser 仍各自维护(S-4)。
## 6. 工程规范
| 项 | 结论 |
|---|---|
| 提交信息 | 82 个提交全部合规,0 个违规 |
| README、runbook 与 deno 任务 | 文档里出现的 19 个 `deno task` 全部存在(根目录或成员包);没有残留 pnpm/npm 用法 |
| 包 README 与导出 | adapters 新增的 `./fsworker` 导出、contracts 新增的 4 个文件、server 的 file-manager 模块没有写进 README(S-9) |
| CHANGELOG | 没有覆盖 W4-E4 到 W5(B1) |
| 迁移与生成类型 | 0024 `trash_item`、0025 订阅单元、0026 整理动作,编号连续(0017 空缺是历史遗留);`db/src/types.ts` 已含 `trash_item`、`slot_unit`、`slot_deferral`、`subscription_history`、`episode_total*`,以及 action/mode 的 `trash/delete/restore` |
| api.ts 双向登记测试 | `api-registry.test` 4 条通过("OpenAPI 路由都已登记"、"登记的都已实现"、public 端点集合、session 端点集合);漏标见 S-2 |
## 7. 抽查 12 个新增测试
| 用例 | 结论 |
|---|---|
| `write.test`「TOCTOU:预览后源被换成指向根外的链接」 | 有效:断言 400 `path_escape`、根外文件内容不变、目标不存在 |
| `trash.test`「永久删除默认关闭;API Key 删除需一次性审批」 | 有效:断言 403 `permanent_disabled`、无审批时 `approval_required`、审批后执行成功且文件已删除。缺一条"重放 → 403" |
| `w5a2.test`「下载:仅管理员会话……」 | 有效:断言 MIME、`disposition`、`nosniff`、READ/EXEC key 403、视频 403、超限 413、`path_escape`、目录 400、审计 2 条。缺"admin scope 的 key" |
| `browse.test`「根离线 → 503,不返回空列表」 | 有效(按用例名与 `root_unavailable` 断言判断) |
| `organize-fs.test`「对账挂死 → TIMEOUT(不是 EEXIST)→ 有限重试 → 待处理」 | 有效:逐次断言错误信息、文件状态保持 running、最终 failed 且 `code=TIMEOUT`、decision 里 `attempts=3` |
| `fsproxy.test`「复制中途挂死 → SIGKILL → TIMEOUT」 | 有效:断言错误类别为 retry、耗时下限、进度大于 0、`timeouts/killed=1`、最终名不存在、partial 可被清理 |
| `subscription.test`「Slot×单元多在途……移除释放」 | 有效:20 个断言(在途单元、延后、释放、重评) |
| `e9`「Slot 忙时 E02/E03 同时在途,另一版本延后」 | 有效(E9 三步串联,最后断言入库和历史) |
| `e6-auto-recover`「授权内 recheck → 系统核实」 | 有效:断言 run 字段、调用序列 `[task.inspect, download.recheck]`、`download__submit` 被拒、`grant_id`、qB recheck 只 1 次、decision resolved、去重。只核实到 downloading |
| `e8`「qB 任务消失 → removed;资产不变,不重下」 | 有效:断言位置前后相同、Slot 仍 satisfied、计划只有 1 个、`torrents/add` 计数 |
| `approvals.test`「改计划 → 被拒 → 重批 → 只消费一次 → 不可重放」 | 有效 |
| `engine-retry.test`「按原任务名与输入重新入队,序号递增,写审计」 | 部分有效:enqueue 是 mock,没有验证真实引擎里的新 run |
## 8. v0.1 已知限制(供 CHANGELOG 引用)
**范围外(00 §3.2 / 02 后置)**
1. 国产综艺分段、`require_distinct_asset` 组合校验、浏览器/camofox 与原站视图、HHClub/CHD、旧库分范围接管、Agent 主观选片与规则学习、多用户。
2. Passkey、主密钥轮换、子代理、Transmission、TTG 自定义适配、馒头 adult 模式。
**订阅(05 遗留)**
3. 完成判定前不会强制刷新元数据;titleQuery 订阅不会自动建档回填;季范围语义(`current_season` 在没有季单元时固定)未做;偏好标签不参与评估。
4. 同作品同季重复订阅没有查重;`prefer_pack`、回退等待期、Agent 选片理由与独立预算、队列按优先级出队、实体级识别词都未做;没有"清账重置"(只能把状态改回 active 重新订阅)。
5. 完成判定暂时有两条路径(S-8)。
**文件管理**
6. NFO/图片投影(scrape 的 `writeNfo/images`)、硬链接、蓝光原盘整目录操作未做;路径守卫没有下沉到 adapters。
7. 从回收站还原不会恢复资产状态,需要重新整理登记;永久删除开关是环境变量,不是设置项。
8. 跨卷进回收站(≤2 GiB)、跨卷还原、永久删除、identify(每项超时 15 秒,不经 TMDB 限流器)、probe 都在 HTTP 请求内执行,也都不经 fsproxy(S-6、S-7)。
9. 前端没有向导 e2e 和移动端快照,页面行为靠 model 单测和冒烟覆盖。
**转移与文件系统**
10. `planImport` 规划期的 stat/哈希、资产对账和文件管理不经 fsproxy;没有根级熔断;失效软链一律按冲突处理;hardlink、organize 源侧空目录上溯清理、蓝光原盘未做。
11. 二进制必须位于 `/app/umt-server`,否则 fsproxy 回退为进程内看门狗(能按时报 TIMEOUT,但卡住的线程无法回收)。基准 B1(50 GB)、B2(并发)、B3(真实 FUSE 冻结)没有跑。
**Agent 与安全**
12. 控制台没有 TOTP 开关入口(S-3)。
13. rss 和 browser 的脱敏键表没有与 contracts 统一(S-4)。
14. `GET /inventory/media-servers` 在请求内串行查 Emby;能力调用超时后,底层调用仍会跑完。
15. Agent 页的幂等键来自审计(最多 3 页),审批卡片的 planId 靠解析工具摘要得到,较早的运行可能取不到。
16. 自动恢复只核实到下载恢复,入库由后续流程负责;恢复白名单只有 `task.retry`、`download.recheck`、`identity.resolve`。
**识别与站点**
17. 字幕搜索只到解析层;RSS 不带时区的时间与 HTML 命名实体未处理;过滤器的 tmdb 放行、SPECSUB 等属于 Slot 能力扩展。
**质量与运维**
18. 恢复流程只有人工演练;流程版本升级没有产品侧测试;下载状态机 `ALLOWED` 未收紧;`scanIncremental` 出错时仍返回 complete;AT18 恶意标题 e2e、AT42 未纳管占用没有测试;`vertical.test` 的用例依赖执行顺序;web 单测少。
19. `deno task ci` 约 135 秒以上,server/tasks 测试是主要耗时。
## 9. 给 M2 的前 5 条建议
1. **把 IO 统一收到 fsproxy 并加根级熔断**:`planImport`、reconcile、文件管理的 stat/readdir/复制全部经 FsProxy;根超时标为 suspect,暂停向它派发,建待处理;回收站和还原的跨卷复制改为入队。跑 B1–B3 基准,并把结果写进性能基线。
2. **凭证与授权收口**:只保留一份脱敏实现(rss/browser 改用 contracts)并加全仓凭证泄漏的故障注入测试;注册表的 `auth` 标注由处理器的真实行为自动核对(对每个端点用 admin scope 的 key 发一次请求,断言 403 与标注一致);多作品 Move 按作品集合授权;补 TOTP 入口。
3. **订阅闭环第二阶段**:消除双轨完成判定;完成前强制刷新;偏好标签编译进 SlotSpec;`require_distinct_asset` 接入评估与入库(这是综艺和双版本的前置);队列按优先级出队。
4. **文件管理补全**:NFO 投影(带 umt 标记与字段锁)、hardlink(`link` 自带 no-clobber)、蓝光原盘目录、空目录上溯清理;前端补向导 e2e、移动端快照和键盘可访问性测试。
5. **把 CI 当作发布门禁**:e2e 按包并行、给 server 测试分片,把 ci 控制在 90 秒内;夜间跑"备份 → 清库 → 恢复 → 只读对账 → 映射比对"和流程版本升级回归;CHANGELOG 与已知限制由每个波次的汇报强制更新,避免再出现 B1。
## 10. W6-B 处理状态(2026-09-28,打 tag 前)
| # | 状态 | 提交 | 说明 |
|---|---|---|---|
| B1 | 已修 | b48d859、本文档提交 | CHANGELOG `[0.1.0]` 按功能补齐文件管理、订阅增强、转移语义、Agent 会话页、识别补迁、稳定性(W4-D~W6-A)与 W6-B 安全修复;"已知限制"换成本文 §8 的 19 条,过时的集单元条目已删 |
| S-1 | 已修 | 17cf3cb | `authorizeMoveEntities`:每个源的作品集合(声明作品 ∪ 源下已登记资产的作品)逐一核对 grant,一次性审批覆盖整请求;无法归属作品的源只认审批;拒绝时列出未授权项并写审计。`/files/move` 与 `/files/organize`(move,先整体预检、不消费审批)都接入;copy 不删源、delete 每次审批,无此问题。测试:`write.test`「两个作品只授权一个 → 403」、`identify-organize.test`「源是别的作品的已登记资产 → 403」 |
| S-2 | 已修 | 17cf3cb | download / trash purge 标 `auth:"session"`,files 路由按注册表挂 `requireAdminSession`;口径:DEV key 等同 admin scope Key,仅会话端点与永久删除一律拒绝,Move/删除审批的免授权仍放行 DEV key(与 `authorizeMove` 一致)。`api-registry.test` 会话列表加这两个,新增反向断言(未标 session 的端点对 admin scope Bearer 不得 403,会话中间件在入参校验之前,暗挂必暴露);download/purge 用例补 admin scope Key 与 DEV key 403 |
| S-3 | 已修 | ba1361a | 账户弹层"二次验证":生成密钥(base32 分组 + otpauth 链接)→ 6 位码确认开启;已开启时凭当前密码 + 验证码关闭。没有二维码(不引新依赖);没有组件测试,只有类型检查 |
| S-4 | 已修 | 47bd04a | contracts 新增 `isSensitiveQueryParam`;rss 删掉本地 `SENSITIVE_QUERY_KEYS`,browser 的 `CREDENTIAL_PARAM` 由 `SENSITIVE_QUERY_PARAMS` 生成,各自只保留特有规则;`adapters/src/redact-consistency.test.ts` 24 种写法三处一致 |
| S-5 | 已修 | 17cf3cb | 还原目标走 `isWritableTarget`;原位还原 `mkdirWithin` 逐级 lstat + realpath,不跟随符号链接;跨卷还原受 `trashCrossMaxBytes`,进出回收站的跨卷复制经 fsproxy。测试:下载根 / register_only 目标 403、父目录换成根外链接 → path_escape 且根外无新目录、逐级重建、跨卷超限 too_large |
| S-6 | 已修 | b4560be | `sharedInvokeLimiter()` 进程共享;identify 的远端查询经 TMDB 信号量 + 15 s 超时,单次请求去重后最多 20 个,超出项给 warning |
| S-7 | 部分,余下延期至 v0.1.1 | 17cf3cb | 做了:跨卷还原加上限、回收站进出的跨卷复制经 fsproxy。未做:回收站 / 还原 / 永久删除 / identify / probe 改入队,根探测与 readdir 经 FsProxy 快操作。已写入 CHANGELOG 已知限制第 8 条 |
| S-8 | 已修(W6-A) | 887843f | 已核对:`organize.ts` 不再有本地 `slotComplete` 与订阅完成写入,手动整理指定 Slot 改调 `services/subscriptions.ts slotComplete`;完成与历史只由 `applySlotEvent(verify_passed)` 负责 |
| S-9 | 已修 | 2e3c209、cb3fb9b | rust-transfer §7 遗留(编译二进制 fsworker、trash_item)标已做;contracts / adapters / server README 补齐;04 表 S9 标已修;files runbook 授权矩阵同步 |
| S-10 | 已修 | 2ddaf7b | 开关需同时 `UMT_FS_TEST_HOOKS=1`(`fsTestHookFromEnv` 单测);`preflight` 发现已设置就 warn;deploy.md 演练命令同步 |
§1.3 建议:
| 建议 | 状态 |
|---|---|
| probe 标 `organize.read` 却写 `media_info` | 已修(d3de2c8):改为 `organize.execute`;仍在请求内串行,见 S-7 |
| 多项永久删除没有服务端确认 | 延期至 v0.1.1(写入 CHANGELOG 已知限制第 8 条) |
| trash 用例补"重放 → 拒绝" | 已补(d3de2c8) |
| download 用例补"admin scope 的 Key → 403" | 已补(17cf3cb) |
| E6 自动恢复延伸到入库、engine-retry 真实引擎用例 | 延期至 v0.1.1(已知限制第 18 条) |
| e2e 编号重复 | 已修(d3de2c8):`e6-w4a-endpoints` → `e10-w4a-endpoints` |
| 04 §1.3 的 7 条 | 延期(已知限制第 18 条) |
| biome 1 warning + 1 info | 已清(9887d8b) |
另:第一次全量 ci 暴露 `fsproxy.test`「正常复制」偶发失败(fsworker 的进度节流吞掉了复制阶段最后一跳,收到 6291456 而非 6291461),7daf544 改为阶段完成立即上报,单测连跑 3 次通过。
**ci**:修复后连续两次 `deno task ci` 全绿:357 s、341 s(server 44 文件 / 267 用例)。未打 tag。
-177
View File
@@ -1,177 +0,0 @@
# v0.2 收尾修复(W8-A)
2026-09-28。基线 `c67f6a9`(Pi 上游、子会话、定时任务看板、IA-1、IA-2 已合入)。与 W8-B(全站文案第二轮)并行,本轮只改逻辑。
## 本轮修了什么
| # | 事项 | 落地 |
| --- | --- | --- |
| 1 | 模块自建计时器并入定时任务注册表 | `pi.token_refresh`(上游登录刷新,维护组,10 分钟,入队 `refreshPiTokens`,幂等键按时间槽)与 `auth.cleanup`(登录会话清理,维护组,1 小时)改为 `registerScheduledJob`;模块里不再有 `setInterval`。测试 `test/schedules/module-jobs.test.ts`。`docs/runbook/schedules.md` 已登记表补两行 |
| 2 | 没有候选的待识别资源进待处理 | 迁移 `0030` 给 `decision.kind` 加 `identity_unresolved`(界面"待识别")。`resolveObservation` 在 no_match(含无法解析、候选为 0)和查询失败且自动复查已停时开事项,去重键与歧义类同为 `identity:<资源ID>`(类别随证据切换)。选项:手动指定 TMDB ID(`requires=tmdb`)、选本地作品(`requires=mediaEntityId`)、忽略;歧义类事项也追加前两项。`POST /decisions/{id}/resolve` 新增 `identity {tmdb:{type,id} \| mediaEntityId}`,走 `assignReleaseIdentity` 人工指定加锁。待处理页选项弹窗按 `requires` 出 TMDB 类型 + ID 输入或本地作品检索(复用 `EntityPicker`),未填完整时确认按钮禁用。身份页只读列表"已进待处理"按类别跳 `?kind=identity_ambiguous / identity_unresolved` |
| 3 | 旧 `settings.pi.provider / baseUrl / model / thinkingLevel / apiKeySecretRef` | `DEPRECATED_SETTINGS_FIELDS`:schema 保留(兼容已存 JSON),`PATCH /settings` 丢弃,设置分区不再归属;`runAgent` 只走上游,没有默认上游直接失败「未配置 Agent 上游」;`pi.apiKey` 写 secret 的分支删除;Agent 页演示开关改看默认上游 provider 是否 fake;上游列表空态不再提旧设置。e2e 夹具改为直接建一条 fake 上游设默认 |
| 4 | `agent.await` 不占 worker 槽 | runAgent 驱动的会话里,子会话未落定时 `agent.await` 记下等待条件并关闭会话(该工具调用留在 JSONL 里未完成);`drive` 返回 `suspended`,runAgent 用 `waitForFact("subagent-await-N")` 每 2 秒查一次子会话落定 / 超时 / 父会话取消,再以 `resumedAwaits` 重开会话 resume。`agent.await` 是只读能力(replay=safe),恢复时重放一次直接拿到结果,模型不多一轮。单会话最多挂起 100 次。e11 断言 `agent.await` 的结束事件带 `recovery:true`、`run_started` 为 `[false, true]`。`agent.spawn {await:true}` 仍在进程内等(见遗留) |
| 5 | Agent 汇报里的下载链接 | `agent-conversation.tsx` 改链 `/console/downloads/:id`,不再经 `/tasks/:id` 404 探测 |
| 6 | 设置旧链接 | IA-2 已由 `settings.tsx` 的 `clientLoader` 把 `?tab=` 转成 `#分区[/节]`;本轮把 `settingsPath()` 改为直接生成 `#分区[/节]`,冒烟的重定向表补 4 条 `?tab=`,静态路由补 8 个分区 hash |
| 7 | 前端冒烟 | 独立库 + `PORT=4392 WEB_ROOT=apps/web/build/client` 单端口跑 `deno task test:web:smoke`:47/47 通过(跳过 4 条详情页,库里无数据)。冒烟脚本对只差 hash 的地址先跳 `about:blank` 再整页加载 |
另:`executeResolution` 对 `identity_unresolved` 同样跳过(由 sources-identity 在处理端点里直接执行)。
## 仍遗留
- **pi 41 个 provider 未联网实测**:只有 fake 与 OpenAI 兼容本地端点有自动化覆盖;8 种订阅登录只测了 fake OAuth 状态机。Codex `_randomBytes` 在 Deno 下的行为仍待 spike(`docs/design/pi-providers.md`)。
- `agent.spawn {await:true}` 仍在进程内轮询等待,占 worker 槽到超时;要挂起需要同样的关闭会话 + 重放,但 spawn 是 replay=never,重放会得到"结果未知",需另行设计。
- 挂起期间父会话状态保持 `running`(避免被祖先会话的 `agent.await` 当成已落定);运行时长预算按每段 drive 计,不含挂起等待。
- 待识别事项:查询失败但仍在自动复查期内的资源不开事项(避免 TMDB 抖动刷屏),复查耗尽后才开。
- 身份页"待识别"详情抽屉的提示句仍写"选择候选或忽略",未识别类(无候选)读起来不准确,留给文案轮。
- 旧设置字段只是弃用未删除;真正删除需要一次迁移清理 `settings.pi` 里的旧键。
- 原站视图仍是占位(IA-1 遗留)。
## W10 集成记录
2026-09-28。目标:所有并行成果合进 main,`deno task ci` 连跑 3 次全绿。
### 合并了什么
- `fe3-web`(FE-3:规则页、命名模板表单、顶栏搜索 / `Ctrl/⌘+K` / 搜索结果页,基于 `81bf010`)以 `0440130` 合入。冲突只在 `routes.ts`(FE-1 的 `status` 与 FE-3 的 `search` 两条都留)与冒烟清单(合并两边路由,加规则页 4 个页签、搜索结果页、`/console/preferences` → 规则页重定向);顶栏挂 `SearchTrigger` + `CommandPalette`,侧栏仍是 FE-1 定稿的 14 项。合并后 web typecheck 通过,分支已删。
### 修了什么
| 提交 | 内容 |
| --- | --- |
| `615917b` | U3 outbox → SSE 桥:`OutboxRelay` 的 `mirror`,`feed.*`(除 `feed.due`)、`decision.*`、`download.*`、`organization.*`、`subscription.*` 投递成功并提交后按序发到总线;以前没有路由的 `feed.fetched` / `organization.changed` / `download.threshold` 一直停在 pending,现在会被认领。测试:tasks `outbox.test.ts`、server `bootstrap.test.ts` |
| `363f85b` | `/dashboard` 补 `automation.library` 与 `slotSatisfiedRate`;`/engine/runs` 的 `status` 多值、`triggeredBy` 筛选(服务端逐批推断,单次最多扫 2000 行,游标停在最后检查的行);任务页来源筛选和"进行中"页签改为一次服务端分页查询 |
| `2ae780d` | 整理向导把目录展开成逐个视频提交时,planImport 把同目录同主干的字幕 / 外挂音轨补进源文件;`/console/organize?ids=` 只列本次记录 |
| `082c556` | 视觉走查前端项 U4 U5 U6(部分)U13 U14 U15 U16 与文案清单;逐条状态见 `09-visual-qa.md`"W10 前端复核" |
| `f6063af` | server PG 测试偶发"schema 不存在"的根因:Kysely 迁移器用 `introspection.getTables()` 反射全库所有 schema 并逐个调 `has_schema_privilege` / `pg_get_serial_sequence`,别的测试文件同时 drop 自己的 schema 就报错(原有 `withSchemaLock` 只覆盖部分 harness)。`createMigrator` 改为只内省目标 schema;回归用例在迁移时并发建删 schema,旧实现可稳定复现失败 |
| `080563d` | CHANGELOG `[0.2.0]` 补 W9 / 前端波内容与已知限制 |
开发库(55433 `umt`)清理:删除 `fe4-*`、`w32-*`、`w33-*` 前缀的作品(11)、站点(4)、订阅(5)及其版本目标、计划、资产、单元、外部身份、路径绑定等从属行;管理员 `w31-admin` 与 fake 上游保留。测试都用独立 schema,不受影响。
### 三次 CI
HEAD `080563d`,本机串行:
| 次 | 结果 | 耗时 |
| --- | --- | --- |
| 1 | 全绿(server 59 文件 / 366 用例) | 403 秒 |
| 2 | 全绿 | 383 秒 |
| 3 | 全绿 | 381 秒 |
前端冒烟:独立库 + `PORT=4392 WEB_ROOT=<build/client>` 单端口,新初始化管理员,64/64 通过,跳过 12 条详情页(库里无数据)。
### 遗留
- U6 悬浮按钮滚动途中仍可能暂时盖住右侧控件;U8(观众站无 H&R 选择器)、U10 不改;U11 全站标点规范未定,未改。
- 还没有 `subscription.*` outbox 事件,订阅页不随后台变化实时刷新。
- `/engine/runs?triggeredBy=` 命中稀少时一页可能少于 `limit`;定时任务运行记录仍没有引擎 run id。
- 开发库里还有 6 个无前缀的测试站点(观众 ×3、HDSky ×2、馒头,指向已停的随机端口 mock),以及历次测试残留的约 150 个 `*_test_*` / `e2e_*` schema(测试进程中断时 afterAll 没跑到),本轮只清了指定前缀的数据,未动它们。
- 开发库上还挂着两个早先起的 `deno task dev` 服务进程(4394 / 4397,`--watch`),本轮未动。
## INT-2 集成记录(2026-09-29)
### 发生了什么
v0.2 收尾阶段四个子代理(憨憨修复、AGUI-2、BR-2、SEARCH-2 / META-1)在同一个工作区、同一个 git 暂存区里并行提交:
1. 憨憨修复的提交 `901b01d` 用 plumbing 方式从自己的暂存副本提交,把 AGUI-2(`20b63db`)刚改的 14 个 agent 前端文件(`components/agent/*`、`agentui/tool-result.tsx`、`routes/console/agent*.tsx`、`api/agent.ts`、`scripts/smoke.mjs`)带回了 AGUI-2 之前的版本。
2. 随后 BR-2(`28974d1`)从共享暂存区提交,暂存区里是 `901b01d` 之前的文件,于是把 `901b01d` 的改动整体撤掉(附带把 AGUI-2 恢复了)。憨憨的改动只剩工作区未暂存改动和 `901b01d` 的 diff。
3. SEARCH-2 的代码被文档提交 `ac47867`(`docs(plan): on-demand site detail page parsing`)从暂存区顺带带入,内容完整;`18a5cf1` 是补记用的空提交。憨憨改动里的 contracts(api / settings / sources)、`routes/sources.ts`、db 类型、runbook 部分也经这些提交进了 main。
### 如何修复
- AGUI-2 核对:`git diff 20b63db HEAD -- components/agent components/agentui routes/console/agent* api/agent.ts scripts/smoke.mjs` 为空,HEAD 上就是 AGUI-2 版本,无需恢复。
- 憨憨改动:在临时 worktree 上把 `git diff 20b63db 901b01d`(排除 agent 相关 14 个文件)三方合并到 HEAD,结果与工作区未暂存改动逐文件一致(22 个文件;`account.ts` 与 `schema.test.ts` 是和 BR-2 / META-1 合并后的版本;contracts、routes、types、runbook 已在 main,合并后无差异,`settings.ts` 的冲突 hunk 因此不再需要手动处理)。按路径 `git add` 后提交 `6e9e8ef fix(sites): HHClub parsing, resource list layout and request restraint (re-landed)`。工作区没有其他无关改动。
- 迁移:编号连续无重号,开发库(55433)的 `kysely_migration` 与仓库一致(0046 → 0049 依次应用,无残留行需要处理);`db:migrate` 无待执行迁移,`gen:types` 无 diff。
| 迁移 | 来源 | 内容 |
| --- | --- | --- |
| `0046_site_request_count` | 憨憨修复(`6e9e8ef`,原 `901b01d`) | 站点请求按小时计数 |
| `0047_identity_pool` | DEC-1(`94fd48b`) | 待识别池 |
| `0048_site_search_default` | SEARCH-2(代码在 `ac47867`,记录 `18a5cf1`) | `site.search_default` |
| `0049_enrichment` | META-1(`f4666a4`) | 元数据补全 |
### 站点详情页(追加需求)
`7b5c2f6`:详情抽屉「站点详情页」一节(简介、MediaInfo、截图经图片代理、IMDb / 豆瓣 / TMDB、文件清单、标签、上传者);只按需抓,`kv_cache` 缓存 24 小时;外部 ID 写 `raw._detail` 并作为显式 ID 进身份解析(TMDB ID 直取详情、IMDb 走 `/find`)。细节见 `runbook/sites.md`「详情页扩展内容」。
真实憨憨未核对:要让新解析生效需重建用户栈的 app 镜像(同时执行 0046–0049 迁移),本轮自动模式不允许部署,所以没有对真实憨憨发请求(0 次)。重建后按一次 `GET /sites/{id}/releases/{rid}?refresh=true` 核对,缓存里的 `probe.labels` 可以看出憨憨详情页实际有哪些字段标签,据此补 `umt.detail.page`。HHClub 选择器目前按 MovieClaw 与 fixture 编写。
### CI
本机串行全量 `deno task ci`:
| 次 | HEAD | 结果 | 耗时 |
| --- | --- | --- | --- |
| 1 | `6e9e8ef`(憨憨重新落地后) | 全绿(server 82 文件 / 498 用例) | 509 秒 |
| 2 | `6e9e8ef` | 全绿 | 505 秒 |
| 3 | `04cffcd`(加详情页) | 全绿(server 503、adapters 381 用例) | 533 秒 |
前端冒烟:独立库 `umt_smoke_int2` + `PORT=4392 WEB_ROOT=<build/client>` 单端口,新初始化管理员,66/66 通过,跳过 12 条详情页(库里无数据)。「Agent 抽屉 → 历史」一步用演示数据,生产构建只有默认上游是 `fake` 时才允许演示数据,所以冒烟库里要先建一个 `fake` 默认上游(没有时这一步超时失败)。跑完已删库。
### 遗留
- 真实憨憨详情页核对(见上)与用户栈重建(0046–0049 迁移)待用户执行。
- 开发库残留的 `hhclub-wt` 临时 worktree(`ac47867`,在会话 scratchpad 下)未删,不影响仓库。
## INT-3 记录(2026-09-29)
BR-3 已先行合并(`80f0fa0`)。本轮把六个 worktree 分支依次 `git merge --no-edit` 到 main,再补集成清单。
### 合并顺序与冲突
| 顺序 | 分支 | 合并提交 | 冲突与解决 |
| --- | --- | --- | --- |
| 1 | `cache2`(CACHE-2) | `eb9cfaa` | 无 |
| 2 | `md1`(MD-1) | `b633eb5` | `contracts/settings.ts` 元数据分区字段表:保留 md1 新增的 `metadata.languages`,去掉 md1 带回的已弃用 `cache.tmdbTtlHours` |
| 3 | `id2`(ID-2) | `f30600e` | `adapters/tmdb/index.ts`:md1 的 `details` / `translations` 与 id2 的 `recall` 导出并存 |
| 4 | `hh2`(HH-2) | `f41cd96` | 基于 BR-3 之前的 main,和 BR-3 在 `browser/service.ts`、`contracts/browser.ts`、`desktop-view.tsx`、`site-origin-view.tsx` 冲突:`RecoveryState` / 会话视图同时保留 BR-3 的 `needsReconnect` / `reconnect` 与 HH-2 的 `lastAutomationAt`;前端 import 用 HH-2 的 `controllerLabel` / `agentActive`,保留 BR-3 的 `reconnect.tsx` |
| 5 | `fe-settings`(FE-7) | `161e6e7` | 无 |
| 6 | `fe6`(FE-6) | `4d850c1` | 无 |
合并后 typecheck 直接通过,没有语义冲突。`deno.lock` 在根 `deno check` 与 web typecheck 之间会来回变(web 依赖是否写入),`0a8bfbb` 按 web 版本提交。
### 迁移
只有 cache2 带迁移(0050),其余分支没有迁移,不需要重排编号。集成补丁新增 0051、0052。开发库(55433)依次执行,`gen:types` 已更新。
| 迁移 | 来源 | 内容 |
| --- | --- | --- |
| `0050_storage_retention` | CACHE-2 | 流水表按时间清理的索引 + 按表 autovacuum 参数 |
| `0051_tmdb_cache_size` | INT-3 `fe05138` | 已存设置里 `cache.tmdbMaxSize` 仍是旧缺省 256 的改成 5000(用户库当前是 256) |
| `0052_agent_run_created_by` | INT-3 `e681fce` | `agent_run (created_by, created_at desc)` 索引,供巡检汇总 |
### 清单完成状态
- [x] `cache.tmdbMaxSize` 缺省 5000(含已存值迁移,runbook / porting 文档同步)。
- [x] 设置页「自动化 → 存储与保留」接 `settings.storage`:存储预算、流水数据保留、缓存有效期三张卡;元数据分区去掉 `cache.tmdbTtlHours`、`cache.emptyResultTtlSec`、`identity.poolArchiveDays`。
- [x] 新建档立即应用语言回退链并入队 full 刷新:`/files/entity-from-candidate`(订阅建档、搜索 / TMDB 预览建档、整理向导)与手动指定未登记的 TMDB 身份;幂等键 `refreshMetadata:created:{id}`。RSS 自动识别新建的作品不入队 full(量大),仍按追踪刷新。
- [x] 桌面模式打开时先调幂等 `POST /browser/sessions/{id}/reconnect`;画面 5 秒未加载或视频流未出帧,每个会话自动重连一次并重建桌面连接。
- [x] `GET /schedules` 的 agent_check 项带 `agentRuns`(最近 100 次会话的上次 / 平均费用、近 7 天次数与费用、模型分布、最近 3 次),计划页卡片与首页计划带改用它,删掉前端的会话列表拉取。
- [x] HH-2 用例 `rss-normalize` 的旧 RSS 行首见时间取"现在",会排在下一个用例的快照前面,第 1 次 ci 失败;固定首见时间后通过(`53790cb`)。
### ID-2 回滚脚本 dry-run(用户库,只读)
连接加 `default_transaction_read_only=on`,不带 `--apply`:
- 待识别合并事项 1 条(Archives S01 2009),预估 0/1 可自动确认。
- 3 个分类词站点别名涉及 3 部作品:「东北有个周东北(2026)」绑定 35 条,误并入 29 条(Irreplaceable / 无可替代、Spring of the Blade、A Prophet、Spark of the Long March / 星火征途 的各版本),要删别名「电视剧」「Spark of the Long March」「Irreplaceable」「A Prophet」「Spring of the Blade」;「炼气十万年」删「动漫」、「心动的信号」删「综艺」,这两部绑定都核对通过。
- 合计解除 29 条资源绑定、删除 7 个站点别名;这些作品下 15 个内容单元无资源引用(脚本不删)。
`--apply` 待用户确认,且要在用户栈重建(新识别规则 + 0050–0052 迁移)之后执行,解除的资源才会按新规则复查。
### CI 与冒烟
| 次 | HEAD | 结果 | 耗时 |
| --- | --- | --- | --- |
| 1 | `53790cb` 之前 | `rss-normalize` 用例依赖当前时间失败(见上) | — |
| 2 | `26ce782` | `contracts/db-sync`:`ScheduledJob.agentRuns` 未登记为只在 API 的字段,`481a97f` 补上 | 447 秒 |
| 3 | `481a97f` | 全绿(server 89 文件 / 541 用例) | 555 秒 |
| 4 | `481a97f` | 全绿 | 537 秒 |
前端冒烟:独立库 `umt_smoke_int3` + `PORT=4393 WEB_ROOT=<build/client>`,新初始化管理员 + `fake` 默认上游,69/69 通过,跳过 13 条(库里无数据)。跑完已删库。
### 遗留
- 用户栈重建(app 镜像 + 0050–0052 迁移)由协调者执行;之后再决定是否对用户库跑 `identity-reevaluate.ts --rollback-wrong --apply`。
- RSS 自动识别新建的作品不自动做 full 刷新;要补每集信息时手动刷新或按 runbook 批量跑。
- 桌面黑屏判断只在同源拿得到 Selkies 文档时看视频帧,拿不到时只按 iframe 是否加载判断。
-206
View File
@@ -1,206 +0,0 @@
# v0.2 浏览器视觉与交互检查
2026-09-28。Playwright(chromium headless)逐页截图 + 走业务链。截图在 `apps/web/test/screenshots/qa-v02/`(不进 git),共 310 张。
## 环境
- 独立 QA 栈:`docker compose -p umtqa`,127.0.0.1:4381,独立 `.env`(随机密钥)与 override;`/media`、`/downloads` 挂 `/opt/umt-qa/*`(属主 10001),
放 18 个假样片(电影 / 剧集 / 整季包目录 / 综艺 / 字幕随行 / 无关文件),视频用 `truncate` 做成 400 MB 稀疏文件(主视频下限 50 MB)。
用户的 `umt` 栈(4380)全程未动。
- mock:testkit 的 qB / NexusPHP(观众,自定 5 条 + 默认 fixture)/ RSS / TMDB / Emby,跑在同一 compose 网络里的 `denoland/deno` 容器(`umtqa-mocks`)。
宿主 ufw 的 INPUT 策略是 drop,容器经 `host.docker.internal` / docker0 访问宿主端口不通,所以没按原计划把 mock 放宿主。
- 镜像:先用 `uhd-media-tool:v0.2.0-rc` 检查;修复后以 `83498f9 + 本轮改动` 构建 `uhd-media-tool:qa` 替换 app 复测(见"CI 与并行开发")。
- 视口:桌面 1440×900 明(d)/ 暗(dd),手机 390×844(m)。每页记录 console.error/warn、pageerror、失败请求、横向溢出(`scrollWidth` + 越界元素扫描)。
## 走过的流程
| 流程 | 结果 |
| --- | --- |
| 首次初始化(空提交 / 口令过短 / 两次密码不一致 / 口令错误)→ 登录 → 改密 → 登出 → 再登录 | 通过;**改密时旧密码错误被踢回登录页**(#1,已修) |
| 错误密码限速(另一 `X-Real-IP` 连续 6 次) | 第 5 次起 429「失败次数过多,15 分钟后再试」 |
| 设置 9 分区:自动化改检查间隔、库根 `/media`(哨兵)、路径映射 `/downloads`(校验)、qB 测试、Emby 测试、观众登记 + 改地址 + Cookie、TMDB key + Base URL、API Key 创建 / 撤销 | 全部成功 |
| Agent 上游:fake 设默认 + 测试 + 最小补全;自定义兼容端点指向不存在地址;fake 订阅登录 browser(粘回 URL)与 device(自动批准)、退出登录 | 成功;错误提示问题见 #13、U2 |
| 定时任务:立即执行、停用 / 启用、运行记录 | 成功 |
| Feed 新建 → 立即抓取 → 识别(5 条:4 条识别、1 条待识别)→ 站点工作台库存叠加 → 快照 | 成功;抓取后列表不自动刷新(U3) |
| 收录策略草案(1080p 模板 + 新作品收录)→ 发布 → 订阅沙丘2 → 下载计划 / qB 任务 → mock 完成 → 自动整理 | 首次因样片 1 MB 被 `too_small` 拦下(测试数据问题);改 400 MB 后手动整理成功入库 |
| 待处理:待识别事项「手动指定 TMDB ID」(842675) | 资源变为已识别 |
| 文件管理:浏览、重命名、新建目录、移动(预览 → 执行)、移入回收站、还原、批量整理向导(3 组 5 文件) | 成功 |
| Agent 会话:execute、plan_only、spawn 子会话 + await;抽屉 Ctrl+J 开关、Esc 关闭、焦点落输入框、FAB 状态 | 成功(自定义 fake 脚本后未匹配目标失败,见 U11) |
| 引擎面板 runs / 详情;业务任务失败项重试 | 成功;引擎里没有失败 run,重跑按钮只看了详情页 |
| 审计列表 | 成功;动作名缺中文(#11,已修) |
| SSE 断线:页面打开时 `docker restart umtqa-app-1` | EventSource 3 次内重连,页面无报错;期间 `/dashboard` 一次 CONNECTION_RESET 属预期 |
## 检查矩阵
38 个路由 × 3 视口 = 114 张(文件名 `M-<路由>-<d|dd|m>.png`),修复后复测截图 `V*`。无横向溢出、无 pageerror、无非预期 4xx/5xx。
"问题"列指下方清单编号,✓ 为该视口未发现问题。
| 页面 | d | dd | m | 问题 |
| --- | --- | --- | --- | --- |
| 登录 / 初始化 | ✓ | ✓ | ✓ | — |
| 首页 | 问题 | 问题 | ✓ | #6 #17 |
| 待处理 | 问题 | 问题 | 问题 | #5 #12 U15 U16 |
| 站点工作台 / 详情(整理视图、快照与 Feed、账号) | 问题 | ✓ | 问题 | #3 #15 U8 |
| 来源健康 | 问题 | 问题 | 问题 | #3 U3 |
| 订阅 / 新建订阅 | 问题 | ✓ | ✓ | #5 |
| 收录策略(草案 / 发布) | 问题 | 问题 | 问题 | #4 #5 U4 |
| 偏好标签 | ✓ | ✓ | ✓ | — |
| 下载队列 / 详情 | 问题 | 问题 | 问题 | #2 #6 #9 #14 U7 |
| 下载计划 | 问题 | 问题 | 问题 | #10 |
| 业务任务 / 详情 | 问题 | 问题 | 问题 | #5 |
| 媒体库 / 详情 | 问题 | 问题 | 问题 | #8 U1 U9 |
| 整理记录 / 详情 | 问题 | 问题 | 问题 | #5 #7 |
| 文件管理 / 回收站 / 向导 | 问题 | ✓ | 问题 | #5 U6 U12 U13 |
| 身份与元数据 | ✓ | ✓ | ✓ | — |
| Agent 会话 / 详情 / 抽屉 | ✓ | ✓ | ✓ | U10 |
| 定时任务 | ✓ | ✓ | ✓ | 文案清单 |
| 设置·自动化 / 站点与账号 / 下载器 / 媒体库与目录 / 媒体服务器 / 元数据 | ✓ | ✓ | 问题 | U5 U6 |
| 设置·Agent | 问题 | 问题 | 问题 | #13 U2 U6 |
| 设置·API Key | ✓ | ✓ | ✓ | 文案清单 |
| 设置·账户与安全 | 问题 | ✓ | ✓ | #1 U14 |
| 引擎面板 / 详情 | ✓ | ✓ | ✓ | — |
| 审计日志 | 问题 | 问题 | 问题 | #11 |
| 404 | ✓ | ✓ | ✓ | — |
## 问题清单
共 34 项:已修 17、未修 17(另有文案清单 13 条,其中 3 条已随 #9/#5/#13 修掉)。
### 已修
| # | 严重度 | 页面 | 截图 | 现象 | 提交 |
| --- | --- | --- | --- | --- | --- |
| 1 | 高 | 设置·账户与安全 | `02-pwd-wrong-current-d.png` → `V01-pwd-wrong-current-d.png` | 改密时旧密码填错,服务端回 401,全局 401 处理当成会话失效直接跳登录页;开启二次验证填错验证码同样 | `0c0907a` |
| 2 | 中 | 下载详情·手动整理 | `45-dl-organize-preview-d.png`、`45-dl-organize-exec-d.png` | 预览显示「无法生成计划:source_size_mismatch」时「执行」仍可点,执行后生成一条「未关联作品 0/0」的失败整理记录 | `d7eb6a4` |
| 3 | 中 | 来源健康 / 站点详情(手机) | `M-sources-m.png` → `V12-sources-m.png` | Feed 表名称列 320px,健康、启停、立即抓取被挤出屏幕,只能横滑发现 | `d7eb6a4` |
| 4 | 中 | 收录策略·新建草案 | `40-policy-new-d.png`、`40-rules-err.png` | 策略键只有占位符 `default`,看起来像已填;保存只弹「表单有错误」 | `46d37d8`(预填 default) |
| 5 | 中 | 策略版本、订阅、待处理、业务任务、整理记录、回收站 | `40-policy-published-d.png`、`87-task-retried-d.png`、`64-trash-d.png` | 操作者显示 `admin_session:<uuid>`、`user:<uuid>`、`system` | `46d37d8` |
| 6 | 中 | 下载队列、首页缓存水位 | `42-downloads-d.png` → `V04-downloads-volume-d.png` | 没填缓存卷标识时显示 `instance:<uuid>` | `46d37d8`(显示下载器名) |
| 7 | 中 | 整理记录详情 | `43-organize_4bedfb53-…-d.png` → `V03-organize-reason-d.png` | 失败原因 `blocked:no_main_video(blocked)` | `46d37d8`(取冲突说明「没有可入库的主视频」) |
| 8 | 中 | 媒体库详情·版本目标 | `47-library_…-d.png` | 原因显示 `blocked:no_main_video`、`deferred:slot_busy;inflight:<uuid>` | `46d37d8` |
| 9 | 低 | 下载详情 | `44-downloads_…-d.png` | 时间线「提交下载:succeeded」「复制:failed」,用途 `upgrade`,说明 `added` | `46d37d8` |
| 10 | 低 | 下载计划 | `M-downloads_plans-m.png` → `V07-plans-d.png` | 「优先级 new_work / upgrade」,说明 `organize:succeeded` | `46d37d8` |
| 11 | 低 | 审计日志 | `M-audit-m.png` → `V08-audit-d.png` | `capability.invoke`、`files.*`、`site.*`、`feed_source.created` 等无中文 | `46d37d8` |
| 12 | 低 | 待处理·待识别 | `42-inbox-d.png` | 摘要「没有匹配的作品(no_viable_candidate)」 | `46d37d8`(新开事项生效) |
| 13 | 低 | 设置·Agent 上游测试 | `72-custom-completion-fail-d.png` | 端点不存在时显示「失败 · Connection error.」,没归到「网络错误」也没说怎么办;凭证来源 `stored credential` | `46d37d8` |
| 14 | 低 | 手动整理预览 | `46-dl-organize-preview-ok-d.png` | 徽章 `copy` / `transfer`、文件类型 `video` | `d7eb6a4` |
| 15 | 低 | 站点详情·库存叠加 | `M-sites_…-m.png` | 候选标签 `1080p:deferred` | `46d37d8` |
| 16 | 低 | 全站弹窗 / 抽屉 | — | 关闭按钮读屏文本 `Close`、面包屑 `More` | `46d37d8` |
| 17 | 低 | 首页·最近产出 | `92-home-after-restart-3s-d.png` | 无作品的记录显示「未命名」,与整理记录页「未关联作品」不一致 | `46d37d8` |
### 未修
| # | 严重度 | 页面 | 截图 | 现象 | 原因 |
| --- | --- | --- | --- | --- | --- |
| U1 | 中 | 媒体库详情 | `47-library_f020597a-…-d.png`、`V09-library-slot-d.png` | 自动整理被拦(版本目标进 attention)后,从下载详情手动整理成功入库、资产已核验,版本目标仍「需处理」、当前资产「无」 | 设计取舍:`transitionWithInflight` 对静止态 attention 拒绝 `verify_passed`("可能已被人工处理")。要么放行 attention→verify_passed,要么手动整理成功时自动解决对应「整理冲突」事项,需要定 |
| U2 | 中 | 设置·Agent 上游 | `V11-pi-test-network-hint-d.png` | Base URL 不存在,「测试」仍「通过」并把状态改成「可用」;只有「最小补全」会连端点 | 测试语义设计(只查凭证和模型,不产生费用);建议测试结果不改写可用状态,或给自定义端点做一次 `/models` 探测 |
| U3 | 中 | 来源健康 / 站点详情 | `90-sse-after-event-d.png` | 点「立即抓取」后,抓取任务完成不发 SSE 事件,列表只在请求返回那一刻刷新一次;worker 慢时一直显示旧的「最后成功」 | 需要新事件(如 `source.feed_fetched`)+ 前端失效,超出本轮 |
| U4 | 低 | 收录策略草案 | `40-policy-saved-d.png` | 保存失败只弹「表单有错误」,不指明字段、不滚动到错误处(长表单里看不到) | 需要表单级错误定位,改动面大 |
| U5 | 低 | 设置各分区表单 | `10-empty-settings_agent-d.png` | 带说明文字的字段,说明与下一个字段标签只隔 4px,节奏不齐 | `FormField` 间距属设计取舍 |
| U6 | 低 | 全站(手机更明显) | `M-settings_agent-m.png`、`60-files-d.png` | 右下 Agent FAB 盖住右对齐开关、文件管理页脚「剩余空间」 | FAB 定位设计取舍 |
| U7 | 低 | 下载计划 / 详情 | `44-downloads_…-d.png` | 新作品首次下载的计划用途是「升级」、时间线写「创建下载计划(升级)」 | 业务逻辑待查 |
| U8 | 低 | 站点详情 | `32-site-detail-d.png` | 所有资源都是「H&R 未知」,fixture 里标了 H&R 的也一样 | 适配器 / mock 选择器待查 |
| U9 | 低 | 媒体库详情·元数据 | `47-library_…-d.png` | TMDB 已识别,元数据各字段仍「未提供」;别名重复(沙丘2 ×2、Dune: Part Two ×3) | 数据层待查 |
| U10 | 低 | Agent | `83-agent-run-execute-d.png` | 配了自定义 fake 脚本后,不匹配的目标直接失败「assistant_error: no fake script matched」,内置演示脚本不再兜底 | fake 仅用于演示,行为与 runbook 一致 |
| U11 | 低 | 全站说明文字 | 多处 | 中文句子里混用半角 `,;:` 与全角 `。`(如「停用后不再访问该站点,也不产生新候选;…」「账号:未验证」) | 全站文案规范问题,留给文案轮 |
| U12 | 低 | 文件管理 | `61-files-incoming-d.png` | 单击目录只选中,双击才进入;手机上不便 | 交互设计取舍 |
| U13 | 低 | 文件管理·移动到… | `63-move-preview-d.png` | 目标树点到源所在目录即自动预览,返回 400 并在控制台报错 | 预期校验,但应在前端先拦 |
| U14 | 低 | 账户弹层 | `02-account-menu-d.png` | 浮层透明度高,底下的「实时」「暂停自动化」透出 | 材质设计取舍 |
| U15 | 低 | 待处理(手机) | `M-inbox-m.png` | 指标卡说明「待处理 + 调查中 + 等你决定」被截断 | 文案可缩短 |
| U16 | 低 | 身份指定弹窗 | `50-inbox-assign-dialog-d.png` | 类型用原生 `<select>`,输入框只有占位符没有标签 | 组件统一待做 |
| U17 | 低 | 健康检查 | — | `v0.2.0-rc` 镜像 `/health` 报 `version: 0.1.0` | 当前 main 已有 `chore(release): v0.2.0`,下次构建复核 |
## 文案问题清单
| 位置 | 现状 | 建议 |
| --- | --- | --- |
| 下载详情·计划 | 用途 `upgrade`、说明 `added` | 已修:升级 / 已添加到下载器 |
| 业务任务·触发 | `user:<uuid>` | 已修:管理员 |
| 上游测试 | `Connection error.` | 已修:「连不上上游,检查 Base URL、端口和网络。原始错误:…」 |
| 元数据保存 toast | 「TMDB已保存」 | 「TMDB 已保存」(中英文之间空格) |
| 设置·API Key | 分区标题「API Key / 签发给外部程序的 Key」下小节又是「API Key / 签发给外部程序(…)的 Key」 | 删掉一处 |
| 定时任务 | 「每 168 小时」 | 「每 7 天」 |
| 定时任务·备份 | 「目前由 scripts/backup.sh 在宿主机执行」 | 与 runbook 的 `umt backup` 对齐 |
| 初始化页 | 字段叫「初始化口令」,服务端报错「setup token 无效」 | 统一一个词 |
| 待处理指标 | 「最高 提示」 | 「最高级别:提示」 |
| 版本目标 | 「延后(单元在途)」 | 「延后:已有计划在途」 |
| 整理记录详情 | 「整理后 · 库根相对 / 未知或未授权」 | 失败记录直接写「未生成目标路径」 |
| Agent 上游列表 | 模型列「provider 默认」 | 「默认模型」 |
| 全站 | 半角 `,;:` 混在中文句子里 | 见 U11 |
## CI 与并行开发
- 检查期间 main 上有另一路开发在提交(W9 站点中心、命名模板等),工作区还有大量未提交改动。当前 main 的 `deno task ci` 在本轮之前就是红的:
`instances-routes.ts` 的 `SiteCredentialsPut` 类型错误、`release-fields.ts` 缺 `challenge`、`api-registry` 端点未实现等,都不在本轮改动里。
- 本轮只提交自己的改动,用临时 index 按块提交(`organize.ts` 里只含本轮 7 行,对方未提交的命名改动未带入),提交后把真实 index 对应路径同步到新 HEAD。
- 验证:在 `83498f9`(本轮开始时的 HEAD)+ 本轮改动的独立 worktree 跑 `deno task ci`:typecheck、lint、check:tokens、build 全过;
测试 server 275 通过,2 个文件因与并行开发共用开发库(55433)的 schema 被对方删掉而失败,单独重跑通过;tasks 的崩溃恢复用例同理重跑通过。
用这份代码构建的 `uhd-media-tool:qa` 在 QA 栈复测了 #1–#17。
## 值得一看的 5 张截图
1. `apps/web/test/screenshots/qa-v02/47-library_f020597a-d1f5-4ca0-8d16-81b974ad1960-d.png`:手动整理成功后版本目标仍「需处理」(U1)
2. `apps/web/test/screenshots/qa-v02/83-agent-run-spawn-d.png`:fake 模型派生子会话 + await,会话树合计与系统核实
3. `apps/web/test/screenshots/qa-v02/66-organize-wizard-1-d.png`:批量整理向导识别步骤(整季包 + 电影 + 待选作品)
4. `apps/web/test/screenshots/qa-v02/45-dl-organize-exec-d.png`:预览失败仍能执行产生的失败记录(#2,已修)
5. `apps/web/test/screenshots/qa-v02/V11-pi-test-network-hint-d.png`:不存在的端点「测试」通过、状态变「可用」(U2)
## W10-pre 后端复核(未修项状态)
2026-09-28。只改后端;前端项留给前端波。验证:HEAD + 本轮改动的独立 worktree 跑 `deno task ci`。
| # | 归属 | 状态 | 说明 |
| --- | --- | --- | --- |
| U1 | 后端 | 已修 | 协调者拍板:状态机新增 `manual_verify`(attention → verifying,带原因)。下载任务的整理成功入库时,Slot 若是 attention 先回核验,再由 `verify_passed` 按实际入库资产落 satisfied / empty(部分覆盖);同一下载任务的「整理冲突 / 做种义务阻止移动」事项(`organize_blocked:<下载任务>`)自动 resolved,`resolution = { key: "manual_organize", organizationRecordId, by }`,自动重试成功记 `organize_retry`。纵切测试覆盖 |
| U2 | 后端 | 已修 | 自定义 Base URL(非 OAuth)的「测试」只核对凭证和模型,不再把状态改成「可用」、不记核验时间,返回提示「没有连接 Base URL;用「最小补全」确认端点可达」。前端直接显示 `message` 即可 |
| U3 | 后端 + 前端 | 未修,需定 | 根因比报告写的大:`ctx.outbox.emit` 写的事件(`feed.fetched`、`decision.*`、`download.status_changed`、`organization.changed` 等)只进 outbox 路由成任务,**没有任何一条镜像到 SSE 总线**;浏览器只收得到直接 `ctx.events.publish` 的事件(agent、settings、files、schedule、heartbeat)。首页订阅的这些类型实际从未到达。要补一个 outbox → SSE 的桥(提交后可见、顺序与裁剪策略),属于设计改动,超出本轮。桥接好后前端来源健康 / 站点详情订阅 `feed.fetched` 失效列表 |
| U4 | 前端 | 未修 | 表单级错误定位 |
| U5 | 前端 | 未修 | `FormField` 间距 |
| U6 | 前端 | 未修 | FAB 定位 |
| U7 | 后端 | 已修 | 电影 Slot 缺口的 mode 仍记 `upgrade`(`determineMode` 的 slot_gap),但计划用途只在 Slot 已有当前资产时才是「升级」,否则「补缺」;时间线随之显示「创建下载计划(补缺)」 |
| U8 | 后端 | 不改 | 观众站定义(照搬 MP 的 `audiences.json`)没有 `hr` 选择器,H&R 三态按设计为 unknown;mock 页面上的 `hitandrun` 标记不会被读取。要补选择器需先在真实站点核对 |
| U9 | 后端 | 已修 | 按 TMDB 建档(自动识别 / 手动指定 / 按候选建)时同事务写入 TMDB 字段并投影,不再等下一次刷新(不记 `last_metadata_refresh_at`,剧集季总集数仍由首次刷新补);别名按规范化文本去重(TMDB 译名/别名列表与主标题、原名重复的不再登记,站点标题与已有别名同文本时也不登记)。已有数据里的重复别名不回溯清理 |
| U10 | 后端 | 不改 | fake 仅用于演示,行为与 runbook 一致 |
| U11 | 前端 / 文案 | 未修 | 全站标点规范 |
| U12 | 前端 | 未修 | 文件管理单击进入 |
| U13 | 前端 | 未修 | 移动目标树先拦「目标 = 源所在目录」 |
| U14 | 前端 | 未修 | 浮层材质 |
| U15 | 前端 | 未修 | 指标卡文案缩短 |
| U16 | 前端 | 未修 | 身份指定弹窗 select / 标签 |
| U17 | 后端 | 已核 | `/health` 与 `/system/health` 都读 `apps/server/deno.json`,当前为 0.2.0;rc 镜像构建早于版本号提交,下次构建复核 |
文案清单里的后端项已改:服务端初始化报错改为「初始化口令无效…」(与页面字段同词);定时任务「备份」说明改为「目前用 umt backup(宿主机 cron 调用)」。其余文案(TMDB 空格、API Key 重复小节、「每 7 天」、「最高级别:提示」、「延后:已有计划在途」、失败记录「未生成目标路径」、「默认模型」)在前端。
其他:`packages/db/test/schema.test.ts` 补 `site_user_data`(0031),迁移往返用例超时放宽到 60s(35 个迁移 down/up 在共用开发库上超过默认 5s);`db-sync` 在 HEAD 上已齐。迁移 0031–0035 连续无冲突(0017 历史上空缺,迁移器按文件名排序,不影响);0035 仍是 W9-E 未提交改动,`types.ts` 已含其列。
前端联调追加的两条站点中心后端 bug(同一提交修):
- `PUT /sites/{id}/credentials` 更新已有账号凭证 500:secret 名 `site_account:<label>` 撞 `secret.name` 唯一约束(各站点都有 `default`,换凭证时新 secret 与旧 secret 同名)。改为同账号同类凭证(主凭证 / passkey / TOTP)原地 `replace`,需要新建时名字带站点 id 与随机后缀;会话 Cookie、`POST /site-accounts` 同样处理。测试夹具的 fake secret 现在按 label 落 `name`,能复现唯一约束。
- 馒头刷新登录 "Invalid URL":用户数据解析器在没有 `config.apiBaseUrl` 时按站点地址推 `https://api.<根域>`,站点地址是 IP / 本地 mock 时拼成非法 URL。改为与适配器一致:站点配置 > 定义里的 `apiBaseUrl`(`https://api.m-team.cc`)。
## W10 前端复核(未修项终态)
2026-09-28。前端项与 U3 在 W10 集成里处理,提交见 `docs/plan/08-v0.2-closeout.md`"W10 集成记录"。
| # | 状态 | 说明 |
| --- | --- | --- |
| U3 | 已修 | OutboxRelay 新增 `mirror`:`feed.*`(除 `feed.due`)、`decision.*`、`download.*`、`organization.*`、`subscription.*` 投递成功、认领事务提交后按顺序发到 SSE 总线,payload 原样(带业务 id);只镜像不路由的类型也会被认领并标记已投递(以前 `feed.fetched`、`organization.changed` 等一直停在 pending)。Feed 表收到 `feed.fetched` / `feed.health_changed` 刷新,任务流多听 Feed 与待处理事件。目前没有 `subscription.*` 事件 |
| U4 | 已修 | `lib/form-errors.ts`:保存失败 toast 写出第一处问题,滚到第一个 `aria-invalid` / `role=alert` 并聚焦;用在收录策略草案、设置分节、实例抽屉、订阅表单 |
| U5 | 已修 | `FormField` 加 4px 底距,说明文字与下一个标签至少隔 8px |
| U6 | 部分 | 页面底部原已留 `pb-24`;文件管理状态栏的"剩余空间"改为靠左。滚动途中悬浮按钮仍会暂时盖住右侧控件,属悬浮按钮的定位取舍 |
| U11 | 不改 | 代码与文档自 W1 起统一用半角 `,;:` + 句末全角 `。`,改成全角要先定全站规范并整体替换(数千条),单改几处只会更乱。留给文案规范决定 |
| U12 | 已修(FE-4) | 窄屏单击目录直接进入、长按多选;桌面保持单击选中、双击进入 |
| U13 | 已修 | `transferTargetProblem`:目标是源所在目录或在源目录之内时,移动 / 复制对话框直接提示,不发预览 |
| U14 | 已修 | 账户弹层用近实底材质 `pg-float-solid` |
| U15 | 已修 | 指标卡说明改为「含调查中、等你决定」 |
| U16 | 已修 | 手动指定 TMDB 的弹窗改用共享 Select,类型 / TMDB ID / 备注都有标签 |
文案清单前端项已改:「TMDB 已保存」等 toast 的中英文空格、API Key 小节标题改「已签发的 Key」、整天间隔显示「每 7 天」、「最高级别:提示」、「延后:已有计划在途」、失败记录的整理后路径显示「未生成目标路径」、上游模型列「默认模型」。
## v0.2.1 C1-C(U6 / U11 收尾)
| # | 状态 | 说明 |
| --- | --- | --- |
| U6 | 已修 | 悬浮按钮向下滚动时滑出右侧,向上滚动、滚到页底或回到顶部附近时回来(只看高度 ≥ 半屏的页面级滚动区域);键盘聚焦时照常显示。静止在页中时仍在右下角,底部 `pb-24` 与状态栏靠左保持不变 |
| U11 | 已修 | `docs/design/glossary.md`「标点」定 10 条规范;`scripts/check-copy-punctuation.mjs` 检查字符串字面量 / 模板 / JSX 文本(不看注释、类型、对象键、匹配与比较用的字符串),接入 `deno task ci`(`check:copy`,warn 模式);`--fix` 已对 web、server、contracts 执行。订阅、站点、浏览器、Agent、认证/账户、库存相关文件当时有并行改动,未改,留给各自收尾时 `--fix` |
-106
View File
@@ -1,106 +0,0 @@
# v0.2.1 收尾波独立检查
2026-09-28。对象:C1-A~E、AG-1、AG-2 合入 main 后的状态(基线 `cd70a69`)。检查者独占工作区。
## 结论
七路成果在 main 上可用,自动化测试与实机走查都能对上交付项。检查中发现并修复 5 项(其中 1 项是 HEAD 上 `deno task lint` 本身就不过),
另记录 6 项未修(都不阻塞发布)。修复后 `deno task ci` 连跑 2 次全绿(协调者要求加速,第 3 次中止),前端冒烟 69/69 通过。文案标点违规数为 0,`check:copy` 改为 `--strict`。
## 提交
| 提交 | 内容 |
| --- | --- |
| `410d4ee` | 工作区遗留的排序改动:`api.ts` 的 `export * from "./mcp.ts"` 排序(**HEAD 上 biome organizeImports 报错,ci 的 lint 一步不过**);`types.ts` 与 `deno task gen:types` 输出一致(开发库迁到 0040 后重新生成,逐字相同);`deno.json` imports 按字母排序。`deno.lock` 的改动(`@peculiar/asn1-*` 2.9.5→2.10.0、多出 `npm:vite@*`)是重新解析产生的,HEAD 的 lock 用 `deno install --frozen` 验证一致,已丢弃 |
| `0fccfe2` | 设置页左侧锚点补齐:Agent 分区加「MCP 服务端」「能力清单」,账户分区加「Passkey」(`SETTINGS_SECTIONS.anchors`) |
| `fb84a71` | 标点:剩余 828 处违规里 602 个字面量由 `--fix` 自动修正(74 个文件,只动字符串字面量 / JSX 文本),biome 重排 9 个文件;e2 / e6 断言跟随全角汇报文本;`check:copy` 改 `--strict`,ci 里有违规即失败 |
| `57346d4` | Agent 会话:挂起的调用(方案选择 / 审批)恢复重放时 `tool_call_start` 用同一 `toolCallId`,`foldEvents` 重复出卡 → React 重复 key 报错,工具调用计数也多 1(界面 6、列表 5)。改为沿用原卡片,补单测;汇报脚注的 `"已提交"` 改 `「已提交」` |
## CI 与冒烟
本机串行,HEAD `c1c8dc8`(代码同 `57346d4`,其后只有文档提交):
| 次 | 结果 | 耗时 |
| --- | --- | --- |
| 1 | 全绿 | 448 秒 |
| 2 | 全绿 | 453 秒 |
| 3 | 未跑完(按协调者要求中止) | — |
用例数(两次相同):server 69 文件 / 419、adapters 310(跳过 3)、agent 38、contracts 858、db 13、domain 582、mapping 22、tasks 17、testkit 4、web 99。
检查过程中的试跑:改标点后第一次 ci 因 biome 行宽与 3 个 e2e 断言(半角汇报文本)失败,已在 `fb84a71` 修掉。
前端冒烟:后端 `PORT=4412`(开发库 55433)+ web dev `--port 5202`,`deno task test:web:smoke` **69/69 通过**,跳过 7 条详情页(库里无站点 / 作品)。
## 逐路核验
| 路 | 交付项 | 证据 | 结论 |
| --- | --- | --- | --- |
| C1-A | 私有规则组、画质字段(MP 分辨率 / 来源 / 特效)、保存路径 / 下载器、关键词搜索、重置、日历 | `test/subscriptions/subscriptions-c1a.test.ts`(12)、`subscriptions-c1a-sources.test.ts`(2);走查:订阅总览 / 列表 / 日历视图、新建表单、详情页「订阅操作 → 重置」确认框与执行 | 通过 |
| C1-B | `agent.await` / `agent.spawn{await}` 挂起不占槽 | `e11-subagents`(`await_wake`、`run_started` resumed) | 通过 |
| C1-B | TOTP 二维码 + 恢复码 | `auth-settings/account-security.test.ts`、`login.test.ts`;走查:begin → enable(返回 8 个恢复码)→ 新会话密码登录出现 TOTP 步骤 → 输码登录 → disable | 通过 |
| C1-B | Passkey | 同上(`soft-authenticator.ts`);走查:CDP 虚拟认证器在设置页登记 → 登出 → 登录页出现「用 Passkey 登录」→ 登录成功 → 删除 | 通过。登录页只在已登记 Passkey 时显示入口(设计如此) |
| C1-B | 主密钥轮换 | `auth-settings/rotate-master-key.test.ts`;runbook `auth.md`「主密钥轮换」 | 通过 |
| C1-C | 契约 G6–G8、identify S-7 字段 | `file-manager/g678.test.ts`、`identify-organize.test.ts`、contracts `schemas.test.ts` | 通过 |
| C1-C | DataTable groupBy | `web/components/console/data-table.test.ts` | 通过 |
| C1-C | U6 悬浮按钮滚动让位 | 走查截图未见遮挡(未专门录滚动过程) | 通过 |
| C1-C | 标点全站执行 | 本轮修完剩余 828 处,0 违规,已切 strict | 已修 |
| C1-D | media-servers 并行 | `inventory/media-servers.test.ts`(并行、慢实例超时、partial) | 通过 |
| C1-D | identify 远端查询入队 + SSE | `identify-organize.test.ts`「缺省入队远端查询…发 identify.completed」 | 通过 |
| C1-D | 清理脚本 dry-run | 实跑 `scripts/dev-db-clean.ts`:dry-run 列出走查留下的 `qa-chk` 订阅 3 / 站点 1 且不改动;`--yes` 删除 174 个残留 schema 与这 4 行 | 通过 |
| C1-E | camofox BrowserProvider、会话 / 租约 | adapters `browser/browser.test.ts`、`interact.test.ts`;server `browser/__tests__/browser-api.test.ts`(9)、`browser-e2e.test.ts` | 通过 |
| C1-E | 原站视图 | 走查(mock camofox 19377):打开会话 → 画面 → 接管 → 地址栏跳转(被重定向到登录页,符合 mock 站点)→ 交还;「画面与输入未对真实实例核验」标记显示 | 通过,见问题 #3 |
| C1-E | 下载走 acquisition-plans | `routes.ts` 下载转调 `POST /acquisition-plans`(同一鉴权 / 去重 / 授权);`browser-e2e.test.ts` | 通过 |
| C1-E | 能力 25 | C1-E 时为 25;AG-2 加 `agent.plan` / `agent.propose_options` 后 `GET /capabilities/tools` 为 27,MCP 可见 read 9 / organize.execute 18 / admin 23,与 `mcp.md` 一致 | 通过 |
| AG-1 | MCP 端点鉴权与 scope、会话绑定、停用 / 允许列表、无授权拒绝 | `test/mcp/mcp.test.ts`(官方 SDK client 实连) | 通过 |
| AG-1 | stats / tools 端点 | 同上;走查 `GET /capabilities/stats` 200、`/capabilities/tools` 27 项、`/mcp/status` 200 | 通过 |
| AG-1 | 设置 → Agent → 工具与 MCP、IO 抽屉 | 走查设置 Agent 分区:MCP 服务端(开关、端点、接入配置)、能力清单表格 28 行(API 27 项) | 通过;左侧锚点已补(`0fccfe2`) |
| AG-2 | mode 移除与兼容 | `e12-interaction`「旧 mode=plan_only 按先看方案处理」 | 通过 |
| AG-2 | plan / propose_options 事件与 UI | `e12-interaction`、contracts `agent-events.test.ts`;走查:fake 脚本 → 步骤 1/3 实时勾选 → 方案卡(REMUX / WEB-DL / 都不要)→ 选定后续跑到 3/3、已完成 | 通过;修了重复 key(`57346d4`) |
| AG-2 | 内联审批 | `e12-interaction`(管理员会话批准后重放、审批 consumed;拒绝写待处理);UI 用 `/console/agent?mock=1` 看审批卡 | 通过。开发库没有版本目标 / 资源,未在实库上走真实审批 |
| AG-2 | E12 | `apps/server/test/e2e/e12-interaction.test.ts` 每次 ci 通过 | 通过 |
## 实机走查
脚本 `apps/web/test/screenshots/chk-v021/_walk.mjs`(Playwright,chromium headless,1440×900),截图与 `report.json` 在
`apps/web/test/screenshots/chk-v021/`(不进 git,共 58 张 + 冒烟 `smoke/`)。
走过:登录页 / 密码登录 / TOTP 步骤 / Passkey 登录;设置 9 区 + Agent 的 MCP 与能力清单锚点;站点列表、添加向导、站点详情(概览 / 账号 / 资源 / 快照)、原站视图;
来源健康;订阅(空态、新建表单、总览、列表、日历、详情、重置);规则页 5 个页签;文件管理(真实目录、演示数据、整理向导与推荐名、回收站);
任务页(全部、计划 Tab、详情);Agent 会话(fake 步骤 + 方案选择、`?mock=1`、列表);运行状态;⌘K 命令面板;搜索结果页。
- 控制台错误:修复前 Agent 会话选定方案后 4–6 条 React 重复 key;修复后 0。
- 失败请求:原站视图交还后 1 次 `409 GET /browser/sessions/{id}/screenshot`(问题 #3)。其余 0。
- 横向溢出:0。
- 走查临时数据(站点、订阅、浏览器服务行、Passkey、TOTP、`agent.fakeScript`)已清理或还原。
## 安全快查
| 端点 | scope / auth | 结论 |
| --- | --- | --- |
| `/browser/providers`、`/browser/sessions/**`(建 / 关 / 导航 / 点击 / 输入 / 滚动 / 选择 / 快照 / 截图 / 接管 / 交还) | admin | 输入类另需调用者持有自己的人工租约 |
| `POST /browser/sessions/{id}/download` | organize.execute | 带 target 需人工租约;提交转调 `/acquisition-plans`,授权与去重同 REST。比其它浏览器端点低一级,见问题 #5 |
| `/api/v1/mcp` | API Key(cookie 会话 403) | 调用走 `capabilities.invoke(source="mcp")`,execute 类要持续授权或有效审批;`GET /mcp/status`、`PATCH /mcp/config` admin |
| `POST /subscriptions/{id}/reset`、`/feed-sources/{id}/reset` | admin | — |
| `/auth/totp*`、`/auth/passkeys*`(管理) | admin + session | API Key 一律 403 |
| `/auth/passkeys/login/*` | public | 与 `PUBLIC_AUTH_PATHS` 一致(api-registry 测试断言) |
| `GET /files/download` | admin + session | — |
| `POST/PATCH /approvals` | admin + session | 会话内审批卡走 `PATCH /approvals/{id}`;能力注册表里没有任何审批类能力,Agent 与 MCP 都不能给自己开审批 |
| `GET /capabilities/stats`、`/capabilities/tools` | read | 只有描述符与计数 |
- 浏览器模块只写 `DATA_DIR/tmp/browser-downloads`(0700 目录 / 0600 文件,24 小时清理),没有媒体库或库根写入;凭证由服务端按 `credential` 注入,请求体不带明文。
- 本波新增 logger 调用 16 处逐条看过:只有 id、计数、错误 message、路径,没有凭证 / token / 明文;`mcp tool call failed` 记 `err` 对象,依赖 logger 的全局遮盖。
## 问题清单
| # | 问题 | 状态 |
| --- | --- | --- |
| 1 | HEAD 上 `api.ts` 导出未排序,`deno task lint` 失败 | 已修 `410d4ee` |
| 2 | 设置页左侧缺 MCP 服务端 / 能力清单 / Passkey 锚点 | 已修 `0fccfe2` |
| 3 | 原站视图交还后第一轮截图轮询 409,状态徽标瞬时显示红色「连接中断」;一次失败就标红偏敏感 | 未修:需要决定是否对单次失败做容忍 / 交还期间暂停轮询 |
| 4 | 能力清单里 `agent.propose_options` 失败率 50%:挂起那次调用在审计里记为失败,重放成功又记一次,统计把正常的挂起算成失败(`agent.spawn{await}` 同理) | 未修:需要定统计口径(挂起单独计或不计) |
| 5 | `POST /browser/sessions/{id}/download` 是 organize.execute,其余浏览器端点 admin。组合下需要先有 admin 建的会话,且带 target 要人工租约,风险有限 | 未修:记录,scope 取舍留给 C1-E 负责人 |
| 6 | 仓库根 `.env` 在 01:35 被换成了 docker compose 用的内容(没有 `DATABASE_URL`、`SECRET_MASTER_KEY`),`deno task gen:types` / `db:migrate` / `dev:server` 直接失败;本轮用环境变量覆盖运行,未改该文件(`umt` 栈可能在用) | 未修:建议把部署 env 放部署目录,根 `.env` 恢复为 `.env.example` 的开发内容 |
| 7 | 开发库缺 3 个迁移,本轮补迁到 0040(`0037_agent_await_passkey`、`0039_capability_stats`、`0040_agent_interaction`) | 已处理 |
| 8 | Agent 会话详情里步骤卡片 sticky,滚动时盖住方案卡上半部分(截图 49) | 未修:视觉取舍 |
| 9 | 自动修正不处理嵌套括号,如 `调查超出预算(工具调用次数超出预算(1))` 仍是半角外括号;检查脚本也不报 | 未修:脚本规则限制 |
| 10 | 会话详情工具调用计数与列表不一致(6 vs 5) | 已修 `57346d4`(同重复 key 根因) |
-313
View File
@@ -1,313 +0,0 @@
# 需求覆盖矩阵与后续计划
> 历史报告:本文核对的是 2026-09-28 的 `57346d4`,其中未做 / 待合并 / 真机验证状态与数字不代表当前版本。2026-10-01 的 v0.3.7 现状、R01–R32 复核及验证边界见 [当前开发情况基线](13-development-baseline.md)。
2026-09-28。对照 REQ v0.7(`../../media_automation_requirements_architecture_v0.7.md`)逐条核对 main HEAD `57346d4` 的实现。证据来自代码 grep、测试文件与已有验收文档(`04`、`07`、`08`、`10-v0.2.1-check.md`),没有跑新的测试。
标记:
- **已做**:代码与自动化测试都在,mock 环境下行为符合 REQ。
- **部分**:只做了其中一段,缺口写在最后一列。
- **未做**:没有对应实现。
- **待真实环境验证**:代码完成,只在 mock(RSS / 站点 / TMDB / qB / Emby / camofox / fake 模型)下验证过,没有连过用户的真实服务。
所有"已做"都隐含一个前提:**没有连过真实 PT 账号、qB、Emby、camofox 和真实模型上游**。下文"待真实环境验证"只标那些核心价值就在真实交互上的条目。
进行中(不在 main,不计入本文统计):`c2a` 分支(工作区 `/opt/uhd_media_tool-c2a`)在做跨进程限流与缓存(PG 令牌桶、`UNLOGGED` 缓存表、TMDB 响应缓存、图片代理,见 `00` §9 最后两条);检查子代理的 `10-v0.2.1-check.md` 尚未提交。
## 1. R01–R32 覆盖矩阵
统计:已做 18,部分 11,未做 0,待真实环境验证 3(R26、R30、R32)。"部分"里 R06、R24、R31 同时也需要真实环境验证。
| 编号 | 需求 | 状态 | 证据 | 缺口 |
| --- | --- | --- | --- | --- |
| R01 | 多个 RSS 并存 | 已做 | `feed_source` / `observation`;`ingest.test`「三个 Feed 看到同一资源只建一条 source_release」「窗口零重叠 → feed.gap_suspected」;`docs/runbook/sites.md` | 没有并发 ingest 的测试(现有用例是顺序抓取) |
| R02 | 综艺正片齐全,附加内容可选 | 部分 | `release-parser` 输出 `contentType`(main / extra / performance_only…)与 `segment`(upper / lower / full);`content_unit.content_type` 列;订阅覆盖矩阵显示分段 | 没有"上 + 下 = 一期正片"的覆盖计算,没有节目级"附加内容全部收录"配置,完整性不按正片覆盖判定 |
| R03 | TMDB 滞后不阻断 | 已做 | 临时季 / 集单元(`resolve.test`「剧集:建 series 作品与临时季/集单元」);总集数账本 0025;`e7-tv-series`「总集数未知,Slot 不判满足」 | 一对多 / 多对一编号关系(`unit_relation`)没有建表 |
| R04 | 按规则收录双版本 | 部分 | 每作品多 Slot、Slot × 单元预留(`e9-continuous-series`);订阅"自动补全其他版本";`require_distinct_asset` 列与表单开关 | `checkDistinctAssets`(`domain/slot/completion.ts`)没有接进服务端,同一文件仍可同时满足两个 Slot |
| R05 | 根据 PT 每日新增自动收录 | 已做 | 策略 `newTitle` 规则与 `new_title` 模式(`rules-api.test`「new_title:not_new_release」);新作品 / 追更 / 升级三种模式(`domain/acquisition/mode.ts`) | 没有"每日汇总"视图 |
| R06 | 部分 PT 需要浏览器 | 部分 | camofox BrowserProvider(`packages/adapters/src/browser`)、会话 / 租约、凭证服务端注入、验证码转待处理(`browser-api.test`、`browser-e2e.test`);`docs/runbook/browser.md` | 站点适配器 HTTP 失败后不会自动切到浏览器(REQ §4.4);只对 mock camofox 验证过 |
| R07 | 保持最终路径结构 | 已做 | `path_binding_active_key`;`organize-move.test`「路径绑定固定:译名变化后新集仍进旧目录」;命名模板对已有绑定只用于新目录(`docs/design/naming-template.md` 样例 24) | — |
| R08 | 不绑定 TMDB 身份 | 已做 | 本地 UUID;`external_identity` (provider, entity_type, external_id) 部分唯一;合并保留 `merged_into_id` 跳转(`resolve.test`「合并重复作品」) | — |
| R09 | 数据库存映射并定期校验 | 部分 | 资产对账 `reconcileLibrary`(哨兵 + 增量 stat,suspected → confirmed);元数据 6 小时刷新;`vertical.test`「资产对账」 | 没有 Identity Reconciler(外部对象存在性 / 类型核对)和 Server Reconciler(本地资产 ↔ Emby);备份恢复只有人工演练 |
| R10 | 必要时 Agent 辅助刮削 | 部分 | `identity.resolve`、`catalog.lookup` 能力;待识别事项自动调查(`e5-auto-investigate`);字段锁 | 没有字段级 `metadata.apply` 能力;NFO / 图片写入返回 `not_implemented`(`file-manager/probe.ts`) |
| R11 | 以 MP / MovieClaw 实现为基线迁移 | 已做 | `docs/porting/`(识别、刮削、转移、订阅、站点框架、Rust 逻辑差分、许可账本 `license-and-provenance.md`);`scripts/mp-sync` 差分脚本 | MP 多文件 / 整季包 / 目标冲突的运行对照(REQ §14.1.1)只到源码层 |
| R12 | Pi Agent 作为推理内核 | 已做 | `@earendil-works/[email protected]`,`AgentRuntimePort`,显式白名单(`agent/test/runtime.test`);ADR-003 | — |
| R13 | 公开 API / MCP | 已做 | 195 个端点定义(`packages/contracts/src/api.ts`)+ OpenAPI;Streamable HTTP MCP(`modules/mcp`,`test/mcp/mcp.test`);27 个能力 | — |
| R14 | qB 标签管理 | 已做 | 提交打管理标签、已有种子接管只补标签(`vertical.test`「already_exists」);`e8-tag-removal` 去标签 / 任务消失 | — |
| R15 | Copy / Move 与缓存清理 | 已做 | `verifyAndImport`、跨卷 Copy→核验→删源、EXDEV 不降级、fsproxy 看门狗;`vertical.test`「缓存清理」「H&R 未满足时 Move 变冲突」 | 硬链接工作流、蓝光原盘整目录未做(REQ 本身不把硬链接作主流程) |
| R16 | 条件选片与 Agent 偏好选片 | 部分 | 确定性 `evaluateAcquisition`、规则组分级优先级与逐级 trace | Agent 主观选片(偏好判断、独立预算、选片理由)没有实现;Agent 只能在会话里按用户委托提交 |
| R17 | 自动执行为主、按操作授权 | 已做 | 策略发布即生成持续授权;服务端授权器(`authorize.ts`);`approvals.test`;e1 / e7 无逐次确认 | — |
| R18 | 旧库分范围接管 | 部分 | 库根 `management_mode`(register_only / managed);资产对账;文件管理可逐项识别整理 | 没有首次全库扫描登记、没有"启用前预览影响"、没有作品级覆盖;`scanIncremental` 出错仍返回 complete |
| R19 | 控制台与 Agent 双入口 | 已做 | 同一能力注册表服务 Web / Agent / MCP;Agent 抽屉带页面上下文;凭证与原站接管走专用 UI | — |
| R20 | 偏好标签编辑与学习 | 部分 | 偏好标签作为规则展示形态,抑制记录(`rules-api.test`、`rule-groups.test`「Agent 不能加回(409)」);修订历史 | "重复反馈调整软排序"的学习没有;标签不参与订阅评估(`CHANGELOG` 0.1.0 已知限制 3) |
| R21 | 下载缓存硬阈值 | 已做 | 按 `cache_volume_key` 汇总多 qB 同卷(`services/volumes.ts`);`vertical.test`「硬阈值……低于恢复水位只恢复阈值暂停项」 | 未纳管任务的占用与不暂停没有测试(AT42) |
| R22 | 跨季综合召回 | 部分 | TMDB + `title_alias` pg_trgm 召回;待识别池可按原始标题检索;`identity_unresolved` 有界复查 | 同名重启、简称变化、特别篇与下一季的区分没有专项逻辑和样本;没有召回率 / 误关联报告 |
| R23 | 整理记录公开接口 | 已做 | `/organization-records`、`/organization-records/changes`(游标,含状态更新);scope `organize.read` / `organize.execute`;`vertical.test`「整理记录 API」 | — |
| R24 | Emby 兼容辅助查询 | 部分 | 多实例并行查询,失败为 unknown(`emby.test`、`inventory/media-servers.test`) | 库存结果不参与收录决策("已有则满足目标");没有 server_mapping 对账;没连过真实 Emby / Jellyfin |
| R25 | 站点缓存优先 | 已做 | 站点独立启停、快照、实时补查并发合并(`sources-identity/api.test`);全局搜索短缓存 | 限流器是进程内的,跨进程令牌桶在 c2a 进行中 |
| R26 | Pi 原生能力配置 | 待真实环境验证 | 上游 / 模型 / 思考档位 / 上下文预算 / 压缩 / 子会话开关;41 个 provider + 8 种订阅登录 | 只有 fake 与 OpenAI 兼容本地端点有自动化覆盖 |
| R27 | 禁止默认自动换源 | 已做 | 无进展只记证据 + 待处理(`downloads.ts`);恢复白名单不含换源(`e6-auto-recover`「换源被拒」) | 用户委托换源没有"替换计划"把新提交与原任务处置绑在一起 |
| R28 | 单管理员、多服务实例 | 已做 | 管理员登录 + TOTP / Passkey;API Key 四档 scope,可撤销;多 qB / 多 Emby / 路径映射 | — |
| R29 | 交互执行模式 | 已做 | 会话默认执行,`agent.plan` 步骤清单、`agent.propose_options`、内联审批、"先给方案"(`e12-interaction`) | — |
| R30 | PT 站点工作台 | 待真实环境验证 | 站点页整理视图:列表 / 搜索 / 详情 / 库存叠加 / 统一下载入口;全局搜索 | 四个主力站没有连过真实账号;观众站定义无 H&R 选择器 |
| R31 | 同会话原站接管 | 部分 | 原站视图:截图轮询 + 坐标点击 / 输入 + 接管租约,交还后强制重读页面(`browser-api.test`) | 画面是 1.5 秒截图轮询,不是实时流;同一标签页人 / Agent 连续操作没在真实 camofox 上核验(`browser.md` §6 清单全空) |
| R32 | 可配置浏览器后端 | 待真实环境验证 | `BrowserProvider` 合同 + camofox 实现;按站点 / 账号隔离 profile | 用户 camofox 的 fork / 版本 / 地址未提供;只实现 camofox 一种 |
## 2. §2.1 五原则
| 原则 | 保障点 | 已知违背或风险 |
| --- | --- | --- |
| 身份≠名字,路径≠外部 ID | `external_identity_accepted_key`(`0005_identity.ts`);本地 UUID 主键;合并留 `merged_into_id`;`resolve.test`「人工纠错映射:旧映射退役留修订」 | domain 匹配仍要求本地候选带 externalId(W1 建议项,未修) |
| 已建立的路径绑定不随元数据重算 | `path_binding_active_key`;命名模板只在首次建绑定时生成目录;`organize-move.test`「路径绑定固定」;文件管理显式 move 才修订绑定(`file-manager/write.test`) | 无 |
| 模型不是业务事实唯一来源 | 服务端授权器忽略模型声称的"已同意"(`e2`「没有授权:download.submit 被拒」);汇报取自整理记录,模型文本标"未经核查";审批只接受管理员会话 | 能力统计把挂起重放算成失败(`10-v0.2.1-check` #4),只影响统计 |
| 失败≠不存在 | 对账库根离线只标 unavailable;TMDB 失败记 `lookup_failed`;Emby 失败为 unknown;文件管理根离线返回 503 不返回空列表(`browse.test`) | `scanIncremental` 出错仍返回 complete(旧库接管会用到,目前服务端未调用) |
| 清理按明确授权 | `assertSafeToDeleteData`(已入库、无失败文件、无引用、保留期与 H&R);只读对账 / 总开关关闭时拒绝自动 execute;回收站代替直接删除,永久删除默认关闭 | 部分文件入库后不整种清理(AT29)只有代码保障,没有测试 |
## 3. §15 分期对照
**结论:当前版本相当于"M0 大部分 + M1 全部(仅 mock 环境)+ M2 约三分之一"。** 不是 M2 首版候选:REQ §15.3 明确 M2 才构成覆盖全部核心痛点的首版,而综艺分段覆盖、双版本组合校验、旧库接管、Agent 选片与规则学习都还没做,四站与浏览器也没连过真实环境。
### M0
| 项 | 状态 | 证据 / 缺口 |
| --- | --- | --- |
| 固定 Deno / Pi / PG / OpenWorkflow 版本与骨架 | 已做 | Deno 2.9.7、pi 0.87.1、openworkflow 0.10.1、PG 16;ADR-002 / 003 / 004 |
| Deno 下 Pi / PG / MCP / OpenWorkflow 实际兼容 | 已做 | `docs/spikes/`,MCP 用官方 SDK 实连(`mcp.test`) |
| Pi 白名单、无隐式扩展 | 已做 | `runtime.test`「未注册工具(bash)被拒」 |
| 执行器崩溃恢复 | 已做 | `tasks/test/crash-recovery`、e4 |
| 流程版本升级 | 部分 | 只在 spike 验证,产品代码没有测试 |
| 提前审批不丢失 | 已做 | `engine.test`「waitForFact 事实先于等待到达不丢失」、`approvals.test` |
| 多 Feed 并发不重复提交 | 部分 | 顺序 ingest 去重 + outbox 并发投递有测试;并发 ingest 没有 |
| 改名与映射修订后路径不变 | 已做 | `organize-move.test`、`resolve.test` |
| MP 多文件 / 整季包 / 目标冲突源码对照 | 部分 | `docs/porting/moviepilot-transfer.md`,只到源码层 |
| 浏览器连用户现有 camofox,验证同会话交接 | 未做(待用户提供) | 只对 mock 验证;linuxserver/chrome / Selkies 适用性未记录 |
| 脱敏真实样本 | 部分 | MP 差分样本(识别 / 规则 / 命名);没有四站真实 RSS 与综艺样本 |
| 自动执行关闭、影子决策 | 已做 | 自动化总开关;`resolve.test`「自动化关闭:证据足够也只提议」 |
### M1
| 项 | 状态 | 证据 / 缺口 |
| --- | --- | --- |
| 观众 + 馒头、多 RSS | 已做(mock) | `api.test`、`mteam-tasks.test`、`ingest.test` |
| qB 标签接管、Copy / Move | 已做 | `vertical.test`、`organize-move.test`、e8 |
| 一个电影目标 + 一部常规剧集 | 已做 | e1 / e2(电影)、e7 / e9(剧集) |
| 整理记录 API、可选 Emby 查询 | 已做 | 见 R23 / R24 |
| 备份、审计、恢复、停用自动化开关 | 部分 | 备份脚本与 `umt backup`;恢复只有人工演练;定时任务里的"备份"是占位 |
| 工作台列表 / 详情 / 库存标注 / 统一下载入口 | 已做(mock) | 站点页、全局搜索、`/search/releases/{siteId}/{rid}/plan` |
| Agent 自然语言目标 → 查库存 / 搜索 / 选片 / 提交 → 持续流程入库 → 真实汇报 | 已做(fake 模型) | e2「有持续授权:……汇报来自整理记录」 |
| 授权内异常调查与恢复 | 已做 | e6「task.inspect → download.recheck → 系统核实已恢复」 |
| 关闭聊天后台继续 | 已做 | e3 |
### M2
| 项 | 状态 |
| --- | --- |
| 综艺内容分类与分段覆盖 | 部分(只到标题解析) |
| 本地临时集账本 | 已做 |
| 独立双版本 | 部分(`require_distinct_asset` 未生效) |
| 新作品 / 追更 / 升级三种规则 | 已做 |
| 浏览器 Adapter、工作台双视图、同会话接管 | 部分(mock;截图轮询) |
| 映射巡检与辅助刮削 | 部分(资产对账、元数据刷新;无身份巡检、无 NFO) |
| 旧库分范围接管 | 未做(只有库根两种模式) |
| 授权规则优化、条件 / Agent 选片 | 部分(只有条件选片;策略预览 = 对最近 200 条已识别资源试算) |
| 缓存硬阈值与清理 | 已做 |
| 偏好标签 | 已做(学习未做) |
| 站点快照缓存 | 已做 |
| 跨季召回与待识别治理 | 部分 |
| 双入口待处理 | 已做(顶栏收件箱 + 首页 + 页面内联) |
| HHClub / CHD 适配 | 待真实环境验证(定义来自 MovieClaw v0.27.0 MIT,有解析单测) |
| 媒体级主动整理 | 已做(文件管理 v2、整理向导、命名模板) |
| 影子模式比较人工选择 | 未做 |
### M3
| 项 | 状态 |
| --- | --- |
| 更多 PT | 部分:23 个站点定义 + MP 专用 spider / 用户信息解析器已移植,均未真实核验 |
| Transmission / Plex | 未做 |
| 历史样本回放、规则迁移 | 未做 |
| Skill 管理 | 未做 |
| CLI | 部分:`umt backup` / `--role export-mappings` 等运维入口,没有业务 CLI |
| MCP 扩展 | 已做(提前到 v0.2.1) |
| 页面提取修复提案 | 未做 |
## 4. §16 AT01–AT80
分类:**测试** = 有自动化测试覆盖通过标准;**部分** = 有测试但只覆盖一段;**手工** = 只能手工验证;**未覆盖** = 没有测试也没有对应实现或手工流程。标 (mock) 的只在 mock 服务上跑过。
统计:测试 44(55%),部分 26(32.5%),手工 2(2.5%),未覆盖 8(10%)。有自动化测试的(测试 + 部分)合计 70 / 80 = 87.5%;完整覆盖率按 55% 计。
| AT | 状态 | 测试 / 说明 |
| --- | --- | --- |
| 01 | 测试 | `ingest.test`「三个 Feed 看到同一资源只建一条 source_release」 |
| 02 | 部分 | e10「按作品选 Slot」;没有两站不同版本分进各自 Slot 的用例 |
| 03 | 测试 | `ingest.test`「窗口零重叠 → feed.gap_suspected,并路由到有预算的补漏快照」 |
| 04 | 部分 | `release-parser.test` 把加更识别为 extra;分段覆盖未实现 |
| 05 | 部分 | `release-parser.test` 会员版样本;节目级规则未实现 |
| 06 | 测试 | `resolve.test`「剧集:建 series 作品与临时季/集单元」、e7 |
| 07 | 测试 | `organize-move.test`「路径绑定固定:译名变化后新集仍进旧目录」 |
| 08 | 测试 | `resolve.test`「人工纠错映射」 |
| 09 | 部分 | e9 多版本延后;没有"两个 Slot 同时缺失分别执行"的专项 |
| 10 | 测试 | `rules-api.test`「new_title:not_new_release」 |
| 11 | 测试 (mock) | `browser-api.test`「租约到期 → 等待处理 + 待处理事项」、`browser-e2e.test` 验证码转接管 |
| 12 | 测试 | e4「download.submit 途中 kill worker → 不重复提交」 |
| 13 | 测试 | `organize-move.test` 跨卷 kill 后 resumed |
| 14 | 测试 | `vertical.test`「资产对账:库根离线只标 unavailable」 |
| 15 | 测试 | `tasks/engine.test` waitForFact 先到;`approvals.test` |
| 16 | 测试 | `approvals.test` 改计划 → 被拒 → 重批 → 只消费一次;`rules-api` 旧授权吊销 |
| 17 | 部分 | `runtime.test` 预算超限;e6 预算超限;没有"模型断网时确定性链继续"的用例 |
| 18 | 未覆盖 | 只有系统提示词约束,没有恶意标题 e2e |
| 19 | 部分 | `vertical.test` 删数据安全检查;硬链接工作流未做 |
| 20 | 手工 | `docs/runbook/backup-restore.md` 演练记录 |
| 21 | 测试 | `resolve.test`「用户锁定字段不被覆盖」(海报锁随 NFO 未做) |
| 22 | 测试 | `emby.test`、`inventory/media-servers.test`「异常实例 unknown」 |
| 23 | 测试 | `vertical.test`「already_exists:……只补管理标签」 |
| 24 | 测试 | e8 去标签 / 任务消失 |
| 25 | 测试 | `vertical.test`「缓存清理」、e8「缓存清理」 |
| 26 | 部分 | `space_insufficient` 事项有代码;没有"全部仍需保留"的专项用例 |
| 27 | 测试 | `organize-move.test` 跨卷 Move resumed |
| 28 | 测试 | `vertical.test`「H&R 未满足时 Move 变冲突」、`organize-guard.test` |
| 29 | 未覆盖 | 代码有文件级结果检查,无测试 |
| 30 | 部分 | 条件选片有测试;Agent 偏好选片未实现 |
| 31 | 部分 | 人工纠错有测试;Agent 规则优化未实现 |
| 32 | 测试 | e1 / e7 发布策略 → 持续授权 → 自动执行 |
| 33 | 未覆盖 | 旧库接管未实现 |
| 34 | 部分 | e7 / e9 持续追更;追新季开关没有测试 |
| 35 | 部分 | `browser-e2e.test` 凭证注入登录 (mock);HTTP → 浏览器自动切换未实现 |
| 36 | 测试 | `rules-api.test` revision 乐观锁 |
| 37 | 测试 | `file-manager/write.test`、`organize-move.test` |
| 38 | 部分 | `domain/slot.test`、`rules-slot.test` 升级档位;没有服务端 4K WEB-DL → 蓝光压制用例 |
| 39 | 部分 | e10「重复评估只留一条候选」;Agent 选片未实现 |
| 40 | 测试 | `rules-api.test`、`rule-groups.test` 抑制记录 |
| 41 | 测试 | `vertical.test`「硬阈值……只恢复阈值暂停项」 |
| 42 | 未覆盖 | 同卷汇总已实现(`volumes.ts`),未纳管任务没有测试 |
| 43 | 未覆盖 | 没有调查整个 qB 实例(含未纳管任务)的能力 |
| 44 | 部分 | `seasonScope` 已实现;来源默认值没有测试 |
| 45 | 部分 | `resolve.test` 本地召回、合并;同名重启 / 简称变化没有样本 |
| 46 | 测试 | `resolve.test`「no_match 进池并开未识别事项」 |
| 47 | 测试 | `ingest.test`「站点关闭后 Feed 不再抓取」、`browser-api.test`「站点停用」 |
| 48 | 测试 | `vertical.test`「changes 游标增量(含既有记录状态更新)」、`w5a2.test` partial |
| 49 | 部分 | Emby unknown 有测试;来源版本未知的专项没有 |
| 50 | 部分 | `download-state.test`「无进展判定」;服务端 e2e 没有 |
| 51 | 部分 | e11 开关关闭时拒绝派生;思考档位能力表达只有 `upstreams.test` |
| 52 | 测试 | e11 预算共享、父中止级联取消 |
| 53 | 测试 | e12「先看方案时执行被拒」 |
| 54 | 部分 | e7 整季包只覆盖实际文件;MP 对照样本没有 |
| 55 | 部分 | `op-ledger`「租约丢失旧 attempt 提交被拒」;流程升级没有 |
| 56 | 部分 | `auth.test` 撤销后 401;恢复与路径改变没有自动化 |
| 57 | 部分 | 站点 mock 列表 / 详情;四站真实页面没有 |
| 58 | 部分 | `vertical.test`「首页、媒体库、库存叠加 API」;Emby 叠加没有 |
| 59 | 测试 | `sources-identity/api.test` 实时补查并发合并、关闭站点 |
| 60 | 部分 | e10 去重、浏览器下载转 `/acquisition-plans`;并发触发没有 |
| 61 | 测试 (mock) | `browser-api.test`「输入类动作要先接管;他人接管中 409」 |
| 62 | 测试 (mock) | `browser-api.test`「交还后需重新读取页面」 |
| 63 | 测试 (mock) | `browser-api.test`「服务重启……标签页标 lost」「站点停用」 |
| 64 | 测试 | `browser-api.test` 快照遮蔽密码与 passkey;`redact-consistency.test` |
| 65 | 测试 (mock) | `browser-api.test` 下载暂存、sha256 / infohash 核验 |
| 66 | 未覆盖 | 不适用:没有接独立 Chrome |
| 67 | 测试 | e2 |
| 68 | 测试 | e5 自动调查、e6 授权内恢复 |
| 69 | 测试 | e3、e4 |
| 70 | 测试 | e2「已提交≠已入库」 |
| 71 | 测试 | e5「重复事件只一个 run」、e6 尝试上限 |
| 72 | 测试 | `mcp.test`「execute Key……有授权后执行并记账本」 |
| 73 | 未覆盖 | 多下载器相同 hash 没有测试 |
| 74 | 未覆盖 | 待处理按 `dedupe_key` 去重,但没有挂载根因聚合 |
| 75 | 手工 | 待处理事项有 dismissed 状态,无测试 |
| 76 | 测试 | `search.test`「两站成功 + 一站超时 → partial」 |
| 77 | 测试 | `w5a2.test` 目录复制 partial;`vertical.test` 文件明细 |
| 78 | 部分 | `agent-mock.test` 生产禁 mock;卡片引用失效没有测试 |
| 79 | 测试 | `sse.test`、`sse-pg.test` resync |
| 80 | 部分 | `logger.test`、`redact-consistency.test`、`runtime.test` 脱敏;"未知用量不填零"没有测试 |
## 5. §17 待落实事项
### 5.1 §17.2 部署配置(界面都已可配,缺的是用户的真实值)
| 项 | 状态 |
| --- | --- |
| HHWEB 所指站点的域名 / 身份 | 待用户提供(目前按 HHClub `hhanclub` 定义处理) |
| 另外 1–2 个站点 | 待用户提供(23 个定义可选) |
| 实例地址与凭据(qB、Emby、站点) | 待用户提供 |
| qB 管理标签 | 可配(每个下载器实例 `managedTag`) |
| 容器路径与共享挂载 | 待用户提供(路径映射有 verify 端点) |
| 现有目录模板 | 可配(命名模板三级覆盖);需用户确认默认模板 |
| 电影 / 剧集 / 综艺的 Slot 作用范围 | 待用户提供 |
| Copy / Move 默认范围 | 可配 |
| H&R / 保留要求及单位 | 可配;观众站缺 H&R 选择器 |
| 缓存阈值 / 恢复水位 | 可配,默认 90% / 85% / 清理目标 80% |
| 新旧媒体保留策略 | 部分:替换时旧文件进回收站,没有独立的旧版本保留策略 |
| NFO / 海报权限 | 未做(写 NFO / 图片未实现) |
| 快照周期 | 可配 |
| Pi 上游、思考档位、上下文 / 压缩预算、子会话开关 | 可配;待用户提供真实上游 |
| camofox 仓库 / 版本 / 地址 / 认证 | 待用户提供 |
| Move 遇未满足 H&R | 已定:409 + 待处理,不自动改 Copy |
| 升级后旧文件何时清理 | 未定,目前进回收站按回收站过期清理 |
| 合并版是否替换既有分段 | 未定(综艺分段未做) |
### 5.2 §17.3 技术准入
| 项 | 状态 |
| --- | --- |
| 业务核心 Deno | 已定 2.9.7 锁版本(ADR-004) |
| PostgreSQL | 已定 PG 16 + Kysely,迁移到 0040;大库规模性能未测 |
| 执行引擎 | 已定 openworkflow 0.10.1(ADR-002);旧流程跨版本升级未做产品测试 |
| Agent | 已定 pi 0.87.1;子会话自研(`agent.spawn` / `agent.await`) |
| 前端 / API | 已定 Hono + React Router 7 SPA,REST / OpenAPI / MCP |
| 浏览器 | camofox 适配完成;fork / 版本 / 画面交接待真实实例 |
| 文件工具 | 静态 ffprobe 9.0.2;真实挂载基准 B1–B3 未跑 |
### 5.3 §17.4 下一阶段产物
| # | 产物 | 状态 |
| --- | --- | --- |
| 1 | 两项目实现迁移对照 | 已做(`docs/porting/`),运行对照未做 |
| 2 | 固定版本兼容原型 | 已做(`docs/spikes/`、ADR) |
| 3 | Agent 发起并跟踪的恢复纵切 | 已做(mock,e2 / e6) |
| 4 | 四站真实脱敏样本集 | 未做,待用户提供账号 |
| 5 | 正式接口与数据迁移草案 | 已做(`packages/contracts`,旧库接入部分未做) |
| 6 | 可观测性合同 | 部分(任务流、时间线、运行状态页;根因聚合与版本覆盖矩阵只做了订阅侧) |
| 7 | PT 工作台原型 | 已做(mock);真实实例核验清单见 `docs/runbook/browser.md` §6 |
## 6. 未做功能清单(按优先级)
估算按一个开发子代理的工作日粗估,不含等待用户提供环境的时间。
| # | 功能 | 实施建议 | 估算 |
| --- | --- | --- | --- |
| 1 | 综艺分段与正片覆盖(REQ §5、R02) | 覆盖计算从"集号"改为"内容单元 × 分段":上 + 下或完整版任一满足一期;节目级策略(附加内容收录、是否等完整版、完整版出现后是否替换分段);待识别池加分段歧义事项;先收集 3–5 个节目的真实标题样本 | 5–8 天 |
| 2 | 四站真实核验(观众 / 馒头 / HHClub / CHD) | 用户给账号后逐站跑列表、详情、下载、促销、H&R、登录过期;补观众 H&R 选择器;结果写 `docs/runbook/sites.md` | 2–3 天(拿到账号后) |
| 3 | camofox 真实实例核验 | 按 `browser.md` §6 六项逐条核验,写 `verified`;决定截图轮询是否够用,不够再评估 VNC / 流式 | 2 天(拿到实例后) |
| 4 | 双版本组合校验(R04) | `checkDistinctAssets` 接进 Slot 落定与计划预留;同一资产满足第二个 Slot 时转 attention | 1–2 天 |
| 5 | 旧库分范围接管(R18、AT33) | 首次扫描任务(续扫游标,复用 `reconcileLibrary` 批处理)→ 识别登记 → 预览影响(作品数、缺口、预计下载量)→ 按库根 / 作品开启;先修 `scanIncremental` 的 partial | 5–7 天 |
| 6 | Emby 库存参与收录决策(R24) | 已确认的外部库存可满足 Slot(需证据与策略开关);Server Reconciler 写 `server_mapping`;真实 Emby / Jellyfin 验证分页与认证 | 3–4 天 |
| 7 | Agent 偏好选片(R16、AT30 / 39) | 策略增加"Agent 判断"层:确定性过滤后把候选交给有界 Agent 任务,独立预算,理由存 `acquisition_plan.evaluation`;硬排除仍由代码执行 | 4–5 天 |
| 8 | 规则学习与样本回放(R20、AT31) | 人工纠错写标注样本;策略新旧版本对历史样本回放,展示新增命中 / 误排除;Agent 发布需额外授权并可回滚 | 5–7 天 |
| 9 | 委托换源的替换计划(R27 例外流程) | 新能力生成"替换计划":新候选 + 原任务处置(保留 / 暂停 / 移除 / 删数据分别授权),一次审批或持续授权覆盖 | 2–3 天 |
| 10 | 备份恢复演练自动化(AT20 / 56) | compose 夜间任务:备份 → 清库 → 恢复 → 只读对账 → 映射行数比对;定时任务"备份"从占位改为真实执行 | 2 天 |
| 11 | Pi 上游真实验证(R26) | 用户提供 1–2 个真实上游(API Key 与一种订阅登录)跑 e2 纵切;Codex `_randomBytes` spike | 1–2 天 |
| 12 | 测试补缺 | AT18 恶意标题 e2e、AT29 部分入库不清理、AT42 未纳管占用、AT73 多下载器同 hash、流程版本升级回归 | 3 天 |
| 13 | NFO / 图片投影(R10) | 遵守字段锁,临时文件 + 原子提交,写入受库根策略控制 | 2–3 天 |
| 14 | 身份巡检与挂载根因聚合(R09、AT74) | Identity Reconciler 周期核对外部对象;同一库根离线的事项按根因合并 | 3 天 |
| 15 | HTTP 失败自动切浏览器(R06、AT35) | 站点适配器在登录失效 / 挑战时按策略转浏览器会话,仍失败再进待处理 | 2–3 天 |
| 16 | 调查整个 qB 实例(AT43) | 只读能力列出实例全部任务(含未纳管),一次性操作不写管理标签 | 1–2 天 |
| 17 | 通知渠道 | REQ 未列为必需;若要做,先做 webhook 出站(待处理新增 / 解决),Telegram 之后 | 2 天 |
| 18 | Transmission / Plex、Skill 管理、业务 CLI | M3 范围,真实需求出现后再排 | — |
说明:任务书里曾列为缺口的"多 qB 缓存卷汇总"已实现(`services/volumes.ts` 按 `cache_volume_key` 汇总),缺的只是未纳管任务的测试(第 12 项)。
## 7. 与 REQ 的有意偏离
| 偏离 | 说明 | 记录位置 |
| --- | --- | --- |
| 运行时先 Node 后改 Deno | REQ 首选 Deno;W0 曾用 Node 24,W1 后迁到 Deno 2.9.7,现已符合 REQ | ADR-001(已取代)、ADR-004 |
| 不引入 Redis / Deno KV | 与 REQ §9.1 一致;限流与短期缓存放 PG(令牌桶、`UNLOGGED` 表),c2a 进行中 | `00` §9 |
| MP 站点定义文件加密不可用,改用 MovieClaw v0.27.0(MIT)定义 | MP `user.sites.v3.bin` 加密;MovieClaw 只取 MIT 版本,之后的附条件许可内容未使用 | `packages/adapters/src/sites/definitions/NOTICE-MovieClaw.md`、`docs/porting/license-and-provenance.md` |
| Agent 取消 plan_only / execute 模式 | REQ §10.6 的"只规划"改为会话内"先给方案"选项与 `agent.plan` 步骤清单;旧 `mode=plan_only` 兼容为 `proposeFirst` | `00` §9、`docs/runbook/agent.md` |
| 子代理自研 | REQ 说不假定 SDK 提供子代理;实现为 `agent.spawn` / `agent.await`,继承授权上限、共享预算 | `docs/runbook/agent.md`、CHANGELOG 0.2.0 |
| 命名模板与 MP 输出的差异 | 10 处:非法字符全变量转全角、空白规整、合集集号范围、扩展名小写、长度截断、安全子集不支持 for / 属性访问、已有绑定目录优先、末段必须有扩展名 | `docs/design/naming-template.md` §5、§10 |
| 识别与 MP 的故意差异 | 125 处 intentional(未知发布组兜底、期号记为 issue 等) | `docs/porting/mp-rust-logic-parity.md` §2.3 |
| 转移行为必须偏离 MP 的点 | 不覆盖、路径绑定优先、跨卷可恢复等 | `docs/porting/moviepilot-transfer.md` §3 |
| OpenWorkflow 面板自研 | 官方面板 Proprietary,只参考信息架构,直查同一 schema;后并入统一任务页 | `docs/design/openworkflow-dashboard-reference.md`、`docs/design/console-ia.md` |
| 原站视图用截图轮询 | REQ 要求同会话可视接管,传输方案待验证;先用截图 + 坐标输入,未冒充实时画面 | `docs/runbook/browser.md` |
| 永久删除开关是环境变量 | 规格原为设置项,改为 `FILES_ALLOW_PERMANENT_DELETE`,需重启生效 | `07` §3.2 |
-188
View File
@@ -1,188 +0,0 @@
# 当前开发情况基线
> 此文保留升级前 v0.3.7 的业务基线,下面版本和数量均属该历史核对。后续依赖升级见 [v0.3.8 升级记录](17-full-upgrade.md),当前 v0.3.9 分类/来源功能部署见 [发布记录](18-organize-release.md)。新一批手动下载、详情页下载、Local 元数据与一次性洗版已接入源码、尚未部署,状态见 [MP 对照 §5.6](15-moviepilot-v3-comparison.md#56-本轮实现与验收未部署)。
本报告用于在继续开发前核对 UHD Media Tool 的实际状态。当前已有收录、下载、整理、刮削和智能体的主流程,也有运行中的部署;需求 v0.7 所要求的完整首版仍有缺口。继续开发前应先确定下一阶段要交付的使用场景和验收边界,不能用版本号、页面数或测试数代替需求验收。
核对日期:2026-10-01,时区 Asia/Shanghai。代码基线:`main`,提交 `8013196e3f5e6a8c4ea0cd26cf77294a82a13ddd`,标签 `v0.3.7`。本轮只梳理现状、运行检查和修正文档,没有修改业务实现、部署服务或用户配置。
## 1 证据和判定口径
需求基线是 [需求与架构 v0.7](../../media_automation_requirements_architecture_v0.7.md),后续范围调整以 [工程规划](00-first-version-plan.md) 中用户确认的追加要求为准。历史验收记录只证明当时的版本和场景;本报告结合当前代码重新判断。
| 证据 | 能证明什么 | 不能据此推断什么 |
| --- | --- | --- |
| 当前代码与装配 | 有实现、已接进正式调用路径 | 用户环境里的全部边界均可用 |
| 当前自动化检查 | 指定测试、类型与构建检查通过 | 所有真实站点、模型、挂载都已验证 |
| 脱敏真实样本回放 | 对这些真实页面或标题的解析符合预期 | 登录、下载和风控在真实账号上都能通过 |
| 历史真机记录 | 指定版本和场景曾通过 | 当前版本已重新做过同样验收 |
| 本轮部署只读检查 | 服务健康、报告版本和迁移状态 | 镜像中的每个文件与源码逐字相同、业务全链路无故障 |
下文「已实现」指当前代码与对应回归覆盖已存在;「部分实现」说明缺哪一段;「未实现」指缺少所要求的完整业务能力;「待核验」是证据缺口,不能直接当作代码缺失。
## 2 工程与部署状态
| 项目 | 本轮核对结果 | 证据 |
| --- | --- | --- |
| 工程形态 | Deno workspace,2 个应用、8 个共享包,模块化单体 | [deno.json](../../deno.json)、[仓库结构](../../README.md) |
| 运行时与前后端 | Deno 2.9.7、TypeScript;React 控制台、Hono API、PostgreSQL 16、OpenWorkflow、Pi | [ADR](../adr/ADR-004-deno-runtime.md)、根依赖清单 |
| 服务装配 | 14 个服务模块,18 个注册任务;最初 9 个任务均由正式模块处理器覆盖,没有落到空壳 | [模块清单](../../apps/server/src/modules/index.ts)、[任务装配](../../apps/server/src/context.ts);本轮调用 `buildTaskRegistry` 核对 |
| 公开合同 | 346 个 REST 端点定义,41 个智能体能力;这是合同数量,不是完整需求的完成比例 | 本轮调用 `listEndpoints()` 和读取 `CAPABILITY_IDS`,来源 [api.ts](../../packages/contracts/src/api.ts)、[capability.ts](../../packages/contracts/src/capability.ts) |
| 站点定义 | 23 个内置定义;定义存在不代表该站真实账号已验收 | [定义加载](../../packages/adapters/src/sites/definitions/index.ts)、[移植账本](../porting/mp-sync-ledger.md) |
| 数据迁移 | 56 个迁移文件,最新编号 `0058_scrape_record`;部署数据库也已应用到该迁移 | [迁移目录](../../packages/db/migrations)、本轮只读查询 `kysely_migration` |
| 当前部署 | 应用、PostgreSQL、浏览器容器健康;应用健康接口报告 v0.3.7、数据库 `ok` | 本轮 `docker ps` 和 `GET /api/v1/health`,2026-10-01 13:02 左右 |
| 部署组成 | 核心应用容器含 API、IO worker、Agent worker 和 web 静态文件;PG、浏览器分别部署,可选 Caddy 提供 HTTPS | [compose](../../docker-compose.yml)、[运行手册](../runbook/deploy.md) |
| 本机附加组件 | 另有代理中继、本地模拟 qB 和开发 PG;不能把模拟 qB 当作真实 PT 下载验收 | [本地模拟说明](../runbook/deploy.md)、本轮容器状态 |
应用版本和迁移编号与代码基线一致。镜像标签没有源码提交号,本轮没有核对构建来源的精确提交;后续发布可补记录构建提交和镜像摘要。
本轮记录的运行应用 image ID 为 `sha256:9d65bf0170f35e685a1ddd9ebfab74088f378138e7c02879488e97b089691629`,对应本机 `uhd-media-tool:latest` 的同一摘要。该摘要只标识运行产物,不补足缺失的源码 revision 标签。
## 3 主流程实际覆盖
| 流程 | 已有行为 | 当前边界 |
| --- | --- | --- |
| 获取资源 | RSS 抓取去重、站点快照、实时搜索、详情缓存、23 个站点定义、账号与健康管理 | 站点实测覆盖不完整;Torznab 索引器仅定义结构 |
| 确认作品 | 标题与副标题解析、本地召回、TMDB、字段锁、人工纠错、待识别池;认不准时升级到详情页 ID | 真实标题样本不能代表全库识别准确率;综艺分类与覆盖仍不完整 |
| 决定收录 | 新作品、追更、升级模式;规则组、多版本目标、分集账本、日历和覆盖矩阵 | 独立双版本组合校验未接入;智能体主观选片与规则学习未实现 |
| 提交和跟踪下载 | 多 qB、管理标签、授权、去重、磁盘水位、下载面板、接管与下载历史 | 未完成四站真实下载全链路验收;默认不会自动换源 |
| 整理入库 | Copy / Move、跨卷恢复、固定路径绑定、命名模板、字幕随行、逐文件结果和回收站 | 不支持整理硬链接、软链接和 Rclone;旧库全量接管不完整 |
| 刮削与展示 | 就地写 NFO、海报、季海报和剧照;任意目录识别与候选选择;整理记录筛选、统计和批量操作 | 与 MP 有明确登记的文件布局差异;不能把刮削成功视作 Emby 已完成索引 |
| 持续自动化 | 持久任务、后台调查与授权内恢复、巡检、审批、预算、子会话、SSE、审计 | 最新提示的真实模型前后对比未跑;自动化成功率与人工介入率缺持续运行基线 |
主流程回归见 [E1 收录入库](../../apps/server/test/e2e/e1-rss-to-library.test.ts)、[E2 智能体委托](../../apps/server/test/e2e/e2-agent-delegate.test.ts)、[E4 崩溃恢复](../../apps/server/test/e2e/e4-crash-recovery.test.ts)、[E6 授权内恢复](../../apps/server/test/e2e/e6-auto-recover.test.ts)、[E7 剧集](../../apps/server/test/e2e/e7-tv-series.test.ts)、[E9 持续追更](../../apps/server/test/e2e/e9-continuous-series.test.ts)。外部系统主要由 mock 或样本回放提供,持久任务与数据库集成使用真实开发 PG。
## 4 需求逐项核对
本表重新核对 R01 至 R32 的主要实现与缺口,不计算整体完成百分比。测试明细以本报告第 7 节为准,真实交互边界以第 6 节为准。
| 需求 | 当前实现状态 | 当前证据与剩余边界 |
| --- | --- | --- |
| R01 多个 RSS | 已实现 | [来源测试](../../apps/server/test/sources-identity)覆盖抓取、窗口和去重;跨 Feed 并发摄取的专项证据仍需单独核对 |
| R02 综艺正片覆盖 | 部分实现 | 能解析内容类型、上下分段;尚无「上+下满足一期」及节目级附加内容策略 |
| R03 TMDB 滞后不阻断 | 已实现主要行为 | 本地临时季集、总集数账本和追更已接入;一对多编号关系未完整建模 |
| R04 独立双版本 | 部分实现 | 多个收录版本目标存在;[checkDistinctAssets](../../packages/domain/src/slot/completion.ts)未被服务端调用,同一资产不能算两个目标的校验未落地 |
| R05 每日新增自动收录 | 已实现 | 新作品、追更、升级模式与规则评估存在;每日汇总视图不是现有交付 |
| R06 浏览器辅助站点 | 部分实现 | camofox、视觉操作、人工接管及 Cloudflare 级联已接入;通用 HTTP 失败转浏览器不完整,真实挑战仍待验证 |
| R07 固定最终路径 | 已实现 | 路径绑定与整理回归保证译名变化不自动改目录;显式文件移动另记修订 |
| R08 本地身份独立 | 已实现 | 本地实体、外部映射、别名、人工锁与合并修订存在 |
| R09 映射与巡检 | 部分实现 | 库根与资产存在性对账存在;外部身份巡检、媒体服务器对账缺完整流程 |
| R10 辅助刮削 | 部分实现 | 智能体辅助识别与元数据补全、NFO/图片写入已做;没有独立字段级 `metadata.apply` 能力,不能因此把刮削写成「未做」 |
| R11 参考项目迁移 | 已实现主要工程 | 有来源账本、MP 差分与偏离登记;文件布局也有运行差分,但不代表所有转移场景均有运行对照 |
| R12 Pi 推理内核 | 已实现 | Pi 封装、能力白名单与有界执行已接入;模型供应商的联网覆盖另算 |
| R13 API 和 MCP | 已实现 | REST、OpenAPI、MCP、鉴权和 SDK 集成测试存在;合同数为 346、能力数为 41,旧文档数字过期 |
| R14 qB 标签纳管 | 已实现 | 提交、接管、去标签后停止管理及保留历史有回归 |
| R15 整理与缓存清理 | 已实现主要行为 | Copy/Move、跨卷、校验、保留期和 H&R 保护;硬链接与 Rclone 不在现有实现范围 |
| R16 条件和智能体选片 | 部分实现 | 确定性规则已做;独立预算下的主观偏好选片未实现,不能用会话内手动委托代替 |
| R17 自动执行与授权 | 已实现 | 发布规则的持续授权、单次审批、服务端权限与自动化总开关有实现和回归 |
| R18 旧库分范围接管 | 部分实现 | 已有库根只登记/纳管模式及逐项整理;缺首次扫描、影响预览与按作品启用 |
| R19 控制台和智能体双入口 | 已实现 | 同一业务能力注册、页面上下文、审批与待处理入口已连接 |
| R20 偏好标签与学习 | 部分实现 | 标签迁入规则组,编辑、抑制与修订存在;旧「标签不参与评估」需按规则串和兼容字段区分,自动学习与样本回放未完成 |
| R21 缓存硬阈值 | 已实现主要行为 | 同卷多 qB 汇总、阈值暂停与恢复水位存在;未纳管占用的专项验收仍待确认 |
| R22 跨季召回与治理 | 部分实现 | 本地别名、季年份、续作判别、待识别池、详情 ID 识别和真实标题差分均有进展;缺全面召回率、误关联和新节目评估 |
| R23 公开整理记录 | 已实现 | 列表、文件结果、游标增量;本版另加统计、删除和批量操作。删除记录不发增量墓碑,见 [整理手册](../runbook/organize.md) |
| R24 Emby 辅助查询 | 部分实现 | 多实例查询、超时与 unknown 状态存在;库存未用于满足收录目标,缺本地资产对账与真实 Emby 验收 |
| R25 站点快照与限流 | 已实现 | 快照和实时补查已做;PG 令牌桶、共享缓存已在 0041/0042 落地,不再是「c2a 待合并」 |
| R26 模型与能力配置 | 已实现,联网覆盖不完整 | 上游、模型、思考、预算、压缩与登录配置存在;有部分真实模型历史记录,41 个 provider 和各订阅登录未逐一实测 |
| R27 不默认自动换源 | 已实现默认边界 | 无进展记录证据,不默认换源;委托换源的「新任务+原任务处置」尚未合成一个替换计划 |
| R28 单管理员与多实例 | 已实现 | 登录、TOTP/Passkey、API Key、多下载器/媒体服务器及路径映射存在 |
| R29 自然语言交互执行 | 已实现 | 步骤清单、方案、内联审批、先给方案和后台续跑有回归 |
| R30 PT 工作台 | 已实现,站点验收不完整 | 搜索、资源、详情、账号、下载入口存在;观众和憨憨有真实样本/局部核对,四站完整验收仍缺 |
| R31 同会话人工接管 | 已实现主要行为 | 截图模式、Webtop/Selkies 同会话桌面、租约、交还与视觉操作已做;真实输入法和客户端兼容性尚未全测 |
| R32 浏览器后端配置 | 已实现一种后端 | camofox 配置与版本核验存在,固定 v1.17.0 有真机记录;其它 fork/版本不能直接沿用核验结果 |
对应运行说明:[身份](../runbook/identity.md)、[订阅](../runbook/subscriptions.md)、[规则](../runbook/rules.md)、[整理](../runbook/organize.md)、[文件](../runbook/files.md)、[智能体](../runbook/agent.md)、[浏览器](../runbook/browser.md)、[缓存](../runbook/cache.md)、[MCP](../runbook/mcp.md)。
## 5 历史状态需要纠正的地方
历史文档按当时的基线保留,不把其中每条「未做」直接搬到当前待办。
| 历史说法 | 当前核对结果 | 证据 |
| --- | --- | --- |
| 所有能力只在 mock 上验证 | 已有浏览器、TMDB、部分模型及短剧补全的真机记录;覆盖仍有限 | [浏览器手册](../runbook/browser.md)、[工程规划后续记录](00-first-version-plan.md)、[身份手册](../runbook/identity.md) |
| 刮削不写 NFO 和图片 | SCR-1/SCR-2 已实现,任意目录可就地刮削 | [刮削服务](../../apps/server/src/modules/acquisition-organize/services/scrape.ts)、[刮削测试](../../apps/server/test/file-manager/scrape.test.ts) |
| 跨进程限流和缓存正在 c2a 分支 | 已合入,PG 令牌桶、UNLOGGED 缓存与图片代理存在 | [缓存手册](../runbook/cache.md)、0041/0042 迁移 |
| 原站视图还是占位或只有截图 | 已有同会话桌面,截图作为回退;camofox v1.17.0 有真机核验 | [浏览器手册](../runbook/browser.md)、[桌面模块](../../apps/server/src/modules/desktop/index.ts) |
| `agent.spawn {await:true}` 一直占 worker 槽 | 当前正常会话驱动支持挂起与恢复,E11 直接验证等待期间槽位空闲 | [子会话实现](../../apps/server/src/modules/agent/subagents.ts)、[E11](../../apps/server/test/e2e/e11-subagents.test.ts) |
| 版本规格只能在订阅侧看 | 作品详情已有分集资产版本与目标比对 | [版本服务](../../apps/server/src/modules/acquisition-organize/services/library-versions.ts) |
| 任何站点 HTTP 失败都不会转浏览器 | Cloudflare 页面挑战已有自动/视觉/人工级联与浏览器代抓;通用失败转浏览器仍不完整 | [Cloudflare 服务](../../apps/server/src/modules/sources-identity/service/cloudflare.ts) |
| 库根离线没有按根因去重 | 整理入口已有 `library_root_offline:<rootId>` 去重;跨业务的统一根因聚合未完整确认 | [整理服务](../../apps/server/src/modules/acquisition-organize/services/organize.ts) |
| 备份恢复完全没做过演练 | 早期临时环境中实际备份、清库、恢复和映射比对通过;当前版本自动定期演练未做 | [部署验证记录](../runbook/deploy.md)、[备份手册](../runbook/backup-restore.md) |
| 看到 tasks 中的 stub 就说明主流程未实现 | 正式装配已覆盖最初 9 个任务,stub 仍作为注册兜底保留 | 本轮比较正式和默认处理器,见第 2 节 |
另有历史问题未确认终态:能力统计可能把正常挂起记成失败、个别浏览器状态提示及跨版本流程恢复。现有统计按审计中的 error/invalid 计失败,不能仅凭旧截图判为已修或仍有故障,需要针对当前版本复现。
## 6 真实环境验证边界
| 对象 | 已找到的证据 | 仍需补的证据 |
| --- | --- | --- |
| TMDB | 工程规划记载真实搜索、识别、订阅季集、海报和缓存验证 | 对下一阶段的固定样本重新验收;不能将历史记录等同于本轮重跑 |
| 浏览器 | v1.17.0 实容器的登录保持、捕获下载、同标签接管与重启;Webtop 原型、视觉点击和插件可信鼠标事件 | 四站真实挑战、输入法组字、客户端浏览器差异;保活、自愈等新变化的实机边界 |
| 憨憨与观众 | 憨憨真实标题差分和局部核对;观众真实脱敏列表/详情样本 | 逐站账号登录、搜索、下载、促销、H&R、过期恢复、文件清单的完整验收 |
| 馒头与 CHD | 定义、适配器和 mock/离线样本测试 | 真实账号全流程证据 |
| qB | 多版本兼容代码、mock 回归、本机运行的 qB 5.1.2 模拟容器 | 用户真实下载目录、挂载与 PT 下载全链路;模拟器可运行不证明真实服务已验收 |
| Emby/Jellyfin | 兼容查询实现和 mock 测试 | 真实认证、分页、库存及资产映射验收 |
| 模型上游 | 规划记载 OpenCode Go/DeepSeek 的部分历史场景;视觉与网页补全有局部真机记录 | AG-5 分层提示的真实前后对比明确未跑;不应声称所有 provider 已实测 |
| 媒体挂载 | 临时文件系统、跨卷和故障恢复回归 | 真实 NFS/FUSE/mergerfs 等规模与故障基准;不能承诺大库性能 |
真实模型前后对比的缺口来自 [评测记录](../design/agent-prompt-eval.md);浏览器核验细节见 [浏览器手册](../runbook/browser.md)。本轮没有连接真实 PT 账号或模型上游,也没有产生下载、刮削或清理用户文件的动作。
## 7 本轮检查结果
同一会话上一轮已完成:全仓 TypeScript 检查通过;domain 21 个文件、642 个测试通过;E1、详情 ID 识别、NFO 与布局差分共 4 个文件、25 个测试通过。
本轮对同一业务代码基线运行完整 `deno task ci`,明确指定开发 PG `127.0.0.1:55433/umt`,集成测试使用临时 schema。检查退出码为 0,完整通过;server 测试于 13:08:06 开始,耗时 538.15 秒,随后其它包检查、构建和编译自检均通过。
| 包 | 通过文件 | 跳过文件 | 通过测试 | 跳过测试 |
| --- | --- | --- | --- | --- |
| server | 122 | 1 | 756 | 1 |
| adapters | 58 | 1 | 459 | 3 |
| agent | 9 | 0 | 82 | 0 |
| contracts | 7 | 0 | 1036 | 0 |
| db | 4 | 0 | 21 | 0 |
| domain | 21 | 0 | 642 | 0 |
| mapping | 2 | 0 | 22 | 0 |
| tasks | 6 | 0 | 17 | 0 |
| testkit | 2 | 0 | 5 | 0 |
| web | 24 | 0 | 224 | 0 |
| 合计 | 255 | 2 | 3264 | 4 |
跳过条件按源码核对:server 的真实模型评测未启用;adapters 的真实 qB 兼容测试没有配置 `QB_REAL`,整个文件未运行;以 root 执行时,三个目录/文件权限拒绝测试主动跳过。真实 qB 文件在没有目标时不生成测试,因此不能仅凭「4 个测试跳过」理解全部外部验证缺口。数据库包与任务包测试均实际执行;server 只有一个文件跳过,没有把 PG 集成文件整体跳过。
非阻断输出:Biome 有一条测试代码 literal-key 提示;web 构建有依赖注释和 sourcemap 提示,构建成功。本轮没有为消除这些提示修改实现。
完整 CI 包含 tsc 与 Deno 类型检查、Biome、各包 Vitest、设计 token 守卫、文案标点、web 构建、server 编译与二进制启动自检。前端全页面 Playwright 冒烟和真实模型评测不包含在普通 CI 内;即使 CI 全绿,也不将这两类算作本轮通过。
本轮原始日志:`/tmp/umt-development-baseline-ci.log`。日志是本机临时证据,最终结果汇总写在本节,不能依赖临时日志长期存在。
完整 CI 后的文档勘误不改变业务代码;文档本身另外核对了本地链接、事实引用和差异格式。本轮没有运行镜像重新构建或前端页面冒烟。
## 8 当前缺口和工程风险
| 项目 | 分类 | 当前判断 | 可验收的交付边界 |
| --- | --- | --- | --- |
| 综艺完整性 | 核心功能缺口 | R02 未完整交付 | 用真实节目样本验证分段组合、正片完整性、附加内容策略和完整版替换 |
| 独立双版本 | 核心功能缺口 | R04 的开关和域层校验未接入服务端 | 同一物理资产不能同时满足要求独立的两个目标,覆盖预留和最终入库 |
| 旧库接管 | 核心功能缺口 | R18 缺完整扫描、预览和分范围启用 | 首扫可续跑、错误不当完成,先预览再按范围开启,既有路径保持稳定 |
| 智能体选片与规则优化 | 核心功能缺口 | R16/R20 缺完整实现 | 在硬规则和授权内做偏好判断,理由可查;反馈、回放、发布和回滚可验收 |
| 外部映射和媒体服务器对账 | 功能缺口 | R09/R24 不完整 | 区分 unknown 与缺失,核对资产映射;是否允许外部库存满足目标需显式策略 |
| 换源替换计划 | 功能缺口 | 新提交与原任务处置尚未绑定 | 一份可审核方案说明两边动作,分别匹配授权并处理部分失败 |
| 备份自动化 | 运维缺口 | 看板 `maintain.backup` 仍 unavailable;脚本和早期手工演练存在 | 当前版定期备份与隔离恢复演练,行数、映射和只读恢复状态可核对 |
| 增量扫描错误语义 | 已确认的代码边界 | [scanIncremental](../../packages/adapters/src/fs/scan.ts)收集目录/stat 错误后仍可能返回 complete;当前服务端未调用 | 接旧库流程前明确 partial/失败及续扫规则;不能把跳过文件算完整扫描 |
| 空壳任务兜底 | 维护风险 | 当前正式任务已覆盖,但遗漏注册会静默回显 todo | 后续新增或重构时核对正式注册与任务版本,不只看默认注册表 |
| 关键故障场景 | 验收证据缺口 | 历史清单中的 AT18、AT29、AT42、AT73 和流程版本升级,尚未逐项重新形成当前验收记录 | 恶意标题、部分入库不清理、未纳管占用、多实例同 hash、旧任务跨版本恢复分别给出证据 |
| 性能与自动化效果 | 验收证据缺口 | 未建立按真实负载统计的持续基线 | 记录规模、挂载、延迟、无人干预完成率、人工介入、误关联、费用与待处理年龄 |
| 文档与发布来源 | 工程管理缺口 | 旧计划、README、覆盖表、部分手册状态互相矛盾;镜像未记源码提交 | 当前基线作为讨论入口,历史报告标清日期;发布记录附构建提交、镜像摘要和验证结果 |
上表不是已批准的开发排期,也不对缺口给出未经验证的固定工期。主观选片、规则学习和现有故障修复应按用户场景确定边界,不能在一次任务中笼统打包为「做完智能体」。
用户确认开展独立检查后,两项中等思考强度的只读代码/验证检查和一项低思考强度的文档检查发现了更具体的恢复与验收边界。见 [工程检查与调整建议](14-engineering-review.md),其中区分代码已确认、实际影响待查和纯证据缺口。
## 9 里程碑判断与下一阶段讨论
按需求 §15 的口径,工程底座和 M1 主流程已有较完整实现,M2 的一部分能力也已交付,但 M0 的部分验收证据和 M2 的核心差异化需求仍不完整。因此不能直接宣称 M0/M1 全部验收完成,也不能继续沿用历史「M2 约三分之一」作为当前精确进度。
下一阶段讨论应基于以下四项材料:本报告中的当前能力、真实环境证据、缺口清单、现有检查结果。先确认目标使用场景,再选择范围:是先稳定现有电影/剧集收录,还是优先完成综艺与独立双版本,或先解决旧库接管。这里列的是待讨论的选择,不自动开工。
建议在恢复功能开发前,为选定范围写清:用户从哪里触发、最终看到什么结果、哪些失败需要调查或人工处理、用哪些固定样本验收、需要哪些真实环境。每项工作完成后更新本报告或后续阶段验收记录,避免再次积累「待合并」和「未做」的过期状态。
本轮仍未确认的事项应继续标为待核验,不据此删减原需求;已做但缺真机证据的能力应优先按场景验收,不重新从零实现。
-107
View File
@@ -1,107 +0,0 @@
# 工程检查与调整建议
本报告回答「继续开发之前是否需要先处理工程问题」。当前版本完整 CI 通过,主流程实现和部署均存在;检查仍发现任务升级、入队与业务记录一致性、故障状态判定以及验收口径需要先厘清。建议先做一轮范围明确的恢复与验收补强,再确定后续功能范围。
日期:2026-10-01。升级前业务代码基线:`main`,`8013196`,`v0.3.7`。开发情况与 R01 至 R32 覆盖见 [当前开发情况基线](13-development-baseline.md)。本轮采用两个中等思考强度的独立代码/验证检查、一个低思考强度的文档检查,由主代理复核和汇总。该检查阶段没有修改业务实现、提交、部署或执行数据修复。后续用户授权的依赖与容器升级执行状态见 [完整升级记录](17-full-upgrade.md)。
后续核查按用户确认先逐项参考 MoviePilot v3 的业务逻辑,再讨论调整。进度见 [MoviePilot v3 逐项业务对照](15-moviepilot-v3-comparison.md):已固定 v3.0.10-1 源码,第一项任务恢复与升级兼容完成源码对照;后续已实施局部恢复修正并做故障注入,见下方当前落实情况。
并行完成的版本检查见 [依赖与程序版本报告](16-dependency-version-review.md):记录 58 个直接依赖、主锁文件和浏览器独立审计、运行程序版本,以及 PG 18/Pi 原生 MCP 的迁移边界;该版本检查阶段没有执行依赖更新或部署。
## 当前落实情况(2026-10-01)
用户讨论后授权的恢复修正已落到源码:引擎接受任务后回填关联失败保留 queued 并记日志;worker 执行时补关联;巡检优先查最新 run 并修复关联,查询失败跳过当前轮次,不误判成不存在补投。刮削去重复用原业务记录,不让回填失败把已接受的任务标成失败。
`enqueue-recovery.test.ts` 在隔离 PostgreSQL schema 中完成 9 个故障测试,包括关联写失败、原 run 修复、已有活跃 run 的查询异常。连同接管、刮削和手动季集覆盖回归,5 个测试文件共 34 个测试通过。发布前旧任务检查补到 [部署手册](../runbook/deploy.md#6-升级)。没有引入新恢复器、任务状态机或永久旧 handler。
后续媒体整理范围经过用户逐项讨论后实现:可编辑 TMDB 分类 YAML、单来源配置、复制/移动、qB 标签接管、首次基线+增量快照、一次性存量向导。运行说明见 [整理手册](../runbook/organize.md)。这轮功能已发布为 v0.3.9,见 [发布记录](18-organize-release.md);下文检查表保留检查阶段的事实和其他仍需讨论的项目。
## 1 结论如何使用
「代码已确认」说明能从当前控制流或配置看到该边界;「影响待复现」说明尚未做故障注入,也未证明用户已经受影响;「验收证据不足」说明已有实现不能用当前证据验收完整要求。下文建议是供讨论的工作范围,不是已执行的修复或已批准的排期。
完整 CI 的 3264 个通过测试能证明现有固定回归通过;不能证明未结束的旧版本任务、线上故障下的双写恢复、真实模型行为或真实媒体挂载已经验收。应为这些具体边界补证据,而不是只扩大通过测试的总数。
### 1.1 用户确认的工程取舍
用户认可任务恢复的调整方向,同时明确本产品面向团队内部,避免过度设计和过度防御。后续优先修复已确认、会影响正常使用的局部问题,复用现有任务引擎、幂等键、巡检和记录页面。MoviePilot 用于参考同一场景的业务处理,不照搬其全部基础设施。
临时查询异常保留日志并等待下一轮,不新增人工审核流程;普通可重试失败使用已有重试,最终失败说明原因并支持现有入口重试。只有删除、覆盖、替换文件等操作出现实际状态不明或冲突时,才停止自动执行并提示处理。没有复现需要之前,不增加通用恢复框架、第二套任务状态机或新的补偿服务。
旧任务兼容先用发布前检查和处置清单解决;没有在途旧任务时,不为假设场景长期维护旧处理器。验证围绕本次改动的失败分支和文件保护展开,T01 至 T06 是场景清单,不作为继续所有开发的统一前置门槛。当前版备份恢复和部署验收已在升级阶段完成,见 [升级记录](17-full-upgrade.md),后续针对实际变更补充验证。
## 2 建议优先核对的恢复边界
### 2.1 刮削任务升级后的旧任务承接
**代码已确认,当前部署未发现未结束的旧刮削任务。** [handlers.ts](../../apps/server/src/modules/acquisition-organize/handlers.ts) 第 193 行起只注册 `scrapeMedia` v2,输入是 `recordId`。旧版本 v1 使用作品 ID 和触发方式等输入。worker 在 [openworkflow-engine.ts](../../packages/tasks/src/openworkflow-engine.ts) 第 221 行起按已注册的任务名和版本过滤认领,当前清单没有 v1。
因此升级前未结束的 v1 run 如果存在,当前 worker 没有对应处理器。0058 迁移还将原 `media_scrape` 表替换为 `scrape_record`,不能简单恢复旧处理器而不核对数据结构。需要明确旧任务应完成、迁移、取消还是以说明终止。
本轮对部署数据库只读聚合:`scrapeMedia|v1|completed|1`,没有其它刮削任务状态;`scrape_record` 没有记录。**这次没有发现受影响的活动旧任务,也没有据此做任何取消或补投。** 这个结论只对应本次检查时点。
最小下一步:把「升级前未结束的任务版本清单及处置」加入发布记录。在隔离环境用上一版本入队、当前版本恢复验证兼容方案,避免只验证同版本崩溃恢复。
### 2.2 入队成功和业务记录关联回填不是一个原子操作
**控制流已确认,故障影响待复现。** [scrape.ts](../../apps/server/src/modules/acquisition-organize/services/scrape.ts) 第 152 行起先插入 `queued` 记录,第 192 行入队,第 207 行回填 `engine_run_id`。入队成功而回填失败,也进入同一个 catch,将记录标为 `failed/task_enqueue_failed`。第 476 行的执行器对 failed 记录直接返回。
可能出现两种不一致:插入后、入队前进程退出,留下没有 run 的 queued 记录;入队已成功、回填失败后记录先被标为 failed,worker 再读取时跳过。实际结果取决于失败时点,本轮没有通过故障注入证明发生概率,也没有把它写成已发生的线上故障。
[common.ts](../../apps/server/src/modules/acquisition-organize/services/common.ts) 第 134 行的 `enqueueTracked` 也依次执行业务行、引擎入队和回填,应一起定义恢复口径。稳定幂等键可以帮助找到已有 run,但本身不等于已完成关联恢复。
最小下一步:在隔离测试中分别注入这三个时点的失败,确认最终状态。再选择恢复回填与补投方案,或复用现有事务 outbox;保持改动集中在这些入口。
### 2.3 引擎查询失败被当成查无任务
**异常处理行为已确认,重复补投的实际影响待复现。** [common.ts](../../apps/server/src/modules/acquisition-organize/services/common.ts) 第 171 行在巡检中使用 `engine.get(...).catch(() => null)`,查询异常和明确不存在共用 null。下一时间槽可能补投新 run,而旧 run 仍在运行,共享同一业务任务行。
下载与文件操作已有底层幂等账本,不能直接推断会重复下载或覆盖文件。但两条 run 竞争业务行、回填关联和状态的风险,需要单独验证。
最小下一步:故障注入中模拟一次引擎查询异常;讨论让查询异常记录并跳过本轮,仅在明确查无 run 或终态时补投。这个边界适合与 2.2 一起检查。
### 2.4 默认空壳可以掩盖正式任务注册遗漏
**维护风险已确认,当前核心任务都有正式处理器。** [context.ts](../../apps/server/src/context.ts) 第 96 行为缺失的核心任务注册 stub;[modules.test.ts](../../apps/server/test/modules.test.ts) 第 86 行主要检查任务名存在。未来如果漏注册真实 handler,名字检查仍可能通过,stub 回显 todo 后可以成功结束。
本轮装配核对确认最初 9 个核心任务均已由业务模块覆盖,不应把当前主流程描述成「还是空壳」。
最小下一步:下一次触及模块或任务注册时,增加完整生产模块装配的处理器来源断言;讨论是否只允许局部测试装配使用 stub。无需因此重写任务系统。
## 3 应先明确的验收口径
| 检查项 | 当前事实 | 需要补的最小证据 |
| --- | --- | --- |
| PG 测试必执行 | 部分 server/tasks harness 在 PG 探测失败时跳过;默认 db 包连接失败会报错,所以不能称默认整条 CI 会在无 PG 时全绿 | 在 CI 汇总中保留跳过清单,讨论严格 PG 模式;区分允许跳过的真实模型和真实 qB |
| 前端冒烟 | 独立于普通 CI;[smoke.mjs](../../apps/web/scripts/smoke.mjs) 第 188 行详情页数据获取失败或为空会 skip,最终主要按失败数退出 | 固定演示数据、必须执行的详情页,以及接口错误和合法空数据的不同判定;本轮没有跑页面冒烟 |
| 模型行为 | E13 有恶意网页清洗、URL 与不可信标注回归,但用固定 FakeScript;AG-5 真实前后评测默认关闭且历史记录明确未跑 | AT18 的真实模型行为单独评测,不用 fake 模型脚本替代;模型调用须在明确范围内进行 |
| 部分入库后的清理 | [downloads.ts](../../apps/server/src/modules/acquisition-organize/services/downloads.ts) 第 1043 行检查最近整理成功并拒绝失败/排队/运行/取消文件,已有保护 | AT29 构造部分成功仍有源文件的反例,核对不会整种清理;不能写成「没有删除保护」 |
| 未纳管占用 | 域层有 `managed:false` 和增长风险断言;服务层现有阈值测试主要使用假磁盘 | AT42 用固定同卷场景核对汇总占用和暂停范围 |
| 多实例同 hash | 多下载器选择有测试,未找到两个实例同 hash 后分别同步、暂停、删除的完整专项断言 | AT73 在 mock qB 的两个实例中核对任务与清理隔离 |
| 备份一致性和恢复 | [backup.sh](../../scripts/backup.sh) 依次 pg_dump、导出映射、归档在线会话,产物不是同一时点;早期临时恢复演练曾通过 | 当前版隔离恢复演练,明确在线变更如何影响映射和会话比对,再决定是否需要一致性改造 |
| 镜像运行与来源 | 编译自检在空环境验证模块图可加载,不能替代最终镜像的进程、工具、静态文件、权限和挂载检查 | 下一次发布记录源码提交和镜像摘要,并做隔离镜像启动验收 |
本轮完整 CI 中 PG 集成实际运行;上述 PG 问题属于后续执行环境变化时的验收可靠性,不是本轮大量跳过集成测试。没有新增或改写测试来掩盖缺口。
## 4 已解决的工程问题与文档调整
已有 cleanup/reconcile 入队、后台任务编排、IO 与 Agent worker 隔离、文件/网络/维护分道、请求限制器、整理 walk 上限、sha256 子进程和操作账本。`GET /inventory/media-servers` 已多实例并行、限制实例内并发并设总超时,旧串行遗留不能继续作为待开发项。
本轮文档调整集中在影响当前判断和操作的内容:建立需求基线与工程检查报告;将早期覆盖表和解耦审计标为历史;校正 README 的能力数、刮削、验证和部署形态;统一当前部署示例的 `.env.deploy`;修正默认桌面镜像构建前提、浏览器内存配置、MCP 工具数、站点详情补请求和识别异步说明。
基线中的所有本地文件链接均另行检查。历史演练表和对应日期、测试数继续保留,避免把新结论改写进旧验收记录。源码中的旧注释与较早手册可能仍有残留,遇到冲突以当前代码和这两份基线报告为准。
## 5 供讨论的下一阶段范围
建议下一阶段先以「任务升级和异常恢复可核验」为范围,交付 2.1 至 2.3 的故障与升级证据、当前恢复演练、明确的测试跳过清单和发布来源记录。2.4 可在同一轮触及装配时补强。这里是建议,尚未修改对应实现或开始功能开发。
然后再确定功能阶段:综艺完整性、独立双版本、旧库接管、智能体选片与规则优化都仍有需求缺口,但应按具体场景分别验收。每阶段要明确触发入口、最终结果、失败和等待处理、固定样本与真实环境需求。
讨论时需要决定的是下一阶段的目标和边界,以及哪些验收证据必须先补。已有授权、稳定路径和清理保护不需要重新设计;尚未复现的问题应先做有针对性的核查,再确定最小实现调整。
## 6 升级核验新增的待讨论证据
2026-10-01 的 1.18 浏览器升级核验已覆盖正常重启、空闲回收、旧元素引用拒绝、登录保持与 SIGKILL 后未落盘登录丢失识别。现有核验脚本原先会捕获错误后返回成功,且仍期待自动重连之前的行为,本次已修正并收紧下载关联和脱敏断言。
精确恢复页面地址仍有证据缺口:核验 a 阶段末尾曾导航回列表,b/idle 恢复观测地址为详情页,缺少 a 末尾的数据库 URL 与最终快照对照,目前不能判定为已复现业务故障。后续应在重启前记录 `BrowserService.view(sid).currentUrl`、`snapshot.url`、`browser_session.current_url` 与当前 `browser_tab.url`,重启后逐一比较;恢复入口见 `apps/server/src/modules/browser/service.ts` 的 `restoreUrl(row.current_url)` 及适配器 `reopenSession`。十项能力核验不等同于精确 URL 恢复已经验收,下一轮业务对照应补这个场景。
-310
View File
@@ -1,310 +0,0 @@
# MoviePilot v3 逐项业务对照
继续开发前,先逐项核对现有业务与 MoviePilot v3 的处理逻辑。每项同时说明业务结果、失败边界、本项目差异和需要补的证据;完成对照后再讨论实现调整。本文是当前对照记录,尚未完成的项目明确列为待核对。
日期:2026-10-01。本项目业务代码基线:`8013196e3f5e6a8c4ea0cd26cf77294a82a13ddd`,`v0.3.7`。前置材料:[开发情况基线](13-development-baseline.md)、[工程检查](14-engineering-review.md)。初始对照阶段只读源码并更新文档;后续用户授权的实现状态见第 4、5 节。
## 1 参考基线和判定方法
| 项目 | 本轮固定基线 |
| --- | --- |
| MoviePilot 仓库 | [jxxghp/MoviePilot](https://github.com/jxxghp/MoviePilot) |
| 参考发布版本 | `v3.0.10-1`,提交 [`0aa857173f77de31c7c8d9d2e12052d99f37bcc1`](https://github.com/jxxghp/MoviePilot/tree/0aa857173f77de31c7c8d9d2e12052d99f37bcc1),2026-09-28;`version.py` 已核对 |
| 分支查询 | 本轮只读查询时 `v3` 分支为 `5741d3c56d761d87868d4f2d0515ac5c498f94d6`;本报告采用上面的发布标签,后续逐项核对不随分支移动 |
| 本地参考位置 | `/tmp/umt-moviepilot-v3-review-20261001`,独立浅克隆,未改上游源码 |
| 旧资料 | [既有移植文档](../porting/moviepilot-transfer.md)使用 `e6820a8`/早期 v3.0.0;原参考仓库保持不变,旧样本结论不直接升级为本轮结论 |
| 证据范围 | 阅读当前源码、迁移和测试断言;没有启动 MP 服务或运行上游测试,也没有对本项目新增故障注入 |
MP 是优先采用的业务参考;用户已确认尽量复用其成熟业务逻辑和实现,存在明确需求差异时再讨论调整。最终行为仍由本项目需求和用户确认决定。两边使用不同任务和数据机制,只比较同一场景的业务结果,不因模块名字相近就认定能力相同。已有 MP 差分样本、运行记录和有意偏离登记继续保留,按各自提交解释。
每项记录五件事:触发场景;MP 的明确处理分支;本项目正式调用路径;差异属于实现缺口、验收缺口、有意调整还是暂不适用;拟采用的规则及最小验证场景。源码结论、未复现影响和讨论建议分开。参考行为与来源登记沿用 [移植规范](../porting/license-and-provenance.md)。
## 2 顺序和进度
按下面顺序逐项讨论。状态「源码对照完成」只表示这一项的控制流核对已完成,不表示故障验收或实现调整已经完成。
| 顺序 | 业务主题 | 核对重点/需求 | 本轮状态 |
| --- | --- | --- | --- |
| 01 | 任务持久化、恢复和升级兼容 | 接受请求、入队、回填、中断恢复、旧任务处置;工程检查 §2 | 源码对照完成,恢复原则已认可;局部恢复修正和 9 个故障测试已完成,见第 4 节 |
| 02 | 下载纳管与实例隔离 | 管理标签、接管、去标签、同 hash 多实例、暂停/删除范围;R14、R28 | 正在逐项讨论;下载入口与浏览器详情页交接已确认,见第 5 节;其余待核对 |
| 03 | 部分入库、缓存清理与磁盘水位 | 未完成文件、H&R、保留期、未纳管占用、同卷汇总;R15、R21 | 待核对 |
| 04 | 整理计划、替换、路径与记录 | 冻结目标、跨卷、字幕随行、替换旧版本、记录删除;R07、R15、R23 | 待核对;01 仅覆盖恢复机制 |
| 05 | 刮削与媒体文件布局 | NFO/图片、缺失与覆盖、部分失败、任意目录、自动触发;R10、R11 | 待核对;01 仅覆盖刮削任务入队 |
| 06 | 识别、身份与映射治理 | 本地身份、人工锁、详情 ID、跨季、元数据滞后、错误关联;R03、R08、R09、R22 | 待核对 |
| 07 | 订阅完成条件与多版本 | 追更、综艺正片/上下段、整季包、独立双版本;R02、R04、R05 | 待核对 |
| 08 | 规则、选片、升级与换源 | 硬条件、优先级、偏好学习、替换计划、授权边界;R16、R20、R27 | 待核对 |
| 09 | RSS、站点快照、搜索与详情 | 去重补漏、缓存新鲜度、限流、文件清单、四站证据;R01、R25、R30 | 待核对 |
| 10 | 浏览器辅助与人工接管 | HTTP/浏览器切换、挑战升级、同会话、交还与重启;R06、R31、R32 | 待核对 |
| 11 | 旧库接管、库存与媒体服务器 | 初次扫描、影响预览、分范围纳管、查询 unknown、资产对账;R09、R18、R24 | 待核对 |
| 12 | 智能体执行与授权 | 能力调用、审批、后台等待、预算、恢复与模型证据;R12、R17、R19、R26、R29 | 待核对 |
| 13 | API、公开记录与管理员入口 | 双入口行为、增量、鉴权、多实例配置;R13、R23、R28 | 待核对 |
| 14 | 验收、备份恢复与发布 | 必执行测试、页面样本、版本迁移、恢复演练、镜像来源;工程检查 §3 | 待核对;MP 中无等价要求的部分独立验收 |
## 3 第一项:任务持久化、恢复和升级兼容
### 3.1 业务场景与对照结论
| 场景 | MP v3.0.10-1 | 本项目 v0.3.7 | 差异判定与建议 |
| --- | --- | --- | --- |
| 请求已接受,执行队列暂时失败 | 整理入口先持久化 admission 并取得租约,再入进程内队列;入队失败保留持久记录和错误,交给恢复路径 | `enqueueTracked` 依次写业务任务、引擎入队、回填 run;`queueScrape` 也有三步,回填失败共用入队失败 catch | **恢复边界需要补强。** 已接受但未关联成功应保留可恢复事实;不能仅因回填失败就宣称外部入队失败。具体双写方案待故障验证后选择 |
| 查询恢复记录失败 | `__replay_pending` 的 claim 查询异常记录日志并返回,本轮不恢复;明确空结果另行返回 | `enqueueSweep` 的 `engine.get(...).catch(() => null)` 把查询异常与不存在合并,随后可按新时间槽补投 | **状态判定需要调整。** 查询失败应记录并跳过本轮;补投必须依据明确的不存在/可重投终态,并保留现有幂等与租约边界 |
| 升级后遇到旧任务/检查点 | 有版本化输入和检查点;当前和指定 legacy 检查点显式解码,未知版本拒绝;一次执行账本迁移将有旧执行迹象的任务隔离到 `manual_review` | 当前只注册 `scrapeMedia` v2,旧 v1 输入不同;0058 删除旧 `media_scrape` 表,worker 对旧版本没有对应认领处理器 | **升级处置缺口。** 不能无限承诺自动兼容,也不能让旧任务无声悬空。升级前列出在途版本,再决定兼容执行、迁移或带理由终止;无法确定副作用时保留证据并人工处理 |
| 已完成的外部步骤遇到进程中断 | 成功步骤直接返回持久结果;遗留 `started` 先观察,已发生则补成功,未发生才重做,未知/冲突进入人工复核 | OpenWorkflow 持久步骤、`tasks_op_ledger` fencing、`asset_operation`、冻结计划和文件恢复已经存在;E4 和 tasks 崩溃恢复测试覆盖同版本 worker 中断 | **保留现有机制,补指定故障证据。** 不能把入队关联缺口扩展成「没有崩溃恢复」。完整文件冲突和替换场景留在 04 逐项核对 |
| 业务结果提交与后续事件通知 | 整理历史、终态结算、receipt 和 outbox 由同一短事务提交,提交后再派发 | 整理核验中资产/路径、业务记录和 `organization.changed` 在业务事务内提交;outbox 中继再向引擎派发 | **方向一致。** 本项目已有 outbox,但刮削与通用任务入口尚未因此获得原子关联;还需核对中继的重投窗口和业务唯一约束 |
| 正式 handler 意外漏注册 | MP 的 `runtime/tasks.py` 管理进程内异步任务生命周期,与本项目的任务名+版本 handler 注册不是同一机制 | `buildTaskRegistry` 缺核心任务时注册 stub;现有测试主要断言名称。当前正式装配已覆盖最初 9 个核心任务 | **本项目独立维护风险。** 建议正式装配验证 handler 来源,缺失时明确失败;MP 的生命周期 TaskRegistry 不能证明这个问题已有等价解决 |
这里的 MP 行为限定在已读的整理任务链,不能推广成 MP 的全部刮削、下载或任意任务都遵守同一协议。尤其 MP 的「持久准入+内存队列」与本项目的「业务库+持久引擎库」不等价:可以借鉴恢复规则,不能照搬队列实现。MP claim 的未命中也可能表示没有取得租约,并非严格等价于本项目查询 run 得到「不存在」。
升级兼容也有明确范围:MP 的分类检查点 v1→v2 迁移遇到已有执行状态、执行载荷或步骤证据时不重写旧计划,继续使用已知旧版本解码路径。这支持「保留已经产生的执行证据」的规则,不能据此承诺任意旧 handler 都能继续执行。见 [检查点迁移](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/database/versions/d8f2b6a4c1e7_3_0_28.py#L290)。
本项目刮削双写控制流已确认,但本轮没有故障注入证明实际受影响。上一轮线上只读检查未发现未结束的旧版刮削 run,也没有 `scrape_record` 记录;该时点结论不代替升级策略。见 [工程检查 §2](14-engineering-review.md#2-建议优先核对的恢复边界)。
### 3.2 精确来源
以下 MP 链接全部固定在 `0aa8571`,行号仅适用于该提交。上游测试名称说明已存在对应断言,**本轮没有运行这些测试**。
| 结论 | MP 源码与测试 | 本项目源码/已有验证 |
| --- | --- | --- |
| 先持久接受,再安排执行 | [workflow.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/application/transfer/workflow.py#L116) `TransferQueueService.put`;[queue.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/queue.py#L1194) `__record_enqueue_failure`;[test_transfer_queue_service.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_queue_service.py#L139) `test_transfer_queue_service_commits_admission_before_failed_enqueue` 使用 SQLite 仓储注入队列失败 | [common.ts](../../apps/server/src/modules/acquisition-organize/services/common.ts) `enqueueTracked` 第 134 行;[scrape.ts](../../apps/server/src/modules/acquisition-organize/services/scrape.ts) `queueScrape` 第 152 行 |
| 查询异常不当作不存在 | [queue.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/queue.py#L754) `__replay_pending` 第 754 行;[admission.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/db/adapters/transfer/admission.py#L296) `claim_recoverable`;[test_transfer_pending_replay.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_pending_replay.py#L423) `test_replay_survives_db_failure` 断言不执行恢复 | [common.ts](../../apps/server/src/modules/acquisition-organize/services/common.ts) `enqueueSweep` 第 159 行起 |
| 旧执行迹象保守隔离 | [models.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/application/transfer/models.py#L485) `TransferPlanCheckpoint.from_payload`;[迁移](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/database/versions/e5c7a9b1d3f6_3_0_16.py#L404) `_backfill_pending`;[test_transfer_execution_migration.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_execution_migration.py#L282) `test_upgrade_is_conservative_and_repairs_interrupted_indexes` | [handlers.ts](../../apps/server/src/modules/acquisition-organize/handlers.ts) 第 193 行;[0058](../../packages/db/migrations/0058_scrape_record.ts);[引擎](../../packages/tasks/src/openworkflow-engine.ts) `startWorker` 第 221 行 |
| 步骤恢复先看已有结果和实际副作用 | [execution.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/execution.py#L193) `_DurableTransferStepRunner.run`;[test_transfer_execution_runner.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_execution_runner.py#L148) 成功步骤不重做、未知孤立步骤进入人工复核断言 | [op-ledger.ts](../../packages/tasks/src/op-ledger.ts);[organize.ts](../../apps/server/src/modules/acquisition-organize/services/organize.ts);[E4](../../apps/server/test/e2e/e4-crash-recovery.test.ts)、[tasks 崩溃恢复](../../packages/tasks/test/crash-recovery.test.ts) |
| 结果和事件共同提交 | [chain.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/db/adapters/chain.py#L219) `TransactionalChainDurableEventWriter.transfer_result` | [organize.ts](../../apps/server/src/modules/acquisition-organize/services/organize.ts) 核验事务第 1386 行、`emitRecord` 第 1710 行;[outbox-relay.ts](../../packages/tasks/src/outbox-relay.ts) 第 46 行起 |
| handler 装配风险不可强行类比 | [runtime/tasks.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/runtime/tasks.py) | [context.ts](../../apps/server/src/context.ts) 第 96 行;[modules.test.ts](../../apps/server/test/modules.test.ts) 第 86 行 |
另有结算回滚/重放专项断言:[test_chain_durable_events.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_chain_durable_events.py#L1373) 检查 outbox 冲突回滚历史与 pending、陈旧租约不写业务、并发结算只提交一次。这些场景值得纳入后续验收,不能把本项目已有的业务事务直接等同于全部专项均已通过。
MP 还在 [test_transfer_planning_checkpoint.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_planning_checkpoint.py#L474) 检查非预览整理缺少 durable writer/execution repository 时,在副作用前拒绝执行。这可支持本项目「关键装配缺失应明确失败」的原则,仍不是相同的 handler 注册方案。
### 3.3 已确认的恢复原则和验收场景
用户认可这一项的恢复方向,并明确避免过度设计和过度防御。以下原则按团队内部产品的规模执行,讨论阶段的原则已落实局部修正,见第 4 节。
1. **请求接受后必须可追踪、可恢复。** 已创建的业务记录、稳定身份和原输入要能关联到已有 run;关联回填失败与真正入队失败要能区分。查不清入队结果时记录不确定性,不能直接假定没有副作用。
2. **不存在、已结束和查不到是三种不同事实。** 临时查询失败跳过本轮并保留诊断;明确查无 run 才进入补投判断;已有运行不能只因到达新时间槽就替换。
3. **升级检查实际在途任务。** 优先等待旧任务结束,或明确说明后终止并通过现有入口重新发起;确有未完成任务必须继续且不能重做时,再评估兼容或迁移。没有在途旧任务时,不新增旧处理器和通用兼容层。涉及已经执行的文件操作时保留原输入和执行证据。
4. **恢复复用已有结果。** 沿用现有冻结计划和已完成步骤;普通查询故障等待下轮重查,可重试失败使用已有重试。删除、覆盖或替换文件的实际状态不明或冲突时,停止自动重放并在现有记录中说明,不为所有异常新增人工审核流程。
最小验收样本:
| 样本 | 注入点 | 应核对的结果 |
| --- | --- | --- |
| T01 | 业务记录插入后、引擎入队前退出 | 能补投或显式结束,不能永远只显示 queued;保留同一业务身份 |
| T02 | 引擎已接受,但 run 关联回填失败 | 找到原 run 并修复关联;不能错误标成入队失败而让 worker 跳过;不能创建第二次外部动作 |
| T03 | 引擎提交后响应丢失,调用方不知道是否接受 | 通过稳定身份与原输入确认已有 run;确认不了时有诊断及等待/人工路径 |
| T04 | 已有 active run,巡检查询一次失败 | 本轮无新增 run;原关联不被覆盖;下一轮查询正常时恢复判断 |
| T05 | 上一版本存在未完成 `scrapeMedia` v1,再启动当前版本与迁移 | 兼容、转换、终止或人工处置有明确结果和证据;无旧 run 永久悬空;迁移前后输入可解释 |
| T06 | 正式装配遗漏一个核心 handler | 装配检查失败或启动时给出明确错误;不能以 stub 的成功回显通过验收 |
已有同版本 kill-worker 测试继续作为回归。T01 至 T06 是针对不同改动的验收场景清单,按实际修复选择,不要求每个功能开发前全部执行。T02、T03 涉及两边不同的队列机制,因此样本按本项目的引擎接口设计,不机械复用 MP 测试。
**当前取舍:保留 OpenWorkflow、冻结计划和现有账本;优先局部修正查询异常判定与入队/关联回填的异常分类,再核实是否可以用已有幂等和巡检完成恢复。** 只有故障样本证明现有机制无法处理时,才评估复用 outbox 等进一步调整;专门恢复器、新状态机和补偿服务不作为默认方案。旧任务处置先落实发布检查清单。第一项实现细节确定后,第二项核对下载纳管与多实例隔离,再进入部分入库后的清理保护。
## 4 用户确认后的当前实现
### 4.1 局部任务恢复
已按第 3 节原则修正入队后关联回填、查询失败补投判定、run 关联恢复与刮削去重。9 个真实 PG 故障样本通过;未新增另一套恢复基础设施。旧版本实际在途任务的发布处置写入部署手册。T05 的跨版本专项和 T06 的 handler 装配强化仍不能因这 9 个测试而宣称完成。
### 4.2 分类、来源与存量
参考插件 [`categoryeditor` v1.3](https://github.com/jxxghp/MoviePilot-Plugins/tree/9b9b14bc0f0bb3890b0f699a84f9bd0df063b2e3/plugins/categoryeditor):插件保存 YAML 并刷新分类 helper,匹配由 MP core 实现。参考 v3 的 `app/domain/classification/projection.py` 的 legacy TMDB 匹配分支:顺序首次匹配、字段 AND、值 OR、排除、年份和空规则兜底。我们内置同类规则编辑能力,保留用户提供的全部名称和兜底习惯;不复制 v3 的完整分类策略版本发布体系。
目录接管参考 MP `app/application/transfer/monitor/snapshot.py`、`poller.py`:先快照现状,再比对新增/变化;完整存量作为单独手动操作。我们采用数据库持久快照、已有识别与整理计划,一条配置一个来源路径。同分类可配置多条不同来源,综艺归 tv;只有复制与移动。qB 标签继续使用现有任务管理和实例范围。
当前源码已接入分类编辑和测试、来源选择、自动/固定分类、qB 配置标签、快照与存量向导。已有路径绑定保留,不按新规则迁移旧库;源离线保留基线,已知 qB 任务不被目录扫描重复接管。具体入口和分批限制见 [整理手册](../runbook/organize.md)。测试使用本项目临时目录与隔离 PG,不是运行 MP 服务;未完成浏览器截图审查;后续 v0.3.9 已发布,见 [发布记录](18-organize-release.md)。
其他主题仍按第 2 节逐项讨论。这轮只补齐与已讨论整理功能有关的分类、标签与目录接管行为,不宣称整个下载隔离、清理、订阅和智能体业务对照已完成。
### 4.3 本次验证记录
- 分类匹配 domain 12 个定向测试;domain 全量 22 文件 / 654 测试通过。contracts 全量 7 文件 / 1048 测试、db 全量 4 文件 / 21 测试通过。
- source monitor 13 个真实 PG/临时文件/HTTP mock qB 测试:基线不处理存量、稳定观察、旧基线保留、分类和失败文件处理、存量只读与分批、文件事实变化、无标签未登记下载等待并在完成后接管。
- 最终合并复验分类 API、来源规划和 source monitor:3 文件 / 23 个真实 PG 测试全部通过。分类 API 与来源规划其中 10 个,包括并发配置保存、固定目录不重复、自动目录层级、已有绑定保留、qB 标签与来源/实例范围,以及媒体类型限制。
- 文件执行、移动、恢复、刮削、接管和向导覆盖最终回归 7 文件 / 38 测试通过;worker lane 注册与文档归属 8 测试通过。
- server 完整回归初次运行 127 文件,783 测试通过、2 失败、1 跳过;失败涉及新任务文档归属及开发过程中分类并发合并,最终相关 3 文件 / 23 测试和 worker lane 8 测试单独复验通过。完整回归不是最终同一时间源码的全绿报告,环境条件跳过也不计为完成验收。
- web 全量 25 文件 / 228 测试、web typecheck 与 production build 通过。workspace TypeScript / Deno 检查、Biome 检查和 `git diff --check` 通过。
没有使用浏览器截图替代或冒充前端验收;本次未替换线上镜像、未执行线上迁移,也未修改宿主机程序。
### 4.4 v0.3.9 发布与文件管理入口
用户要求部署后,核对发现文件管理入口原来只复用向导,没有携带来源配置。本次补齐同根/目录边界/最长来源匹配、唯一匹配自动应用、多配置明确选择及首击等待加载;H&R 与根目录保护保留。新增匹配测试后 web 全量 26 文件 / 232 项通过,typecheck、Biome 和 production build 通过。
v0.3.9 Docker 镜像完成 web build、server compile 和空环境二进制自检后部署;执行 `0059` 迁移,注册 `scanOrganizeSource@1` 和 `organize.sources`。发布前没有在途业务或引擎任务,备份校验通过;部署后 HTTP / HTTPS、版本一致、登录后的分类/来源/文件根读取以及 3 个分类预览通过。详细镜像与证据见发布记录。
## 5 下载入口与浏览器详情页交接(讨论记录)
2026-10-01 用户继续逐项讨论后确认以下业务决策,并随后授权实施。实现与验证见第 5.6 节;这批变更已部署为 v0.3.10。
### 5.1 已确认
- 常规入口包括订阅自动下载、手动搜索选择下载、外部 qB 任务接管,前两种使用较多。三种入口进入共同的后续下载管理和整理流程,保留来源。
- 手动下载独立于订阅,不要求先建订阅,不自动创建持续订阅。
- 优先沿用 MP 普通订阅的完成口径:下载器成功接收资源后记录下载事实,按目标覆盖判断完成,不以整理入库成功作为普通订阅完成的前置条件。下载与整理状态分别展示;洗版和多版本口径后续单独核对。
- 浏览器手动下载入口位于串流窗口外的工具操作栏,桌面串流与截图模式共用。用户进入原站资源详情页后点击「下载当前资源」,复用现有详情解析和下载提交能力,不要求用户点击原站下载按钮或等待文件捕获。
- 下载按钮绑定当前浏览器标签页的资源详情;按站点定义解析资源身份和详情,使用对应站点的种子获取方式。默认下载器与保存目录可直接提交,也提供修改选项;提交结果及下载任务入口在工具操作栏显示。
- 浏览器详情页入口不以资源预先存在于本地搜索/快照索引或已有订阅为条件。需要新增当前页面与详情解析、手动下载的衔接;本地缺少记录不能阻止用户选择的资源进入手动流程。
### 5.2 已核对的代码依据与现有差异
MP 固定基线中的 `app/api/endpoints/download.py` 手动入口使用 `source="Manual"`,不创建订阅;`app/chain/subscribe/search.py` 提交订阅下载后调用完成判断,`app/chain/subscribe/completion.py` 普通电影可在取得下载结果后完成,普通剧集按剩余目标和下载事实判断。外部 qB 任务在配置管理标签时按标签筛选,并由 `app/chain/transfer/queue.py` 检查下载完成、来源监控目录及已有整理状态;无下载历史的任务由文件重新识别。
本项目已有 `packages/adapters/src/browser/camofox/client.ts` 的 `pageContent` 当前页面读取、`packages/adapters/src/sites/spider/detail.ts` 的可配置详情解析,以及 M-Team 等站点的 `detail`/`downloadTarget` 能力。现有 `apps/server/src/modules/browser/routes.ts` 文件捕获入口要求匹配本地资源,不能替代本次确认的详情页直接下载入口。现有搜索/工作台手动提交与收录版本的关联限制也需在后续实现中梳理。
### 5.3 已确认:未识别资源与 TMDB/Local 元数据
MP 手动添加入口识别失败时返回 `requires_confirmation`;前端 `AddDownloadDialog.vue` 允许用户确认继续下载,通过 `allow_unrecognized` 提交最小媒体上下文。该分支不强制用户先指定 TMDB 身份。
用户确认未识别资源可以下载。整理阶段采用以下识别与元数据补全流程:
1. 常规识别先匹配 TMDB;成功后按 TMDB 元数据整理。
2. 自动匹配失败时由 Agent 接棒,读取 PT 详情页并结合网络搜索核对原名、译名、别名、年份及季集信息,再尝试匹配 TMDB。因翻译/别名导致的失败应在查明正确条目后回到 TMDB 模式。
3. 找不到合适 TMDB 条目时进入 Local 模式,由 Agent 基于 PT 详情页及搜寻资料补全标题、简介、年份、类型、演职人员等信息,选取封面,生成本地 NFO 和图片。有依据的字段补全,缺少资料的字段留空。
4. TMDB 与 Local 模式共用分类、命名、复制/移动、整理记录和入库流程。Local 保存独立身份及资料来源,后续可以重新匹配 TMDB;不为 Local 新建一套整理系统。
5. Local 使用补全后的语种、国家、类型等字段匹配现有分类配置;字段不足时使用对应电影/剧集的兜底分类,允许手动调整。
实现复用现有身份候选、元数据补全、字段投影与 NFO 刮削链路;资料不足或候选冲突仍保留待处理。具体实现和验收范围见第 5.6 节。
### 5.4 已确认:已有任务与已有作品
用户认可同种子复用已有下载任务,同一作品的不同资源仍允许手动下载。媒体详情需要展示库内已有版本及规格,用户据此决定下载何种资源;已有作品不作为手动下载的阻断条件。
### 5.5 已确认:正常入库与一次性洗版入库
用户确认在本次下载中选择「正常入库」或「洗版入库」:正常模式允许多版本并存;洗版模式在下载完成后整理时用新版本替换旧版本,是本次任务的一次性规划,不自动变成持续洗版订阅。
- 洗版允许用户选择替换哪些已有版本。只有一个旧版本时默认选中,有多个时由用户勾选,未选择的版本保留。
- 新文件成功落库后再清理对应旧文件;部分整理失败时保留相应旧文件。
- 剧集按实际成功入库的季集范围替换。例如旧版有第 1–10 集、新资源只包含第 1–6 集,只替换前六集。特殊编号、上下段及合并集由 Agent 辅助核对,不能明确对应时供用户调整。
固定 MP 基线 `app/modules/filemanager/transhandler.py` 提供 `always`/`size`/`never`/`latest` 覆盖模式;`latest` 其它版本清理按同目录、季集与 Part 匹配视频文件。它的清理步骤在新文件转移前执行,不能把本项目拟采用的「新文件成功落库后清理旧文件」描述为 MP 原样行为。
多版本是否产生不同路径取决于命名模板。MP 的 `app/runtime/config.py` 默认电影文件名包含 `videoFormat`,默认剧集文件名没有分辨率/编码等版本字段;本项目 `packages/domain/src/naming/defaults.ts` 当前沿用这两条模板。因此需要明确多版本命名方案,不能假定不同资源会自动产生不同文件名。已有 `path_binding` 仍按前述约定保留,模板调整不自动迁移旧库。
用户要求模板可配置,并提供目前使用的两条模板作为参考配置,原样记录如下。本轮未修改线上命名设置。
电影:
```jinja
{{title}}{% if year %} ({{year}}){% endif %}/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}{% if edition %} - {{edition}}{% endif %}{{fileExt}}
```
电视剧:
```jinja
{{title}}{% if year %} ({{year}}){% endif %}/Season {{season}}/{{title}} - {{season_episode}}{% if part %}-{{part}}{% endif %}{% if episode %} - 第 {{episode}} 集{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}{% if edition %} - {{edition}}{% endif %}{{fileExt}}
```
已核对本项目支持模板编辑、`videoFormat` 和 `edition`。`edition` 按 MP 语义为来源与特效组合(如 `WEB-DL DV`),剪辑版本另有 `cut` 变量,不改变用户提供的模板。模板不能区分的同名文件冲突需沿用整理冲突处理;不据此宣称模板可以区分所有同规格版本。
使用现有命名引擎直接编译并渲染上述两条模板,完整字段及年份/分辨率/edition 缺失的样本均通过;文档差异空白检查通过。此验证未修改任何媒体文件或线上配置。
一次性洗版已接入手动计划和冻结整理计划;同路径与不同路径替换复用现有整理记录、文件执行和操作账本。用户选择的旧版本及季集范围随本次计划保存,验证记录见第 5.6 节。
后续仍需讨论手动选择与自动筛选规则、同 hash 多实例及删除范围。
### 5.6 本轮实现与验收(已部署 v0.3.10)
手动搜索、站点工作台与原站详情共用独立手动下载入口,支持下载器、映射保存目录和正常/一次性洗版选择。未识别先尝试现有身份解析,仍未识别时返回确认结果;确认后建立无 Slot 的手动计划,不创建订阅。迁移 `0060_manual_acquisition` 允许计划无 Slot,并添加 `download_task.import_plan_id` 保存本次入库选择,原下载任务的订阅计划关联继续保留。
原站详情入口在桌面串流/截图之外显示「下载当前资源」与「下载选项」。`POST /browser/sessions/{id}/resource` 从当前可见标签按站点定义读取详情,登记此前没有索引的资源并缓存 PT 内容;不点击原站下载按钮。桌面新增 popup 会被发现,无法唯一确定当前标签时给出明确提示,不提交旧页面。普通 Agent 浏览器动作沿用原标签选择。原生桌面新页未被上游登记时,仍需回到已登记详情页;本轮没有改浏览器插件登记协议。
整理增加 `metadata.resolve_organize`:常规匹配 → 详情页/网络补全与模型抽取 → 别名重查 TMDB → 有资料的 Local 身份。复用现有预算、补全记录、字段来源、分类和刮削;不能将 TMDB 查询故障视为没有条目,候选冲突或资料不足保留待处理。Local 身份不锁定,后续可重新识别。公网封面通过现有图片抓取能力写入;成功的 Local 入库安排 NFO/图片任务,普通 TMDB 自动刮削继续遵循原设置。
一次性洗版冻结用户所选旧文件及事实;新文件核验和资产登记成功后,按实际季集/Part 覆盖清理所选旧版及私有附属文件。同路径替换先暂存旧文件,核验失败恢复旧文件;未选文件、未成功替换的季集、未完整覆盖的合并集、共享图片/NFO及下载缓存保留。清理复用现有回收站与 `asset_operation`;不增加持续洗版订阅。命名模板仍通过设置编辑,本轮没有修改用户线上模板或已有路径绑定。
普通订阅现在按下载器确认接收及实际目标覆盖结束;多目标必须全部满足,TV 未知总集数不伪造完整覆盖。Slot 与下载任务继续表示真实下载/核验进度,洗版目标沿用入库后的判定。
本轮验证:
- 手动下载/订阅/面板 15 项与搜索入口 11 项隔离 PG 测试通过;覆盖未知资源确认、无假订阅、映射目录、同 hash 原路径及新入库计划、多目标订阅完成。
- 浏览器详情入口及既有浏览器 API 共 13 项通过;适配器全量 58 文件/461 项通过、1 文件/3 项环境跳过,其中可见标签交接 12 项定向通过。没有用 fixture 结果替代用户真实浏览器验收。
- 整理、分类与来源 4 文件/18 项通过,包含同路径失败恢复、旧文件变化保护、剧集部分失败/合并集保留,以及 Local 在自动刮削关闭时仍排 NFO/图片任务。领域整理专项 22 项通过。
- Local 与既有元数据补全 21 项通过;NFO/Local/刮削差分 25 项通过,Local 内部人物编号不会写成负 TMDB ID。模型和外部服务使用受控 fixture,没有声称真实模型或全部 PT 站已验收。
- server 完整回归 132 文件:804 项通过、9 项失败、1 项环境跳过。9 个失败集中在 4 个旧测试文件,仍把手动提交视作订阅评估、使用旧订阅完成原因或固定 41 能力。修正测试准备入口和新口径后,这 4 文件合并复验 19 项全部通过;保留实际入库、季集覆盖、资产/文件保留和不重复下载断言,并增加独立手动不创建订阅/Slot 的验证。没有将这次分批复验描述为最终同一时点的单次全量全绿报告。
- contracts 全量 1052 项、db 21 项、domain 656 项、web 232 项通过。workspace TypeScript/Deno、Biome、设计 token、文案标点和 `git diff --check` 通过。
- 用本机 Chromium 与真实 React 组件验证单旧版默认选择、无选择禁用洗版、勾选洗版、切回正常、未知资源二次确认、串流外按钮交接;API 为 fixture,未产生真实下载,页面无未捕获异常。
- web production build、server compile 与空环境二进制模块自检通过。局部证据存于本机 `.local/manual-download-ui/`。
以上业务回归主要为源码与隔离环境验证。随后于 2026-10-01 发布 v0.3.10,线上 `0060` 迁移、HTTP/HTTPS、管理员接口及 6 个生产页面冒烟通过,未触发真实下载或媒体整理;镜像、备份与验证证据见 [发布记录](18-organize-release.md#v0310-手动下载与入库发布)。
### 5.7 v0.3.11 体验修复
搜索资源标题接入与站点工作台同一详情抽屉与读取接口,复用按需详情抓取和缓存,不增加第二套解析器。详情抽屉仍提供下载选项、库存和 Agent 入口。桌面浏览器的最大化规则修复后,上下黑边消失。已部署并经过组件交互、生产真实缓存详情接口与 HTTPS 页面验证,范围和证据见 [发布记录](18-organize-release.md#v0311-搜索详情与桌面黑边修复)。
## 6 整理使用方式与文件管理器专项研究(2026-10-02)
本节只记录研究与最少改动建议,未修改业务代码。MP 后端仍使用 §1 固定提交;补充前端固定源码 `/tmp/umt-moviepilot-frontend-v3-review-20261001`,提交 `63515db2e873d6adaf6aa385a89c5901e3bc1ea6`。**MP 仅源码对照,没有 MP 实际页面截图。** 本项目截图来自本轮 `https://localhost:4380` 已登录页面,使用 Chromium / Playwright,只打开页面、选择项与整理对话框;未点击执行整理、下载、接管或清理,未新增测试数据和来源配置。
### 6.1 实际页面步骤与证据
截图均在 `.local/organize-flow-review-20261002/`,已打开检查;这是本机研究证据,不作为发布包。
| 步骤 | 实际操作与健康情况 | 截图 |
| --- | --- | --- |
| 1 | 整理记录页正常显示作品、源→目标、复制、失败原因和来源;手动整理入口可见 | `01-organize-history.png` |
| 2 | 点击手动整理,当前环境没有整理来源配置,仅显示「手动选择其他来源目录」;可以继续,但空状态没有去新增来源的入口 | `02-manual-source-picker.png` |
| 3 | 打开手动来源目录对话框,下载根与媒体库根可浏览;尚未选择时确认禁用,正常 | `08-manual-directory.png` |
| 4 | 文件管理器打开下载目录,选择一个电影目录后出现底部整理/移动/复制工具栏;移动因做种义务禁用,正常 | `03-file-manager.png`、`05-file-selection.png` |
| 5 | 点击整理,目录展开为一个视频,自动识别作品、显示已有记录和 H&R;页面显示「已选 0 项」但下一步可点,执行范围表达容易混淆 | `06-file-organize-wizard.png` |
| 6 | 下一步显示复制/移动、冲突/跳过/替换、自动/媒体库/指定目录、命名和元数据刷新;H&R 禁用移动,正常;没有提交预览或执行 | `07-file-organize-options.png` |
| 7 | 失败记录菜单点击重新整理,源文件与原作品进入相同向导,正常;未执行 | `10-record-reorganize.png` |
| 8 | 设置页实际显示媒体库、整理方式、刮削、分类、来源与命名;本环境没有来源配置 | `04-settings.png` |
| 9 | 下载面板切换已完成正常,显示已入库与入库失败及原因;下载盘独立展示。未操作下载或接管,下载详情整理仅做源码核对 | `09-download-board.png`、`11-download-completed.png` |
### 6.2 业务边界与 MP 差异
| 场景 | MP 固定源码行为 | 本项目当前路径、差异与最少建议 |
| --- | --- | --- |
| 文件管理器单项/多选整理 | `components/filebrowser/FileList.vue:562`、`:568` 将当前项或去重后的 selected 传给同一个 `ReorganizeDialog`,不是另建整理系统。对话框 `ReorganizeDialog.vue:667` 使用文件项或历史 ID 查询 `transfer/manual/target-path`,展示匹配目的目录;用户明确选择目标后不再覆盖 | `routes/console/files.tsx:392` 过滤视频/目录,进入 `FileSourceSelection` 再共用三步向导,方向一致。来源匹配按根、目录边界和最长路径,唯一匹配自动应用,多匹配需用户选择(`components/organize/source-match.ts:4`、`file-source-selection.tsx:23`)。保留已有保护和预览,不需要再做第二个简化整理弹窗 |
| 手动整理、文件整理、历史重整的来源默认 | MP 相同对话框对 fileitems 与 logids 都查询自动目标,匹配结果包含整理方式和刮削/分类目录选项;后端 `app/api/endpoints/transfer.py:549` 针对各来源匹配目录,不同目标时不宣称共同匹配 | 本项目来源存量带 `sourceConfigId/defaultAction`,文件管理器先匹配来源;但「其他来源目录」直接创建不含配置的 files 来源(`source-selection.tsx:37`),历史和下载详情也直接进向导(`routes/console/organize.tsx:319`、`downloads/download-detail.tsx:233`)。默认方式因此回到全局(`organize-wizard.tsx:233`),请求只在 files 分支透传配置(`:327`、`:341`)。服务器 `services/organize-config.ts:17` 对手动文件不会按目录自动匹配,只有显式配置或带实例/标签的 qB 自动来源。这是入口默认不一致,建议解析完 FileRef 后共用已有来源选择,不另建配置体系;历史重新整理应优先保留原次策略或明确展示当前匹配策略 |
| 成功移动后的重新整理 | MP `app/api/endpoints/transfer.py:737` 对成功且方式含 move 的历史,用历史目标文件作为本次输入;失败/复制使用原源文件 | 本项目 `file-manager/tree.ts:75` 的 `recordSources` 只解析原 source_path/主文件 source_path;源不存在则进入 unresolved。`record-parts.ts:30` 仍让已结束移动记录显示重新整理入口。这会让成功移动后的正常重整无可用文件。最少修正是按成功移动历史选择现存目标,源缺失其他场景说明不可重整;不承诺任意丢失文件恢复。此项为源码确定差异,本轮没有制造真实移动样本 |
| 已有成功整理记录的手动扫描 | MP `ReorganizeDialog.vue:1388` 查询历史,显示成功记录处理选项,`:1314` 对预览与提交一致传 `reorganize/skip_success` | 本项目文件行显示「有 N 条整理记录」,但勾选框是批量修改作品/季集的选择,不是纳入整理范围;所有未排除行仍进入预览(`organize-wizard.tsx:274`、`:588`)。已有文件靠冲突/跳过/替换处理,而「跳过目标已有」与「跳过成功记录」不是相同概念。先把「已选」改为「批量修改已选」,明确「本次整理 N 个未排除视频」;历史成功跳过有实际需求再复用已有记录判断,不默认新增整套状态 |
| 下载接管和手动整理 | MP 管理标签决定外部下载纳管,完成后进入整理;文件管理器整理是明确选中文件的单次操作 | 本项目 qB 标签来源用于自动纳管/整理(`organize-config.ts:55`、`:64`),文件管理器单次整理和来源存量不等于下载任务接管,也不改变目录监控基线。下载详情可以在完成/importing/imported 时打开向导(`download-detail.tsx:98`),内容路径先转换为登记根 FileRef(`organize-wizard.tsx:126`);向导请求没有传下载 taskId/instance/tags(`model.ts:248`)。建议文案明确「整理所选文件」与「接管下载」;不在文件整理时隐式增加标签或接管任务 |
| 整理、刷新元数据和刮削 | MP 对话框支持 scrape;匹配目录可给出 scraping 默认(`ReorganizeDialog.vue:711`),设置用于长期默认,本次选项用于本次操作 | 本项目第 2 步的「刷新元数据」是字段级 refreshMetadata(`file-manager/organize.ts:523`),不是 NFO/海报刮削;页面底部已经说明现有 NFO/图片不搬,可整理后刮削。Local 成功/部分成功后另有 NFO/图片任务(`acquisition-organize/services/organize.ts:1225`),应保留 §5 已确认的 Agent→TMDB/Local,不因对标 MP 强迫仅 TMDB。最少先区分刷新资料与写入 NFO/图片;如需本次刮削开关,复用现有 queue,不创建第二套刮削 |
### 6.3 保留与最小验证
保留仅复制/移动、同分类多个来源、固定/自动分类、已绑定作品沿用原路径、执行前预览、跨卷核验后删源、H&R、来源监控不回填存量,以及 Agent/TMDB/Local 共用整理。不要照抄 MP 的软/硬链接、rclone、音乐或额外分类发布系统。
建议优先处理:① 文件/手动/下载/历史入口统一来源策略展示;② 成功移动历史改用目标重整;③ 向导明确批量修改选择与实际整理范围;④ 无来源空状态加现有设置入口。完成这些后再讨论成功记录跳过和本次刮削,不把它们提前扩成新业务系统。
最小验证使用同一来源下同一目录,从文件管理、手动其他目录、下载详情、历史入口进入,核对所选来源、方式、目标、分类一致;增加成功移动后源已删除、多个来源同路径、跨不同来源多选及部分失败样本。H&R 在本轮文件入口可见且禁用移动;其他入口及服务器执行保护还需定向验证。预览冻结、实际文件执行、Agent 实际补全、来源多配置和源离线没有在本轮页面运行,不把源码能力等同浏览器验收。截图也不能证明完整可访问性;批量单选的方向键导航、焦点与读屏说明仍需交互验证。
### 6.4 用户反馈与后续讨论(2026-10-02)
用户要求以下样式问题先记下,等整理模块讨论完成后一并修改;当前不改样式:
- 右上角通知数字角标目前为金色,与用户期望的整体样式不一致;具体替代颜色尚未确定。
- 分页的每页条数选择控件仍呈现原生样式,应复用项目现有选择控件与统一样式。
整理讨论按实际场景逐项进行,每项对照「MP 行为 → 当前行为 → 希望的行为 → 验收示例」,讨论确认后再实施。建议顺序:入口和来源默认;文件范围与已有记录;作品识别、目标命名与刮削;执行结果、失败重试与历史重新整理。前面已确认的复制/移动、分类 YAML、存量单次触发、qB 标签/快照、TMDB/Local 和多版本/洗版约定继续沿用,不重新扩展需求。
用户进一步倾向于 MP 的独立、共用整理弹窗方案,整理流程也优先参考 MP。据此讨论统一弹窗:手动整理、文件管理器、下载详情和历史重整传入各自上下文,共用来源/目标配置、识别修正、整理选项、预览和提交逻辑。「全局生效」指规则与交互在各入口一致;单次调整只作用于本次整理,长期默认在设置中保存。自动整理共用业务规则,后台执行不要求弹出窗口。弹窗具体操作结构与细节继续讨论,尚未授权据此实施业务改动。
后续讨论中用户认可「本次设置作用于整个所选范围」:单文件只处理该文件,目录递归处理其中视频,多选合并去重;自动识别按文件进行,手动指定作品作用于本批范围,混合批次中需要单独纠正的作品另行选择整理。用户要求参考 MP 的弹窗能力:手动指定电影/剧集,直接输入 TMDB ID 或调用 TMDB 搜索并选择作品,选择转移模式与目标路径、是否刮削,并通过队列执行。既有范围仍仅复制/移动,TMDB/Local 与 Agent 兜底继续沿用。
用户另提出学习 MP 的「整理队列/整理记录」划分。当前本项目整理页将记录和进行中状态集中展示;底层已有 task、引擎调度与整理记录关联(`file-manager/organize.ts`),可优先复用。拟将等待/执行中的任务展示于整理队列,将处理结果展示于整理记录;整理成功的文件在开启刮削时再触发 NFO/图片任务,界面区分整理与刮削状态。MP 固定前端提供预览、加入队列和立即整理按钮,是否完整沿用按钮组合尚未确定;本项目不因拆分视图另造调度机制。以上仍为需求讨论记录,未实施业务改动。
用户随后确认提交按钮只保留「加入整理队列」,不提供「立即整理」。任务受理成功后即可关闭弹窗或继续其他操作,不要求用户停留等待文件处理完成;进度和结果通过整理队列/整理记录查看。仍沿用既有执行前预览约定。本项已定,下一项继续讨论已有成功整理记录的跳过与重新整理语义。
已有成功记录的处理,用户要求采用 MP 做法。重新核对固定前端源码:`ReorganizeDialog.vue` 的 `skipSuccessfulRecords` 初始为 `false`;手动文件/目录入口查询成功历史,有记录时显示数量和「跳过成功记录」开关,默认关闭,用户本次开启后才跳过;改变开关会清除旧预览,预览与提交共用该参数。历史 ID 入口直接采用重新整理语义,不走上述手动历史提示查询。本项目采用这些默认与交互,先前讨论提出的「普通整理默认跳过」不采用。成功记录跳过和目标文件冲突仍是两个独立判断;保留本项目只提供「加入整理队列」的已确认差异。
### 6.5 本轮落实(v0.3.13)
用户授权「一鼓作气做完」。共用整理弹窗已改为单个配置表单与可展开预览,手动、文件管理、下载详情、历史重整统一来源匹配和选项;支持自动/电影/剧集、TMDB 搜索与 ID 定位、本批作品与季号、复制/移动、目标路径、命名/冲突、刮削开关,只提交现有整理队列。成功受理即可关闭;部分受理后已入队文件从本批移除,避免重新预览时重复提交。异步定位取消后不会覆盖用户新选择,选项或识别变化使旧预览失效。
整理页分队列与记录:队列直接查询现有 `verifyAndImport` 与 `scrapeMedia` 的 pending/running/sleeping 任务,覆盖未生成记录的下载核验任务;记录默认终态且聚焦整理行为,旧记录/筛选深链接仍可打开。成功移动重整用结果的媒体库目标路径;复制和失败仍用源。成功历史判断同时要求完整成功记录与成功主文件,识别提示与执行共用查询;手动跳过默认关闭,目标冲突另行判断。本次刮削值冻结到计划,TMDB/Local 及恢复执行都复用原刮削任务机制。
通知数字角标改为主题 primary/primary-foreground;每页条数控件改为项目现有 Select,保留分页行为。使用手册同步更新。验证与发布证据集中于 `.local/organize-final-20261002/`,浏览器交互的写请求使用拦截响应,不对用户媒体进行实际下载、移动、替换或刮削;真正文件执行由隔离数据库与临时目录回归验证。
### 6.6 原地入库补充(2026-10-02,v0.3.14 待部署)
用户指出文件管理整理不应一律复制/移动,并要求修复和记录。核对已有固定 MP 前后端源码:`api/constants.ts` 的整理方式是 copy/move/link/softlink,没有独立的原地登记枚举;文件管理的 `ScrapeDialog.vue`/`app/chain/scraping.py` 提供独立就地刮削。`storages/local.py` 的同路径 move 快速返回只是底层保护,不能据此认定上层整理流程已支持原地入库。
本项目既有「刮削」可就地写 NFO/图片,`addToLibrary` 仅创建作品条目,不登记视频位置、资产及季集覆盖;保留原文件名选项仍会搬运到模板目录。新增共用弹窗「原地入库」,保留路径和名称,复用识别、预览、队列、逐文件结果与资产登记事务;记录方式为 `in_place`,迁移 `0061` 扩展两个枚举约束。媒体库内选择默认原地入库,下载缓存继续原复制/移动。原地模式不使用分类搬运目标和命名模板、不建立路径绑定,配对字幕一并登记;可选刮削只针对本次成功文件。
这是为本项目资产管理补齐的行为,参考 MP 就地刮削语义,并非声称 MP 有同名模式。未改动自动接管配置或加入链接方式。实现与验证边界见发布记录和整理手册。
-145
View File
@@ -1,145 +0,0 @@
# 依赖与程序版本检查
> 本文保存升级前的检查与候选建议。用户随后要求全面升级,实际修改、验证与部署状态统一见 [全面升级执行记录](17-full-upgrade.md);下文“没有执行升级”对应检查阶段。
检查日期:2026-10-01;业务代码基线:`8013196e3f5e6a8c4ea0cd26cf77294a82a13ddd`/`v0.3.7`。本轮只读检查声明、锁文件、实际程序版本和官方发布信息,并保存文档与证据。没有安装新依赖、更新锁文件、重建镜像、迁移数据库或变更部署。
结论:应先处理安全补丁和运行环境维护,再分别评估 PostgreSQL 18、Pi 原生 MCP 和构建工具的大版本变更。建议将 **PostgreSQL 18.6 列为数据库迁移目标**;16.15 是迁移前的同分支补丁选项。**Pi 0.99.2 已有可复用的 MCP 客户端 SDK**,应评估接入;它与当前对外 MCP 服务端承担不同职责。Caddy 已是当前最新版本,是否保留取决于部署是否已有 HTTPS 网关。
## 1 检查范围与证据
| 范围 | 本轮覆盖 | 证据/限制 |
| --- | --- | --- |
| 主 workspace 直接 npm 依赖 | 58 个不同包,逐包查询官方 npm registry;19 个锁定版本不同于 latest,39 个相同 | [完整直接依赖表](evidence/2026-10-01-dependencies/direct-dependencies.csv),包含声明、实际锁定、latest 与建议 |
| 主 workspace 间接 npm 依赖 | `deno.lock` 648 个 npm 实例、633 个不同包名,逐包查询;191 个包名存在不同于 latest 的锁定版本 | [完整锁定依赖表](evidence/2026-10-01-dependencies/locked-npm.csv);不同版本不等于漏洞,不强制覆盖所有传递依赖 |
| JSR | `@std/path` 1.1.6、`@std/internal` 1.0.14,均与查询时最新相同 | [补充元数据](evidence/2026-10-01-dependencies/additional-metadata.json) |
| 安全公告 | 主锁文件 `deno audit`;已部署浏览器独立 package-lock 的生产依赖 `npm audit --omit=dev` | [主项目审计](evidence/2026-10-01-dependencies/workspace-audit.txt)、[浏览器审计](evidence/2026-10-01-dependencies/browser-audit.json);公告命中仍需判断使用路径 |
| 实际运行环境 | 应用、数据库、浏览器和可选入口容器;宿主 Docker/Compose/Node/FFprobe;默认下载器演示实例 | 实际版本见第 3 节。外部用户自建的下载器、Emby 和其它主机未做全面资产扫描 |
| 浏览器独立依赖 | 6 个直接 npm 包逐个查询,独立 lock 做生产依赖安全审计;上游 1.18.0 manifest 对照 | 不属于主 workspace 的 58/633 统计;浏览器全部传递包没有逐个做新旧版本查询 |
| 系统组件 | Debian 发行版、Python、Selkies/pixelflux/labwc 与浏览器基础镜像摘要 | 未做全量 dpkg 包清单与系统 CVE 扫描,不能据此声称所有系统包均为最新或无漏洞 |
| 历史 spike 和参考源码 | 额外查询部分 spike 工具版本,识别其独立作用域 | `spikes/` 不属于生产 workspace;`spikes/movieclaw-src/repo` 是参考源码,不把其依赖计作正在发布的程序 |
完整 registry 查询快照、查询时间与源码提交保存在本地 `.local/evidence/2026-10-01-dependencies/registry-snapshot.json`(约 240 KB,未进 git;SHA-256 `4ca6c78ecc62f881e2219d6068fdb9e9e48c1b47d79c57ab40e32cc107edd948`)。版本结论是这个检查时点的快照,后续执行升级应重新查询。锁文件 SHA-256:`f4c517c88acac3656ce94bfe6f6e19daf5410c4187624f46110c41cf29e200a0`。
`deno outdated --recursive --no-lock --node-modules-dir=manual` 在允许联网后及加入 `--reload=npm:` 后都列出 12 个包;逐包官方 registry 查询发现 19 个。本轮没有查明这 7 个差异的工具原因,不能简单认定刷新缓存即可解决。报告采用逐包 registry 版本,并用 Pi 等官方发布记录交叉核对;工具结果差异继续保留为检查限制。
## 2 直接依赖的更新建议
下表列出 19 个不同于 latest 的直接依赖。建议版本是后续隔离验证的候选,尚未执行安装或兼容验收。完整 58 个包见证据 CSV。
| 包 | 当前锁定 | 查询时 latest | 建议 |
| --- | --- | --- | --- |
| `@biomejs/biome` | 2.5.14 | 2.5.15 | 补丁候选;检查格式/lint 变化 |
| `@earendil-works/pi-agent-core` | 0.87.1 | 0.99.2 | 与 pi-ai 同步评估;会话、工具与嵌入接口回归 |
| `@earendil-works/pi-ai` | 0.87.1 | 0.99.2 | provider、模型、OAuth、流与取消回归 |
| `@modelcontextprotocol/sdk` | 1.30.1 | 1.31.0 | 同主版本候选;对外鉴权、工具、错误和流式连接回归 |
| `@simplewebauthn/server` | 13.3.3 | 14.0.3 | 与 browser 配套评估;存量凭据、注册、登录和恢复入口 |
| `@simplewebauthn/browser` | 13.3.0 | 14.0.0 | 配套大版本变更,不混入普通补丁批次 |
| `@types/node` | 24.19.0 | 26.6.3 | 保持 Node 24 兼容目标,另核对 24.x 补丁;不直接追 26 |
| `fast-xml-parser` | 5.11.1 | 5.11.2 | 补丁候选;RSS、XML 与 NFO 固定样本 |
| `hono` | 4.13.9 | 4.13.12 | 补丁候选;API、静态回退、鉴权与流式接口 |
| `pg` | 8.23.0 | 8.23.1 | 补丁候选;连接、迁移、事务与任务恢复 |
| `typebox` | 1.3.27 | 1.3.34 | 同主版本候选;Agent 工具 schema |
| `vitest` | 5.0.2 | 5.0.3 | 补丁候选;PG 集成实际执行与跳过清单 |
| `react-router` | 7.12.0 | 8.4.0 | **先验证 7.18.4 安全修复**;8.4.0 单独迁移 |
| `@react-router/dev` | 7.12.0 | 8.4.0 | 与另外两个包统一 7.18.4;检查 peer 和构建 |
| `@react-router/node` | 7.12.0 | 8.4.0 | 统一 7.18.4;保留编译所需作用域检查 |
| `lucide-react` | 0.576.0 | 1.49.0 | 图标导出和视觉变更独立评估 |
| `react-day-picker` | 10.0.1 | 10.0.2 | 补丁候选;日期、时区和日历样本 |
| `vite-tsconfig-paths` | 5.1.4 | 6.1.1 | 配合构建工具链评估 |
| `vite` | 7.3.6 | 8.3.1 | 大版本构建迁移;单独核对 Rolldown/Oxc、插件和 Deno 执行 |
已经与 registry latest 相同的依赖包括:TypeScript 7.0.2、OpenWorkflow 0.10.1、React/React DOM 19.3.0、Zod 4.6.5、Kysely 0.29.6、postgres 3.4.9、主项目 playwright-core 1.63.0、Pino 10.3.1、Tailwind 4.3.3 等。声明里的旧范围下限不等于正在运行旧版本,应以 lock 实际解析结果判断。官方 npm 元数据快照见上文本地 registry-snapshot.json
### 2.1 安全审计的实际意义
主项目审计命中 **13 条 React Router 公告:6 high、6 moderate、1 low,0 critical**。完整公告 ID、受影响范围和链接保存在审计证据中。当前 [React Router 配置](../../apps/web/react-router.config.ts) 为 Framework SPA(`ssr: false`),生产由 [Hono 静态托管](../../apps/server/src/http/static.ts) 返回构建文件和 `index.html`,没有部署 React Router SSR/single-fetch/RSC 服务端处理器。
其中 9 条依赖服务端 SSR、single-fetch、manifest 或 RSC 路径,本轮没有发现对应生产调用路径;不能将审计里的 6 条 high 直接表述成 6 个可利用的线上漏洞。3 条客户端导航公告仍需检查不可信 URL,`ssr:false` 不能排除它们。登录 `safeNext` 有过滤,但不能据此宣称任意编码路径都已验收。另 1 条涉及构建时 prerender 的 Location HTML 注入;SPA 构建仍有根预渲染,但当前没有发现外部输入驱动根 loader 重定向的触发路径。本轮没有复现利用。
建议三个 Router 包先统一到 7.18.4,再做 URL/跳转、构建和页面冒烟。该版本包含本轮公告所需的修复;但 `@react-router/dev` 和 `@react-router/node` 的 TypeScript peer 声明为 `^5.1.0 || ^6.0.0`,本项目是 7.0.2,**候选仍需验证,不能称无兼容风险**。来源:[7.18.4 发布](https://github.com/remix-run/react-router/releases/tag/[email protected])、[7.x changelog](https://reactrouter.com/7.18.4/start/changelog)、[SPA 官方说明](https://reactrouter.com/how-to/spa)。
浏览器独立生产 lock 审计命中 **5 个受影响包、16 个不同公告 URL**,npm 聚合严重性是 2 个 high 包、3 个 moderate 包。包为 `adm-zip` 0.6.0、`brace-expansion` 5.0.9、`fast-uri` 3.1.7、`hono` 4.13.5、`ip-address` 10.4.0。包数、公告数与主项目审计的统计单位不同,不相加成「18 条漏洞」。浏览器里的 Hono 与主应用 Hono 是两个独立版本。
应沿浏览器下载/解压、请求解析和可访问入口核对公告条件,再在候选镜像重跑 audit。上游 1.18.0 的 manifest 仍含 `hono:4.13.5` override 等旧约束,不能承诺只更新 camofox tag 就清除全部命中;npm 的聚合 `fixAvailable:true` 也不能替代每条公告修复范围核对。[浏览器审计与原始公告链接](evidence/2026-10-01-dependencies/browser-audit.json)
## 3 程序和镜像版本
| 程序/环境 | 本次实际版本 | 官方查询结果 | 建议 |
| --- | --- | --- | --- |
| 项目应用 | v0.3.7 | 本项目发布基线 | 是否发布新版本取决于验收,不是依赖 latest |
| Deno(宿主/构建) | 2.9.7 | 2.9.7 | 保持 |
| PostgreSQL(部署/开发) | 16.14 | 稳定主版本 18.6;16 分支 16.15 | 18.6 作为迁移目标;耗时较长时先安排 16.15 补丁 |
| 应用 pg_dump 客户端 | 17.11 | 17 分支 17.11;18 分支 18.6 | 升 PG 18 时必须一并升客户端 |
| Caddy(可选 TLS) | 2.11.4 | 2.11.4 | 保持,按入口部署决定是否启用 |
| camofox-browser | 1.17.0 | 1.18.0 | 候选镜像验证后更新,重新审计独立 lock |
| Camoufox 浏览器二进制 | 152.0.4-beta.28 | 152.0.4-beta.30 | 仍是 beta;配套验证,不当作稳定版自动切换 |
| 浏览器 Node | 22.22.3 | 22 LTS:22.23.3 | 同 LTS 补丁候选,随镜像重建验收 |
| 宿主 Node(非应用主运行时) | 24.15.0 | 24 LTS:24.21.0;Current:26.10.0 | 选 24 LTS 补丁,不因 latest 跳 26 |
| 生产 FFprobe/FFmpeg | 9.0.2 | 稳定 9.0.2 | 保持 |
| 宿主 FFprobe | 7.1.3 | 7.1 分支 7.1.5 | 同分支补丁候选,区别于生产容器工具 |
| 默认 qB 演示实例 | 5.1.2 | 5.2.4 | 先核对 API、标签、暂停/删除和多实例隔离;不替用户外部 qB 升级 |
| Docker Engine(宿主) | 29.2.1 | 29.8.2 | 单列宿主维护窗口,涉及其它项目;核对安全发布说明 |
| Docker Compose(宿主) | 5.0.2 | 5.5.1 | 随宿主维护验证 compose 行为 |
| containerd/runc(宿主) | 2.2.1/1.3.4 | 2.4.1/1.5.2 | 跟随选定 Docker 发行包兼容组合,不单独替换二进制 |
| Debian(应用镜像) | 13.5 | 13.7 | 重建候选层并更新发行版补丁,验证工具和权限 |
| Debian(浏览器桌面镜像) | 13.7 | 13.7 | 发行版一致;仍需判断实际包补丁与镜像构建日期 |
| Python(宿主/浏览器) | 3.13.5 | 上游 3.13.16;稳定主版本 3.14.8 | Debian 管理的软件随发行包更新,不能仅凭上游版本判定系统补丁缺失 |
| Selkies/pixelflux | 2.0.0/2.1.0 | 2.0.0/2.1.0 | 保持,随桌面基础镜像验证 |
| labwc(桌面基础镜像) | 0.9.7-dirty | 上游 0.20.2 | vendor 定制构建,跟随基础镜像,不直接替换 |
| socat(开发转发容器) | 1.8.0.0 | 官方最新未完成核验 | 官方查询超时/证书错误,记录未知;开发辅助,不冒充生产主依赖 |
版本来源:[PostgreSQL 支持表](https://www.postgresql.org/support/versioning/)、[Node 官方发行索引](https://nodejs.org/dist/index.json)、[FFmpeg 下载与版本](https://ffmpeg.org/download.html)、[Debian 新闻](https://www.debian.org/News/)、[Python 发行列表](https://www.python.org/downloads/)、[PyPI Selkies](https://pypi.org/project/selkies/)、[PyPI pixelflux](https://pypi.org/project/pixelflux/)。其余上游 GitHub 发布标签、URL 和日期见本地查询快照 registry-snapshot.json 及[补充快照](evidence/2026-10-01-dependencies/additional-metadata.json)。
桌面基础镜像 `ghcr.io/linuxserver/baseimage-selkies:debiantrixie` 当前配置固定摘要 `sha256:f3ca0b10e923a603941a996ea1c3356b9527cc53163259f4e836f61fd4523e12`;本轮远端 manifest 查询仍为同一摘要。该镜像没有可用的 GitHub release/latest 记录,不能把 release API 的 404 当作已过期。供应方说明基础镜像更新可能破坏兼容,应按镜像组合验收。[供应方说明](https://github.com/linuxserver/docker-baseimage-selkies)
应用与部分服务的 `debian:trixie-slim`、`postgres:16-alpine`、`caddy:2-alpine` 等是可移动标签。实际运行版本由已经构建/拉取的镜像决定;修改 tag 或再次构建都应记录摘要与源码来源,不以配置文字直接证明部署已经更新。
### 3.1 PostgreSQL 18 的迁移边界
PG 18 已是正式稳定分支,当前 18.6。16 仍受支持到 2028-11-09,因此 16.15 是有效的过渡补丁,不能将「最新 16」表述为「最新 PostgreSQL」。本轮没有找到必须永久停留在 16 的业务约束,也没有执行 PG 18 兼容验证。[官方版本策略](https://www.postgresql.org/support/versioning/)
当前 [生产 compose](../../docker-compose.yml) 使用 `postgres:16-alpine`,卷挂载 `/var/lib/postgresql/data`。官方 PG 18 镜像默认 `PGDATA` 改为 `/var/lib/postgresql/18/docker`,声明卷改为 `/var/lib/postgresql`;升级需一起核对卷、目录和权限。PG 大版本不能直接复用原数据目录,需要导出/恢复或经过验证的 `pg_upgrade` 方案。[官方镜像 PGDATA 说明](https://github.com/docker-library/docs/blob/master/postgres/README.md#pgdata)
应用现有 `pg_dump` 是 17.11,不能导出 PG 18 服务端;更新数据库时须同步升备份/恢复客户端至 18.x,并确认脚本调用实际新版本。[pg_dump 官方说明](https://www.postgresql.org/docs/current/app-pgdump.html)
候选验收至少包括:独立 PG 18 实例从当前备份恢复;56 个迁移后的业务和 OpenWorkflow schema 核对;真实 PG 集成测试;旧任务、outbox 和操作账本的一致性;备份再次导出和恢复;回滚时使用原 PG 16 数据/快照。不能将「容器健康」作为数据库迁移完成证据。16.15 的补丁升级也应阅读[发布说明](https://www.postgresql.org/docs/release/16.15/),按实际扩展和配置判断是否有额外步骤。
### 3.2 Pi 原生 MCP 的作用
本项目当前嵌入 `pi-agent-core`/`pi-ai` 0.87.1。官方 0.99.2 已提供 MCP 客户端能力,包括 stdio、Streamable HTTP、OAuth、工具发现和调用。新版还发布独立 `@earendil-works/pi-mcp` 和 `@earendil-works/pi-codemode` 0.99.2;官方 Agent 示例说明可以将其包装成 `AgentTool`,进入工具调用钩子。它不只限于命令行产品,可以评估在本项目嵌入层复用。[官方 MCP 文档](https://github.com/earendil-works/pi/blob/v0.99.2/packages/coding-agent/docs/mcp.md)、[Agent SDK 说明](https://github.com/earendil-works/pi/blob/v0.99.2/packages/agent/README.md#mcp-and-codemode)
本轮读取官方 0.99.2 发布 tarball 的声明文件,确认顶层仍导出 `AgentHarness`,没有依据认定必须重写整个 Agent 接入。其它方法签名、会话兼容、provider 导出和 Deno 行为仍未实际编译/运行验证。两个新 SDK 声明 Node `>=22.19.0`,项目主运行时是 Deno,不能用 Node engines 达标代替 Deno 验收。
当前 [MCP 模块](../../apps/server/src/modules/mcp) 是**服务端**:对外提供本项目能力,并复用 API Key 鉴权、审批、幂等与审计。Pi 原生模块是**客户端**:让内部 Agent 调用外部 MCP 服务。应保留对外服务端;若接入客户端,需定义外部工具如何经过已有权限/审批和审计,避免仅升级包版本就默认开放所有外部工具。新版能力的存在也不证明当前项目已经接入它。
### 3.3 Caddy 当前负责什么
当前 [Caddyfile](../../docker/browser-desktop/Caddyfile) 和 compose 中的 `tls` profile 表明,它是可选 HTTPS 入口:证书、TLS、zstd/gzip 压缩,以及向 `app:4380` 的反向代理。`flush_interval -1` 配合代理保持 Agent SSE 和桌面 WebSocket 的流式传输。默认使用内部 CA;设置域名和 issuer 后可以配置 ACME,实际公网条件仍需符合部署配置。
请求链路为「用户浏览器 → Caddy HTTPS → 应用 4380」;桌面路径再由应用鉴权、会话与租约检查后代理到 Selkies 3000。桌面视频 WebCodecs 与剪贴板要求 HTTPS 或 localhost 的安全上下文,因此入口 TLS 有具体用途。Caddy 不承担 Agent 执行、数据库访问或浏览器会话授权。
已有 Nginx/Traefik/统一 Caddy HTTPS 网关时,可由现有入口提供证书和转发,本项目无需另启 Caddy profile;应验证 SSE、WebSocket 和安全上下文仍正常。当前 Caddy 2.11.4 已是查询时最新,无需为了版本检查更新。
### 3.4 浏览器依赖的独立组合
| 包 | 部署 lock | latest | 边界 |
| --- | --- | --- | --- |
| better-sqlite3 | 13.0.1 | 13.0.3 | 原生模块,随 Node 与镜像重建 |
| camoufox-js | 0.11.5 | 0.12.0 | 新版 peer `playwright-core <1.61.0`,与现有 1.62.1 冲突,不能单独更新 |
| express | 5.2.1 | 5.2.1 | 保持 |
| playwright-core | 1.62.1 | 1.63.0 | 配套浏览器与 camoufox-js 验证 |
| prom-client | 15.1.3 | 15.1.3 | 保持 |
| swagger-jsdoc | 6.3.0 | 6.3.0 | 保持 |
camofox 1.18.0 manifest 的直接依赖仍固定 camoufox-js 0.11.5;更新服务版本并不意味着自动更新这个包到 0.12.0。该发布含 Docker postinstall 等调整,应重新检查本地 Docker 补丁是否仍适用,再验证 Cookie 登录、挑战页、页面操作、人工接管、交还、重启、下载捕获和权限。[1.18.0 官方发布](https://github.com/jo-inc/camofox-browser/releases/tag/v1.18.0)
## 4 建议的处理顺序与验收
1. **安全补丁与候选镜像**:三个 Router 包统一 7.18.4;复核浏览器 audit 并验证 1.18.0 候选;若 PG 18 迁移需较长时间,先安排 16.15。主项目其它补丁包可在同一明确范围内更新,重新生成 lock 并跑现有 CI,确认 PG 测试实际执行,再跑有固定数据的页面冒烟。
2. **PG 18.6 专项**:数据库、18.x 客户端、卷结构、隔离恢复与回滚一起处理,形成可审阅迁移方案,再进入部署。
3. **Pi 0.99.2 与原生 MCP 专项**:先验证原有 Agent 接口和会话,再决定外部 MCP 客户端所需场景;复用已有授权、审批与审计;真实模型验证仍按独立范围进行。
4. **宿主运行环境维护**:Docker/Compose/发行版与 Node 补丁单列维护窗口,核对受影响的其它项目与镜像启动,不混在业务依赖安装中。
5. **其它大版本**:Vite 8/Router 8、Passkey 14 和图标等分别评估价值与兼容;当前没有证据要求全部同时迁移。
本轮完成版本与公告核对,**没有完成候选升级验收**。此前 CI 通过仍只对应现有版本。版本工作与 [MoviePilot v3 逐项业务对照](15-moviepilot-v3-comparison.md) 并行准备,不改变对照表中 02 至 14 尚待核对的状态;恢复问题也不能因依赖更新自动视为解决。
-86
View File
@@ -1,86 +0,0 @@
# 全面升级执行记录
执行日期:2026-10-01,Asia/Shanghai。**本次项目全面升级已完成,部署验收通过。** 全面升级发布应用为 v0.3.8(后续整理功能已发布 [v0.3.9](18-organize-release.md)),生产与开发 PostgreSQL 为 18.6,浏览器为 1.18.0,演示 qB 为 5.2.4;宿主与其它项目不在升级范围。下文保留隔离验证和阶段记录,并明确已退休的旧回退资源。升级前版本与官方查询证据见 [版本检查](16-dependency-version-review.md),运行时决策见 [ADR-004](../adr/ADR-004-deno-runtime.md)。
## 范围与状态
本次范围仅为 UHD Media Tool 项目依赖、项目容器及生产/开发数据库。宿主操作系统、Docker Engine、Compose、宿主 Node 和其它项目均不在此次升级范围,也不是本次剩余待办。
发布应用版本为 **0.3.8**。以下展示升级前基线、验证结果与最终部署状态。
| 范围 | 升级前 → 当前 | 隔离验证 | 实际部署 |
| --- | --- | --- | --- |
| 根 workspace 与 Pi | Pi 0.87.1 → 0.99.2;主 workspace 直接依赖更新至查询时 latest | 已安装;主 deno audit 0 公告;全 workspace tsc 与 Deno check 通过,完整 CI exit 0:3264 通过、4 跳过 | 已部署,验收通过 |
| Web 与构建工具 | Router 7.12.0 → 8.4.0、Vite 7.3.6 → 8.3.1 等 latest | 已安装并通过类型检查;完整 CI 与 web 构建通过;最终页面冒烟 83 通过、1 跳过(无订阅详情数据) | 已部署,验收通过 |
| 浏览器独立依赖与镜像 | camofox 1.17.0 → 1.18.0;Node 22.22.3 → 24.21.0;Camoufox beta.28 → beta.30 | 两套最终镜像构建 exit 0;174 个实际安装依赖 audit 0;隔离桌面与真实 BrowserService 25 项验证通过 | 已部署,验收通过 |
| PostgreSQL 生产实例 | 16.14 → 18.6,新卷逻辑恢复 | 隔离演练及正式停写恢复通过;56 迁移齐全,89 表行数与内容指纹全匹配 | 已部署,验收通过 |
| PostgreSQL 开发实例 | 16.14 → 18.6,新卷逻辑恢复 | 88 表行数与内容 fingerprints 全匹配 | 正式迁移与恢复验证完成;旧开发卷已授权清理 |
| 应用备份客户端 | pg_dump/pg_restore/psql 17.11 → 18.6 | 最终镜像构建 exit 0;client18 / server18 隔离备份恢复 E2E 通过,含校验和、映射计数与 Agent 会话恢复 | 已部署,验收通过 |
| Deno、Caddy、生产 ffprobe | 2.9.7、2.11.4、9.0.2 → 保持当前已核验版本 | 与新镜像一起验证 | 已验证并部署;Caddy 固定最新 2.11.4 |
| 项目代理中继 | socat 1.8.0.0 → 1.8.1.3 | 更新项目容器 | 已部署 |
| 应用 Debian 基础层 | 已部署 13.5 → 当前 13.7 补丁层 | 最终应用镜像已更新容器内 apt 最新补丁,实际 Debian 13.7 | 已部署,验收通过 |
| 演示 qB | 5.1.2 → 5.2.4 | 5.2.4 候选真实 API 测试:3 通过、1 跳过 | 已部署 5.2.4;不升级外部用户下载器 |
## 升级前只读部署检查(历史)
本节记录迁移开始前的位置。表中保留旧卷的要求在迁移验收期间已执行;验收后用户授权清理旧版本,旧 PG16 卷现已退休删除,不能再据此回滚。
生产 Compose 项目 `umt` 位于 `/opt/uhd_media_tool`,装配 `docker-compose.yml` 与本机 `docker-compose.override.yml`。检查没有输出环境文件或容器凭证。应用、PG 与浏览器健康。
| 对象 | 当前持久化位置 | 升级要求 |
| --- | --- | --- |
| 生产 PG16 | `umt_umt-pg-data` → `/var/lib/postgresql/data` | 保留旧卷;PG18 使用另一个明确命名的新卷 |
| 开发 PG16 | `umt-dev_umt-pg-data` → `/var/lib/postgresql/data` | 独立迁移,不与生产库共卷 |
| 应用数据 | `umt_umt-data` → `/data` | 保留备份、Agent 会话与导出;候选实例使用隔离副本 |
| 媒体目录 | `/opt/umt-demo/media` → `/media`;`/opt/umt-demo/downloads` → `/downloads` | 保持路径映射;隔离验证不写真实挂载 |
| 浏览器登录状态 | `umt_umt-camofox` → `/root/.camofox` | 保留旧卷与旧镜像回退证据;候选实例先使用隔离副本 |
| 桌面配置 | 浏览器另挂匿名 `/config` 卷 | 重建时记录并保持其绑定,不能只检查登录状态卷 |
生产数据库已应用 56 条迁移,最新 `0058_scrape_record`。检查时现有备份目录为 `/data/backups/20260929T103808Z`,manifest 声明服务端 16.14、pg_dump 17.11;初始只读检查只确认该历史备份存在和版本;随后主代理已将生产 dump 恢复到隔离 PG18,但尚未确认所用 dump 与该历史目录是同一份。切换前需生成新的停写备份。宿主数据所在文件系统约有 1023 GiB 可用,不代替目标数据大小与峰值空间核算。
## PostgreSQL 16 → 18 执行与回滚边界(迁移阶段历史)
以下步骤描述验收前的迁移与回退安排,已完成迁移。旧镜像、旧 PG16 卷及旧回退脚本已在最终验收后按用户授权删除;当前仅支持用保留的新版 PG18 备份恢复 PG18,不再承诺第 6 步的旧卷回退。
1. 保存旧应用镜像 ID、Compose 配置及 PG16 镜像 ID;保留原主密钥并避免写入文档。确认数据库、Agent 会话、映射清单备份和校验和完整。
2. 在隔离 PG18 新卷演练逻辑恢复;核对表、扩展、迁移、映射行数以及任务引擎 schema,运行 PG18 集成与编译产物检查。候选应用关闭自动化并避免连接真实下载器、模型与站点。
3. 正式切换前停止应用写入,再生成最终一致的数据库及会话备份。恢复到新的 PG18 卷,保留原 PG16 卷不变;新应用先以只读对账状态启动,确认迁移、健康、映射、凭证和挂载后再决定恢复自动化。
4. PG18 官方镜像默认 `PGDATA=/var/lib/postgresql/18/docker`,卷应挂在 `/var/lib/postgresql`,不能将 PG16 数据目录直接交给 PG18 启动。[官方镜像说明](https://hub.docker.com/_/postgres)
5. 升级应用镜像内备份工具到 18 客户端,避免 17 版 pg_dump 无法备份 18 服务端。恢复脚本当前过滤 `SET transaction_timeout` 以兼容旧服务端,更新说明并分别验证旧备份恢复与新库备份。
6. 如切换验收失败,停止新应用和 PG18,恢复旧配置、旧镜像与 PG16 原卷。此回滚返回最终切换备份时点;PG18 上线后产生的新写入不能自动落回旧卷,需另行处理,不能承诺零数据损失的降级。
不得用 `compose down -v`、卷 prune 或覆盖旧数据目录完成迁移。最终部署镜像和清理对象见文末 JSON 证据。
## 范围外检查说明
前期只读检查曾记录宿主版本、软件路径及共享 Docker daemon 上的其它项目;这些检查用于确认项目容器变更的边界,不构成本次宿主升级计划。此次未升级或重启宿主服务,也不更新其它项目。
## 安装与验证进展
依赖安装首次因 Deno 2.9 默认 24 小时 `minimumDependencyAge` 拒绝刚发布的 Biome 等包。按用户全面升级至最新的明确要求,本次安装使用一次性 `--minimum-dependency-age=0`;不写入永久禁用配置,依赖仍精确锁定。这是安装策略例外,不代表依赖已通过安全审计或兼容检查。主代理已拉取 `postgres:18.6-alpine`,并完成隔离库恢复;生产迁移现已完成。qB 适配器已有 5.2 的 HTTP 204 登录与 `QBT_SID` 测试,旧 override 的“只能 5.1.2”注释待同步纠正。
主 workspace 共 58 个外部直接依赖已安装;除 `@types/node` 保持官方 24.x 最新 24.19.0,其余更新至查询时 latest。主 `deno audit` 为 0 公告,不涵盖浏览器独立 lock、系统包或实际业务兼容。全 workspace tsc 与 Deno check 通过。首次完整 CI 停在根 deno.json 格式错误,修复后完整 CI **exit 0,3264 通过、4 跳过**;主 npm 安全审计为 0 公告。最终应用镜像 `uhd-media-tool:v0.3.8` 构建 **exit 0**,镜像 ID 为 `sha256:6f4f73d8ea53057cdf5bf4aa2904a360ea953adb1270a7b9f783a757b7e70559`,已正式部署并验收通过。最终页面冒烟 **83 通过、1 跳过**,跳过原因为没有订阅详情数据。
Pi 初次测试因 TypeSafe provider 与 OpenAI 原生 ChatGPT OAuth 新增导致旧计数断言过期,合同已随升级更新为 42 providers / 9 OAuth,并纳入上述通过的完整 CI。另对 **35 份旧 Pi JSONL 会话完成兼容检查并通过**。Pi 提供独立的 `@earendil-works/pi-mcp` 客户端 SDK,本次升级 `pi-agent-core` 不会自动接入外部 MCP 工具;该功能拓展不属于本次依赖升级交付。
qB 5.2.4 候选的真实 API 兼容测试 **3 通过、1 跳过**;该结果不代表用户 PT 下载全链路已经验收,演示实例随后已正式切换并完成旧密文凭据连接验证。
浏览器采用 `camoufox-js` 0.12 与 Playwright 1.60.0 的兼容组合:上游协议对 `viewport.isMobile` 有限制,不能简单提升该独立 Playwright 至主 workspace 的 1.63.0。这是浏览器独立依赖的协议兼容例外,主 workspace 的 Playwright 不降级;已结合实际镜像与安装依赖审计验证。浏览器目标 Node 改为 24.21.0。
浏览器最终纯截图镜像 1.18.0(ID 前缀 `92ea962`)和桌面镜像(ID 前缀 `eb6c8e`)均构建 exit 0。实际安装的 174 个依赖安全审计 0 公告;MCP SDK 1.31 initialize 与原生 SQLite 3.53.4 检查通过。此处 MCP initialize 验证浏览器依赖兼容,不表示应用智能体新增外部 MCP 工具入口。
生产浏览器 profiles/config 的独立副本完成重启前、重启及重启后隔离检查,授权、固定身份保持、cookies、PNG、可信鼠标与 profile 隔离均通过。实际 BrowserService 的 setup / a / b / idle / c / d 六阶段 **25 项通过**,1.18 的原 10 项能力已登记;相关回归 14 个文件、100 个测试以及登记相关 19 个测试通过。SIGKILL 后最近一次落盘以来的新登录会丢失,验证确认能明确识别,不能据此宣称无损恢复。URL 恢复证据缺口见 [工程化检查第 6 节](14-engineering-review.md),本次没有业务修复。
开发 PG16 → 18.6 正式迁移已完成,88 表行数与内容 fingerprints 全匹配,旧开发卷在验收期间保留,随后按用户授权清理。client18 / server18 隔离备份恢复 E2E 的校验和、映射计数与 Agent 会话恢复通过。生产停写备份及 PG18 迁移完成,89 表的源库与恢复库行数、内容 fingerprints 全一致,56 条迁移齐全。原主密钥与全部非数据库持久挂载(包括浏览器匿名 `/config`)完全一致;旧密文凭据通过新版应用成功连接 qB 5.2.4。API、IO worker 和 Agent worker 均以 UID 10001 存活,HTTP / HTTPS 健康,映射计数吻合,验收期间 error 级日志为 0。源库只读对账设置原为 false,迁移验证后已恢复 false,应用正常重启且健康。最终发布镜像的页面冒烟再次通过:83 通过、1 跳过。
新版生产备份 `/data/backups/20261001T072536Z` 已在隔离 PG18 完成恢复,校验和、映射计数、89 表一致;35 个 Agent 会话文件逐字节 hash 一致。新版开发备份 `.local/backups/dev-pg18-20261001.dump` 也完成隔离恢复,88 表 fingerprints 一致。
执行证据:[版本与锁文件](evidence/2026-10-01-dependencies/upgrade-locks-and-direct-versions.json)、[验证汇总](evidence/2026-10-01-dependencies/upgrade-verification.json)、[最终部署容器](evidence/2026-10-01-dependencies/deployed-containers.json)、[授权清理记录](evidence/2026-10-01-dependencies/upgrade-cleanup.json)。
## 验收后的授权清理与当前恢复路径
用户在验收通过后明确授权:“确认没问题的话,老的备份和老的版本都可以干掉的”。已清理 5 个隔离容器、8 个卷(2 个旧 PG16 卷及 6 个隔离卷)、14 个旧镜像标签,以及旧数据归档、PG16 dump、旧环境配置与回退脚本。旧备份 `20260929T103808Z` 和最终停写阶段备份 `20261001T072253Z` 也已删除。`uhd-media-tool:latest` 已指向新的 v0.3.8 镜像。
当前仅保留上述新版生产和开发 PG18 备份作为恢复依据。其它项目仍使用的共享 `postgres:16-alpine` 镜像保留;宿主服务和其它项目未升级。旧镜像/旧 PG16 卷回退路径已退休,发生故障时应使用新版备份恢复 PG18。
本次依赖、容器与数据库升级及部署验收完成;已有业务和工程问题仍以 [工程化检查](14-engineering-review.md) 与 MP02–MP14 的待办为准,本次没有继续功能开发,也不据此宣称全部业务无缺陷。
-126
View File
@@ -1,126 +0,0 @@
# 媒体整理与下载发布记录
当前生产应用为 **v0.3.13**,健康检查通过,见下方新增发布记录。
## v0.3.9 媒体整理发布
2026-10-01,Asia/Shanghai。用户授权部署,应用发布为 **v0.3.9**,健康检查通过。分类规则、整理来源、目录快照、手动存量和文件管理入口一起发布。详细行为见 [整理手册](../runbook/organize.md)。
## 发布范围与来源
- 版本:workspace / server / web 0.3.9;前端 `version.json` 为 v0.3.9,健康接口为 0.3.9。
- 源码:HEAD `8013196` 加用户已授权的工作区修改,未创建提交。镜像标记 `8013196-dirty-121ab989fb4a427b`;对应源码文件指纹 `121ab989fb4a427b`,完整值保存在本地发布证据。
- 镜像:`uhd-media-tool:v0.3.9`,ID `sha256:4db9dda97358a5adc5e6f3472d88b4cd05d5522eb6183663f2659d86b5401163`。
- 迁移:`0059_organize_source_snapshot`;新增持久快照表,已有 settings 读取时补分类 YAML 和空来源列表。发布不会扫描或导入存量。
- 本次只替换 app 容器。PostgreSQL、qB、浏览器、Caddy 和宿主程序没有更换。
## 发布前检查
引擎 pending / running / sleeping、业务 task queued / running / waiting、整理记录、刮削记录及 Agent 在途数量均为 0。备份完成于 `/data/backups/20261001T095557Z`,数据库、映射和 Agent 会话的 SHA256 校验全部通过。
最终 web 回归 26 文件 / 232 项通过;来源匹配新增根范围、目录边界、全部所选项、最长路径和多候选测试。typecheck、Biome 与 production build 通过。服务端验证范围和完整回归的阶段性失败及复验口径见 [业务对照 §4.3](15-moviepilot-v3-comparison.md#43-本次验证记录)。Docker 构建完成冻结依赖安装、设计 token 检查、web build、server compile 和编译二进制模块图自检。
## 部署后验证
- `umt-app-1` healthy,运行镜像 ID 与发布镜像一致。
- HTTP 4380 与 Caddy HTTPS 健康均为 ok;API / IO worker / Agent worker 存活,UID 10001。
- `0059` 已登记,快照表存在;`scanOrganizeSource@1` 进入 maintenance worker,`organize.sources` 可用并启用。
- 通过现有管理员登录验证分类配置、整理来源、settings 和文件根读取;华语电影、澳大利亚兜底欧美电影、TV 综艺三个预览结果正确。
- 文件管理、整理记录、设置三个 SPA 地址返回正常;部署静态资产包含文件管理策略选择入口。
- 验证时来源配置仍为空,留待用户手动选目录;没有新增来源配置或对用户媒体执行整理。
- 部署后验证窗口内 error 级应用日志为 0。
本次没有运行浏览器截图或完整交互冒烟,用户可以刷新现有页面,进入「设置 → 媒体库与目录」检查分类规则与来源配置,再从文件管理器进入同一整理向导。
本地证据:`.local/deploy-organize-20261001/release.json`、`http-check.json`;构建日志 `/tmp/umt-organize-docker-build.log`。部署配置的版本和源码标记已更新,当次部署配置使用 v0.3.9;下方 v0.3.10 发布已更新配置。
## v0.3.10 手动下载与入库发布
2026-10-01 23:18,Asia/Shanghai。用户授权部署,本批独立手动下载、串流外详情页下载、TMDB/Local 元数据兜底、正常多版本/一次性洗版与订阅完成口径调整已上线。实现、业务边界与回归结果见 [MP 对照 §5.6](15-moviepilot-v3-comparison.md#56-本轮实现与验收已部署-v0310)。
- 应用与前端版本:0.3.10/v0.3.10。镜像 `uhd-media-tool:v0.3.10`,ID `sha256:cbf3081281c75f4e60dba9a9ce18de63b0c5665c66b2b133baebaa7f05aabdd2`。
- 源码标记:`8013196-dirty-5ecf10d5b4f89432`,HEAD 加用户授权的工作区修改;1552 个源码文件指纹记录保存在本地,部署后逐项复核未变化。没有创建提交。
- 迁移:`0060_manual_acquisition` 已执行;计划允许无 Slot,下载任务新增 `import_plan_id` 外键并使用 `ON DELETE SET NULL`。
- 升级前引擎、业务任务、整理、刮削、Agent 在途均为 0;备份 `/data/backups/20261001T151510Z` 的数据库、映射和 Agent 会话 SHA256 校验通过。
- Docker 构建的冻结依赖、设计 token、web production build、server compile 与空环境二进制自检通过。
- 只替换 app;PostgreSQL、qB、浏览器、Caddy 等其他容器 ID 保持不变。
部署后 app healthy,运行镜像与构建镜像一致;HTTP/HTTPS 返回 ok,前后端版本一致,API、IO 与 Agent 三个进程运行于 UID 10001。管理员登录及分类/来源/settings/文件根/下载任务/浏览器会话读取通过,3 个分类预览符合原配置。能力目录为 42 项,包含 `metadata.resolve_organize`;线上 OpenAPI 包含详情页资源解析与手动入库选项。
使用 Chromium 在真实生产入口登录,下载面板、计划列表、整理记录、文件管理、设置及媒体库共 6 个页面冒烟通过,无未捕获页面异常或失败 API,并保存截图。部署验证窗口内 error 级应用日志为 0;计划、下载、媒体资产、整理记录和订阅数量均保持一致。没有以验收为由提交下载、洗版或移动用户媒体;实际 PT 下载、外部模型与入库执行仍以真实任务验收为准。
本地证据:`.local/deploy-manual-20261001/release.json`、`source-manifest.json`、`http-check.json`、`ui-check.json` 及页面截图。构建日志 `/tmp/umt-manual-docker-build.log`。`.env.deploy` 已固定 v0.3.10 及本次源码标记,后续普通 Compose 启动使用本版本。
### 4380 自签 HTTPS 入口调整
随后按用户测试要求,将 4380 的宿主端口交给 Caddy:`https://服务器地址:4380` 使用现有 Caddy 内部 CA 证书,443 HTTPS 入口继续保留;app HTTP 仅映射 `127.0.0.1:4381`,容器内仍监听 4380。仅重建 app/Caddy 容器,无镜像升级或新增迁移。配置写入本机 override 与 `.env.deploy`。
4380/443 HTTPS 健康、管理员接口、版本一致及新接口描述验证通过;真实 HTTPS 下 6 个生产页面冒烟通过,无页面异常或失败 API,Chromium 报告 `isSecureContext=true`。证据为同目录 `https-http-check.json`、`https-ui-check.json`、`https-switch.log`,汇入 `release.json`。
## v0.3.11 搜索详情与桌面黑边修复
2026-10-01 23:58,Asia/Shanghai。搜索资源标题现在可以点击,打开与站点工作台相同的详情抽屉;加载完整资源后复用已有详情缓存、按需抓取、简介/规格/截图/文件展示、下载选项与 Agent 入口。抽屉关闭后搜索词、筛选和分页保留。读取失败显示原有错误与重试,打开详情不会提交下载。
- 镜像 `uhd-media-tool:v0.3.11`,ID `sha256:fa54237f250c92bee1436912275cb0b8da97d1b7c9bf9489dc1152144908b944`;源码标记 `8013196-dirty-a2f3f773724cbde4`,1553 个文件的指纹部署后复核一致。
- 前端 typecheck、Biome、232 项测试通过;Docker 内设计 token、web build、server compile 与空环境二进制自检通过。真实 React 组件验证点击标题、未缓存自动抓取一次、缓存不额外抓取、失败显示与详情下载选项,无实际提交。
- 切换前业务、引擎和 Agent 在途为 0;备份 `/data/backups/20261001T155308Z` 的数据库、映射与会话校验通过。仅替换 app,数据库迁移仍为 `0060`。4380/443 自签 HTTPS 入口保留。
- app healthy,前后端版本一致,42 项能力及管理员接口检查通过;6 个 HTTPS 生产页面无异常或失败 API。另使用生产构建、真实缓存资源详情 API 和受控搜索结果验证点击后的简介展示及下载选项;未触发下载。验证窗口内 error 级应用日志为 0。
桌面黑边修复先于应用发布部署。实际视频与 iframe 都为 1280×900,黑边是浏览器窗口未最大化造成:底座初始化脚本把快捷键 XML 按行注释,产生无效 XML,连带最大化规则无法读取。内置完整 `/defaults/umt-labwc.xml` 在底座初始化后覆盖配置,保留既有快捷键/桌面菜单行为,并为窗口设置 `Maximize` 与 `serverDecoration="no"`。配置语法对应 [labwc 窗口规则](https://labwc.github.io/labwc-config.5.html)。
浏览器修复镜像 `umt-browser-desktop:1.18.0-umt.1`,ID `sha256:cf31c83524b260b22c65f39a759771e6861e65a951c89c0382043794aebfadb6`,基于当时运行的 1.18.0 镜像只替换初始化脚本与 XML,基础依赖保持一致;完整构建入口也已包含这两份源码。`.env.deploy` 固定修复标签。浏览器容器 healthy,登录状态卷保留;真实串流前后截图中心线上下黑边从 65/66 像素变为 0/0,画面铺满,视频尺寸保持 1280×900。
发布证据:`.local/deploy-search-detail-20261001/` 中的 `release.json`、`source-manifest.json`、API/页面/搜索检查与截图。桌面证据:`.local/desktop-sizing/` 中的修复前后截图、测量值、有效 XML、补丁构建与发布记录。
## v0.3.12 列表分页、搜索历史与首页概况
2026-10-02,本次将表格和卡片列表统一为 `Pagination` + `useListPagination`,自定义详情列表复用 `PaginatedList`。每页 10/20/50/100 条,默认 50 条,浏览器记住选择;筛选和目录变化回到第一页。支持页码、上一页/下一页、已知集合的首页/末页。游标接口仍按需拉取,返回上一页使用缓存;没有总数时准确写「已加载」,不将已加载数量当完整总数。
覆盖整理记录、审计、下载/业务任务、任务流、媒体库/身份/别名、订阅、站点卡片与资源、搜索作品候选与资源、待处理、智能体历史、媒体详情季集/版本/历史、整理预览、来源配置、计划与运行历史、设置里的规则/偏好/路径映射/Passkey,以及文件管理器和目录选择小窗。表格默认共用分页,订阅和整理批量勾选明确按本页操作,跨页已选项目保持可见计数。少量嵌套季集或版本(不超过 10 条)保留展开列表,避免同一项内重复出现翻页工具;首页近期摘要和导航通知预览不做完整列表分页,仍链接到完整页面。
小规模配置 API 跟随游标取齐,保留原返回形状;主要历史列表继续按需游标请求。作品详情的下载、整理和未提交成功的失败计划取消原先 100/100/50 条读取上限,前端分页呈现;这是现有单作品详情接口的完整查询,不新增多套 API。定时运行记录后端只保留最近 50 次,本次分页覆盖其实际保存的记录。聚合作品搜索仍是本地与 TMDB 的候选结果集合(每来源至多 50),资源列表走完整的 `/search/releases` 游标;未扩展 TMDB 跨页搜索业务。目录树接口原有数量截断提示保留,右侧文件列表仍可完整按游标浏览。
搜索历史在当前浏览器按账号隔离保存最近 30 个已搜索关键词,重复搜索前移,搜索页和 Ctrl/⌘ K 共用,支持点击重搜、删除单条与清空;输入过程不写入历史。通知铃铛、角标、浮层、条目、严重度、加载/空/错误状态共用控制台按钮和样式,修正手机边距与暗色角标对比。
首页新增存储和下载器两张概况卡:媒体库/下载目录所在磁盘的剩余、已用、总容量、占用比例与健康状态;各下载器连接状态和上下行速度、全系统进行中任务数。容量按所在文件系统展示,不把共用磁盘的多个目录相加;进行中数量包含等待计划、暂停和异常任务。不可读容量和离线速度显示未知。存储每 60 秒更新、下载器每 15 秒更新,首页只请求一条面板记录,速度和计数由完整汇总提供。详情分别链接运行状态和下载面板。
验证证据:`.local/pagination-release-20261002/` 的类型/测试/格式/浏览器结果、页面截图和构建发布记录;`.local/notification-style/` 的通知前图、首页手机/桌面与离线/容量未知检查。前端 29 文件 242 测试通过;服务端媒体版本 4 项通过,新增跨旧上限的 105 条下载、105 条整理和 51 条失败计划验证。浏览器用受控审计响应验证 10/100 条切换、跨游标下一页、缓存上一页、末页和筛选复位,搜索历史验证刷新、删除、重搜、快捷面板和清空;未执行实际下载或整理。
整理使用方式另完成 MP 固定源码与本项目实际页面对照,结论见 [对照第 6 节](15-moviepilot-v3-comparison.md#6-整理使用方式与文件管理器专项研究2026-10-02)。来源默认一致性、成功移动后重整、向导勾选语义和空配置入口仍待讨论;本次只调整分页,不实施这些业务改动。
## v0.3.13 统一整理弹窗与队列
2026-10-02,用户确认 MP 方案并授权完成本轮与部署。手动、文件管理、下载详情和历史重整共用单表单弹窗,支持电影/剧集、TMDB 搜索或 ID 定位、来源配置、复制/移动、目标路径、本次刮削及可展开预览。只提供「加入整理队列」,后台受理后关闭;部分受理时移除已入队文件,剩余文件再预览。整理页分队列与记录,直接复用原任务引擎;默认队列也展示尚未生成整理记录的下载核验任务。
成功历史跳过遵循 MP:手动入口有成功历史才显示开关,默认关闭,预览与执行同参数;失败/部分成功记录不计入成功数。成功移动历史从媒体库目标重整,失败/复制仍使用源。刮削开关冻结到既有计划,TMDB/Local 与恢复执行均遵守本次值。通知数字角标改为主题 primary 配色,每页条数使用现有 Select。
无新增迁移,沿用 `0060_manual_acquisition`;只发布应用镜像,浏览器、Caddy、PostgreSQL、qB 和宿主版本保持当前状态。部署配置固定 `v0.3.13` 及本次源码指纹。备份、镜像、健康和前后端版本证据记录于 `.local/organize-final-20261002/release.json`。
前端全套与相关服务端回归通过;服务端用隔离数据库/临时文件验证移动后重整、成功跳过、配对字幕、TMDB/Local 刮削开关和任务恢复。类型与格式检查通过。浏览器验证统一 Select、成功历史开关、预览失效、过时 TMDB 回包、受理期关闭行为、部分受理后只提交剩余文件以及手机布局,所有整理写请求均使用拦截响应,未对用户媒体执行整理、移动、替换或刮削。生产构建与应用发布检查同目录保存。
部署后核验:前端 250 项、相关服务端 64 项测试通过;4380 HTTPS 的前后端版本一致,应用 healthy,启动 error 级日志为 0,其它服务容器未替换。生产入口 13 个主页面、媒体详情及整理记录详情冒烟通过,无页面异常或失败 API;刮削队列与关联记录、桌面/手机弹窗和分页交互验证通过。备份 `/data/backups/20261001T184721Z` 三个文件的 SHA256 校验通过。发布文档在验收后补充,此文件不进入 Docker 构建上下文。
业务对照与决策见 [第 6.4/6.5 节](15-moviepilot-v3-comparison.md#64-用户反馈与后续讨论2026-10-02),使用方式见 [整理手册](../runbook/organize.md)。
## v0.3.14 原地入库(代码完成,未部署)
2026-10-02。用户要求文件管理整理补齐原地处理,参考 MP,并记录改动。新增共用整理弹窗「原地入库 · 保留位置与名称」:已登记媒体库内的选择默认原地登记,可切回复制/移动;下载缓存维持原有方式。源目标相同,隐藏搬运目标/命名/替换选项,识别和预览后仍只加入既有队列。
执行只读核验源文件,不复制、移动、改名、创建目标目录或清理临时文件;复用逐文件账本和最终事务登记视频、字幕、位置、版本与季集覆盖,同作品既有资产不重复创建。结果和重新整理入口识别 `in_place`。不新建目录绑定,不原地洗版;已登记为其它作品或计划后改变的文件会提示失败。可选刮削只针对本次成功主视频,不扫描整个同目录/作品其它版本;只登记库根不写元数据。
MP 固定源码没有独立原地入库模式,其文件管理提供就地刮削;本项目原有刮削仅创建作品条目和元数据文件,未登记视频资产。此改动补齐本工具的资产管理,参考依据见 [业务对照 §6.6](15-moviepilot-v3-comparison.md#66-原地入库补充2026-10-02v0314-待部署)。
迁移 `0061_organize_in_place` 为整理记录 action 和文件结果 mode 增加 `in_place`,未执行生产迁移。workspace/server/web 源码版本准备为 0.3.14;`.env.deploy` 与当前生产保留 v0.3.13。
验证:前端 30 文件/252 项测试,整理计划 22 项、枚举映射 22 项、执行只读核验 3 项通过;前后端类型检查(含新增服务端测试)、格式和差异检查通过。服务端编译及编译二进制模块图自检通过。新增 6 项隔离 PostgreSQL 集成用例覆盖实际文件、重复登记、字幕、季集、刮削范围、变更/归属冲突和只读库根,但当前会话不能连接测试数据库,全部跳过,不计为通过。
前端 production build 的客户端与服务端打包已完成,最后 React Router 预渲染需要监听 localhost,被当前沙箱以 `listen EACCES` 拒绝,完整构建未通过。Docker socket 访问同样被拒绝,因此未构建发布镜像、未部署、未进行生产浏览器验收。早期 Deno Vitest forks/threads 受运行时兼容问题影响;前端与执行核验改用 Node 复验通过,其它结果按实际日志记录。没有修改用户实际媒体文件。
证据位于 `.local/in-place-20261002/`。待有正常部署权限时,先补跑这 6 项数据库用例和完整前端构建,再按现有发布流程备份、检查在途任务、构建新镜像并仅更新 app。不得直接把旧 v0.3.13 镜像改标签当作新版。
### 2026-10-02 补充验证与修复(仍未部署)
开发库补齐迁移至 `0061` 后补跑:原地入库 6 项数据库用例全部通过(其中 1 项原先断言调用了文件管理装配未挂载的整理记录接口,改为直接查记录表)。全 workspace 类型检查、Biome 与各包测试通过:server 840(1 跳过)、adapters 464(3 跳过)、contracts 1053、domain 658、agent 82、db 21、mapping 22、tasks 17、testkit 5、web 252。
同批修复:识别升级链不再用更差的结果(`no_match`)顶替原有歧义候选,与 MP 识别兜底"只在前一步没有结果时采用"一致;作品目录刮削对 `bluray_dir` 资产直接以原盘目录为刮削根(MP 同做法);`metadata.resolve_organize` 补登记到入库排障 Skill 与能力地图;`db/src/types.ts` 按迁移重新生成,contracts 整理动作枚举顺序与数据库对齐。前端统一通知铃铛弹层与站点卡片样式。前端完整 production build 与镜像构建仍按上文发布流程执行。
@@ -1,466 +0,0 @@
{
"jsr_path": {
"scope": "std",
"name": "path",
"latest": "1.1.6",
"versions": {
"0.204.0": {
"createdAt": "2024-04-24T06:22:04.054652Z"
},
"0.219.0": {
"createdAt": "2024-03-08T07:11:36.056284Z"
},
"1.1.6": {
"createdAt": "2026-06-30T10:24:29.944663Z"
},
"0.213.0": {
"createdAt": "2024-01-26T08:16:46.618940Z"
},
"1.0.0-rc.3": {
"createdAt": "2024-07-02T11:46:57.873141Z"
},
"0.218.1": {
"createdAt": "2024-03-01T03:44:36.998490Z"
},
"0.212.0": {
"createdAt": "2024-01-16T10:22:10.013057Z"
},
"1.1.5": {
"createdAt": "2026-05-26T09:57:13.079190Z"
},
"0.205.0": {
"createdAt": "2024-04-24T06:20:40.855663Z"
},
"0.198.0": {
"createdAt": "2024-04-24T06:54:01.469929Z"
},
"1.0.3": {
"createdAt": "2024-08-26T08:37:13.530991Z"
},
"1.0.0-rc.4": {
"createdAt": "2024-07-09T06:17:02.990512Z"
},
"0.211.0": {
"createdAt": "2024-04-23T14:29:25.560075Z"
},
"1.1.3": {
"createdAt": "2025-11-17T08:24:43.343051Z"
},
"0.223.0": {
"createdAt": "2024-04-16T11:36:08.884192Z"
},
"1.1.4": {
"createdAt": "2025-12-24T07:18:31.658575Z"
},
"0.216.0": {
"createdAt": "2024-02-16T06:12:39.561859Z"
},
"0.214.0": {
"createdAt": "2024-02-06T09:55:45.141761Z"
},
"0.225.2": {
"createdAt": "2024-06-03T06:47:44.310028Z"
},
"0.218.0": {
"createdAt": "2024-02-29T23:28:16.999233Z"
},
"0.196.0": {
"createdAt": "2024-04-25T05:40:21.193428Z"
},
"0.203.0": {
"createdAt": "2024-04-24T06:33:50.897004Z"
},
"0.197.0": {
"createdAt": "2024-04-25T05:37:18.401737Z"
},
"0.215.0": {
"createdAt": "2024-02-08T21:57:13.585426Z"
},
"0.213.1": {
"createdAt": "2024-01-26T19:07:59.609934Z"
},
"1.0.7": {
"createdAt": "2024-10-24T10:27:26.548716Z"
},
"1.0.8": {
"createdAt": "2024-11-01T10:38:03.318975Z"
},
"1.0.6": {
"createdAt": "2024-09-12T17:00:19.414985Z"
},
"1.0.0": {
"createdAt": "2024-07-12T10:50:37.927014Z"
},
"0.225.1": {
"createdAt": "2024-05-16T11:15:16.445309Z"
},
"0.217.0": {
"createdAt": "2024-02-22T05:47:42.790539Z"
},
"0.207.0": {
"createdAt": "2024-04-24T06:15:28.582574Z"
},
"1.0.5": {
"createdAt": "2024-09-12T10:26:27.814650Z"
},
"1.0.2": {
"createdAt": "2024-07-26T10:49:53.428735Z"
},
"1.0.1": {
"createdAt": "2024-07-19T11:25:58.680433Z"
},
"0.200.0": {
"createdAt": "2024-04-24T06:44:40.616320Z"
},
"1.0.9": {
"createdAt": "2025-04-24T06:23:27.551197Z"
},
"0.224.0": {
"createdAt": "2024-04-25T07:39:27.314783Z"
},
"0.221.0": {
"createdAt": "2024-03-28T00:04:19.321412Z"
},
"1.1.2": {
"createdAt": "2025-08-13T12:38:19.728538Z"
},
"0.210.0": {
"createdAt": "2023-12-22T11:26:40.901028Z"
},
"0.220.1": {
"createdAt": "2024-03-15T02:40:27.584710Z"
},
"0.206.0": {
"createdAt": "2024-04-24T06:19:17.889544Z"
},
"1.1.1": {
"createdAt": "2025-07-01T07:43:30.791791Z"
},
"0.222.1": {
"createdAt": "2024-04-11T11:12:26.247290Z"
},
"1.0.0-rc.2": {
"createdAt": "2024-06-17T09:56:55.865334Z"
},
"0.219.1": {
"createdAt": "2024-03-08T09:39:27.810189Z"
},
"0.218.2": {
"createdAt": "2024-03-01T04:33:16.720737Z"
},
"1.0.0-rc.1": {
"createdAt": "2024-06-06T11:30:54.416826Z"
},
"1.0.4": {
"createdAt": "2024-09-04T10:41:45.785792Z"
},
"1.1.0": {
"createdAt": "2025-05-27T07:30:38.330934Z"
},
"0.225.0": {
"yanked": true,
"createdAt": "2024-04-29T16:05:05.405226Z"
},
"0.208.0": {
"createdAt": "2024-04-24T06:08:36.973193Z"
},
"0.199.0": {
"createdAt": "2024-04-24T06:50:53.628212Z"
},
"0.201.0": {
"createdAt": "2024-04-24T06:39:27.098273Z"
},
"0.222.0": {
"createdAt": "2024-04-11T05:45:10.832896Z"
},
"0.209.0": {
"createdAt": "2024-04-23T15:22:26.240920Z"
},
"0.202.0": {
"createdAt": "2024-04-24T06:37:25.846681Z"
}
}
},
"jsr_internal": {
"scope": "std",
"name": "internal",
"latest": "1.0.14",
"versions": {
"0.197.0": {
"createdAt": "2024-04-25T05:37:04.008388Z"
},
"0.202.0": {
"createdAt": "2024-04-24T06:37:17.278025Z"
},
"1.0.9": {
"createdAt": "2025-07-01T07:43:25.269328Z"
},
"1.0.10": {
"createdAt": "2025-07-22T13:14:56.088744Z"
},
"0.216.0": {
"createdAt": "2024-02-16T06:12:14.762854Z"
},
"1.0.0": {
"createdAt": "2024-05-22T11:08:19.499017Z"
},
"0.215.0": {
"createdAt": "2024-02-08T21:56:50.207994Z"
},
"0.205.0": {
"createdAt": "2024-04-24T06:20:31.113119Z"
},
"0.225.1": {
"createdAt": "2024-05-16T11:15:06.195844Z"
},
"0.225.0": {
"yanked": true,
"createdAt": "2024-04-29T16:04:59.888254Z"
},
"0.214.0": {
"createdAt": "2024-02-06T05:50:11.102748Z"
},
"0.213.1": {
"createdAt": "2024-01-26T19:07:53.131686Z"
},
"0.201.0": {
"createdAt": "2024-04-24T06:39:20.704773Z"
},
"0.196.0": {
"createdAt": "2024-04-25T05:40:15.000809Z"
},
"1.0.5": {
"createdAt": "2024-11-01T10:38:01.932266Z"
},
"0.207.0": {
"createdAt": "2024-04-24T06:15:05.941336Z"
},
"1.0.6": {
"createdAt": "2025-03-25T07:17:06.921973Z"
},
"0.199.0": {
"createdAt": "2024-04-24T06:50:41.464255Z"
},
"1.0.13": {
"createdAt": "2026-04-20T13:57:46.913983Z"
},
"0.206.0": {
"createdAt": "2024-04-24T06:19:08.538414Z"
},
"0.208.0": {
"createdAt": "2024-04-24T06:08:11.707171Z"
},
"1.0.8": {
"createdAt": "2025-05-27T07:30:44.423577Z"
},
"0.204.0": {
"createdAt": "2024-04-24T06:21:55.527004Z"
},
"0.213.0": {
"createdAt": "2024-01-26T08:09:52.150304Z"
},
"0.198.0": {
"createdAt": "2024-04-24T06:53:55.032868Z"
},
"1.0.3": {
"createdAt": "2024-09-04T10:41:43.017156Z"
},
"1.0.14": {
"createdAt": "2026-05-26T09:57:07.159939Z"
},
"0.203.0": {
"createdAt": "2024-04-24T06:33:28.861052Z"
},
"1.0.7": {
"createdAt": "2025-05-13T07:25:42.233848Z"
},
"1.0.2": {
"createdAt": "2024-08-26T08:37:19.813343Z"
},
"0.200.0": {
"createdAt": "2024-04-24T06:44:21.451197Z"
},
"1.0.12": {
"createdAt": "2025-10-07T08:24:12.939816Z"
},
"1.0.4": {
"createdAt": "2024-09-24T10:31:08.642379Z"
},
"0.224.0": {
"createdAt": "2024-04-25T07:39:12.537935Z"
},
"1.0.1": {
"createdAt": "2024-07-02T11:47:12.495023Z"
}
}
},
"pi_release": {
"tag_name": "v0.99.2",
"published_at": "2026-09-30T19:30:47Z",
"html_url": "https://github.com/earendil-works/pi/releases/tag/v0.99.2"
},
"camofox_118_manifest": {
"name": "@askjo/camofox-browser",
"version": "1.18.0",
"engines": {
"node": ">=22"
},
"dependencies": {
"better-sqlite3": "13.0.1",
"camoufox-js": "0.11.5",
"express": "^5.2.1",
"playwright-core": "^1.58.0",
"prom-client": "^15.1.3",
"swagger-jsdoc": "^6.2.8"
},
"optionalDependencies": {
"@modelcontextprotocol/sdk": "^1.30.0"
},
"overrides": {
"@jest/reporters": {
"glob": "13.0.6"
},
"babel-plugin-istanbul": {
"test-exclude": "8.0.0"
},
"jest-config": {
"glob": "13.0.6"
},
"jest-runtime": {
"glob": "13.0.6"
},
"swagger-jsdoc": {
"glob": "13.0.6"
},
"qs": "6.16.0",
"fast-uri": "3.1.8",
"hono": "4.13.5"
}
},
"static_ffmpeg_release": {
"error": "HTTP Error 404: Not Found"
},
"pixelflux": {
"version": "2.1.0",
"requires_python": ">=3.9",
"project_urls": {
"Homepage": "https://github.com/selkies-project/pixelflux"
}
},
"selkies": {
"version": "2.0.0",
"requires_python": ">=3.9",
"project_urls": {
"Bug Tracker": "https://github.com/selkies-project/selkies/issues",
"Homepage": "https://github.com/selkies-project/selkies"
}
},
"labwc_release": {
"tag_name": "0.20.2",
"published_at": "2026-08-21T17:28:56Z",
"html_url": "https://github.com/labwc/labwc/releases/tag/0.20.2"
},
"rr7": {
"name": "react-router",
"version": "7.18.4",
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"rrdev7": {
"name": "@react-router/dev",
"version": "7.18.4",
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"vite": "^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"wrangler": "^3.28.2 || ^4.0.0",
"typescript": "^5.1.0 || ^6.0.0",
"react-router": "^7.18.4",
"@vitejs/plugin-rsc": "~0.5.21",
"@react-router/serve": "^7.18.4",
"react-server-dom-webpack": "^19.2.3"
}
},
"rrnode7": {
"name": "@react-router/node",
"version": "7.18.4",
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"typescript": "^5.1.0 || ^6.0.0",
"react-router": "7.18.4"
}
},
"browser:better-sqlite3": {
"name": "better-sqlite3",
"version": "13.0.3",
"engines": {
"node": ">=22"
}
},
"browser:camoufox-js": {
"name": "camoufox-js",
"version": "0.12.0",
"engines": {
"node": ">= 22"
},
"peerDependencies": {
"playwright-core": "<1.61.0"
}
},
"browser:express": {
"name": "express",
"version": "5.2.1",
"engines": {
"node": ">= 18"
}
},
"browser:playwright-core": {
"name": "playwright-core",
"version": "1.63.0",
"engines": {
"node": ">=20"
}
},
"browser:prom-client": {
"name": "prom-client",
"version": "15.1.3",
"engines": {
"node": "^16 || ^18 || >=20"
}
},
"browser:swagger-jsdoc": {
"name": "swagger-jsdoc",
"version": "6.3.0",
"engines": {
"node": ">=20.0.0"
}
},
"pi_mcp": {
"name": "@earendil-works/pi-mcp",
"version": "0.99.2",
"engines": {
"node": ">=22.19.0"
},
"dependencies": {
"cross-spawn": "7.0.6"
}
},
"pi_codemode": {
"name": "@earendil-works/pi-codemode",
"version": "0.99.2",
"engines": {
"node": ">=22.19.0"
},
"dependencies": {
"quickjs-wasi": "3.6.2"
}
}
}
@@ -1,353 +0,0 @@
{
"auditReportVersion": 2,
"vulnerabilities": {
"adm-zip": {
"name": "adm-zip",
"severity": "high",
"isDirect": false,
"via": [
{
"source": 1193734,
"name": "adm-zip",
"dependency": "adm-zip",
"title": "adm-zip extraction follows destination symlinks, allowing arbitrary file overwrite",
"url": "https://github.com/advisories/GHSA-vwc7-r8mq-g2x9",
"severity": "moderate",
"cwe": [
"CWE-59"
],
"cvss": {
"score": 6.5,
"vectorString": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N"
},
"range": ">=0.5.9 <=0.6.0"
},
{
"source": 1239030,
"name": "adm-zip",
"dependency": "adm-zip",
"title": "adm-zip: Uncontrolled memory allocation via the declared uncompressed size (DoS)",
"url": "https://github.com/advisories/GHSA-7q85-xj36-vmfc",
"severity": "high",
"cwe": [
"CWE-789"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": "<0.6.1"
},
{
"source": 1240032,
"name": "adm-zip",
"dependency": "adm-zip",
"title": "adm-zip: Decompression-bomb protection (fix for CVE-2026-39244) can be bypassed by declaring uncompressed size as 0",
"url": "https://github.com/advisories/GHSA-rcw4-f5rp-g42v",
"severity": "high",
"cwe": [
"CWE-409"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": "<=0.6.0"
},
{
"source": 1240034,
"name": "adm-zip",
"dependency": "adm-zip",
"title": "adm-zip extraction preserves SUID/SGID bits from untrusted ZIPs -> local privilege escalation",
"url": "https://github.com/advisories/GHSA-j5f4-cc29-5x44",
"severity": "high",
"cwe": [
"CWE-732"
],
"cvss": {
"score": 7.1,
"vectorString": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N"
},
"range": "<=0.6.0"
},
{
"source": 1240064,
"name": "adm-zip",
"dependency": "adm-zip",
"title": "adm-zip: Duplicate ZIP entry names: getEntry() and extractAllTo() resolve to different content",
"url": "https://github.com/advisories/GHSA-p634-w6r4-rjp2",
"severity": "moderate",
"cwe": [
"CWE-436",
"CWE-696"
],
"cvss": {
"score": 5.9,
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N"
},
"range": "<=0.6.0"
},
{
"source": 1240065,
"name": "adm-zip",
"dependency": "adm-zip",
"title": "adm-zip: getDataAsync() bypasses the maxOutputLength size guard enforced by the synchronous getData() path",
"url": "https://github.com/advisories/GHSA-c6fg-446q-cg94",
"severity": "moderate",
"cwe": [
"CWE-400",
"CWE-770"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
},
"range": "<=0.6.0"
},
{
"source": 1240066,
"name": "adm-zip",
"dependency": "adm-zip",
"title": "adm-zip: Unhandled error event in async DEFLATE decompression crashes Node.js process (DoS)",
"url": "https://github.com/advisories/GHSA-8238-w5pm-2374",
"severity": "high",
"cwe": [
"CWE-248",
"CWE-400"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": "<=0.6.0"
}
],
"effects": [],
"range": "<=0.6.0",
"nodes": [
"node_modules/adm-zip"
],
"fixAvailable": true
},
"brace-expansion": {
"name": "brace-expansion",
"severity": "high",
"isDirect": false,
"via": [
{
"source": 1240103,
"name": "brace-expansion",
"dependency": "brace-expansion",
"title": "brace-expansion: Quadratic-time expansion of the `{a},b}` rewrite causes CPU denial of service",
"url": "https://github.com/advisories/GHSA-q2hr-2g5m-vwhr",
"severity": "moderate",
"cwe": [
"CWE-400",
"CWE-407"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
},
"range": ">=4.0.0 <5.0.12"
},
{
"source": 1240107,
"name": "brace-expansion",
"dependency": "brace-expansion",
"title": "brace-expansion: DoS via uncontrolled recursion on nested brace groups causing stack exhaustion",
"url": "https://github.com/advisories/GHSA-qhr7-859c-m2p7",
"severity": "high",
"cwe": [
"CWE-400",
"CWE-674"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=4.0.0 <5.0.11"
},
{
"source": 1240111,
"name": "brace-expansion",
"dependency": "brace-expansion",
"title": "brace-expansion: DoS via uncontrolled recursion in parseCommaParts causing stack exhaustion",
"url": "https://github.com/advisories/GHSA-6j4f-fj2g-mc7p",
"severity": "high",
"cwe": [
"CWE-400",
"CWE-674"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=4.0.0 <5.0.10"
}
],
"effects": [],
"range": "4.0.0 - 5.0.11",
"nodes": [
"node_modules/brace-expansion"
],
"fixAvailable": true
},
"fast-uri": {
"name": "fast-uri",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1240091,
"name": "fast-uri",
"dependency": "fast-uri",
"title": "fast-uri vulnerable to inconsistent host case normalization via percent-encoded octets",
"url": "https://github.com/advisories/GHSA-hrr3-gc8f-f4qj",
"severity": "moderate",
"cwe": [
"CWE-178"
],
"cvss": {
"score": 4.8,
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N"
},
"range": ">=3.0.0 <3.1.8"
}
],
"effects": [],
"range": "3.0.0 - 3.1.7",
"nodes": [
"node_modules/fast-uri"
],
"fixAvailable": true
},
"hono": {
"name": "hono",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1240640,
"name": "hono",
"dependency": "hono",
"title": "hono/jsx renders plain strings unescaped in boundary components, leading to XSS",
"url": "https://github.com/advisories/GHSA-hxh3-vqpv-xpqv",
"severity": "moderate",
"cwe": [
"CWE-79"
],
"cvss": {
"score": 4.7,
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N"
},
"range": "<4.13.7"
}
],
"effects": [],
"range": "<4.13.7",
"nodes": [
"node_modules/hono"
],
"fixAvailable": true
},
"ip-address": {
"name": "ip-address",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1239948,
"name": "ip-address",
"dependency": "ip-address",
"title": "ip-address: Address6.isLinkLocal() recognizes fe80::/64 rather than fe80::/10, allowing SSRF and trust-boundary bypass to on-link hosts",
"url": "https://github.com/advisories/GHSA-rpw4-54j3-4h4q",
"severity": "moderate",
"cwe": [
"CWE-697",
"CWE-918"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<=10.5.0"
},
{
"source": 1239949,
"name": "ip-address",
"dependency": "ip-address",
"title": "ip-address: no classifier recognizes the NAT64 local-use range 64:ff9b:1::/48, allowing SSRF and trust-boundary bypass",
"url": "https://github.com/advisories/GHSA-2vr4-cq9g-pvrc",
"severity": "moderate",
"cwe": [
"CWE-918"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": ">=10.2.0 <=10.5.0"
},
{
"source": 1240097,
"name": "ip-address",
"dependency": "ip-address",
"title": "ip-address: isInSubnet() and isHostInSubnet() compare addresses of different families as if they shared an address space, allowing an allowlist check to admit an address outside its range",
"url": "https://github.com/advisories/GHSA-j6r3-76f7-8jcv",
"severity": "moderate",
"cwe": [
"CWE-697",
"CWE-843"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<=10.7.0"
},
{
"source": 1240098,
"name": "ip-address",
"dependency": "ip-address",
"title": "ip-address: Address6 builds a parse diagnostic proportional to the input with no length bound, allowing a single long string to stall or crash the process",
"url": "https://github.com/advisories/GHSA-h3mg-xc3c-68pw",
"severity": "moderate",
"cwe": [
"CWE-400",
"CWE-770"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<=10.7.0"
}
],
"effects": [],
"range": "<=10.7.0",
"nodes": [
"node_modules/ip-address"
],
"fixAvailable": true
}
},
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 3,
"high": 2,
"critical": 0,
"total": 5
},
"dependencies": {
"prod": 171,
"dev": 501,
"optional": 84,
"peer": 1,
"peerOptional": 0,
"total": 680
}
}
}
@@ -1,51 +0,0 @@
[
{
"name": "umt-app-1",
"image": "uhd-media-tool:v0.3.8",
"imageId": "sha256:6f4f73d8ea53057cdf5bf4aa2904a360ea953adb1270a7b9f783a757b7e70559",
"health": "healthy",
"status": "running"
},
{
"name": "umt-postgres-1",
"image": "postgres:18.6-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873",
"imageId": "sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873",
"health": "healthy",
"status": "running"
},
{
"name": "umt-dev-postgres-1",
"image": "postgres:18.6-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873",
"imageId": "sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873",
"health": "healthy",
"status": "running"
},
{
"name": "umt-camofox-1",
"image": "umt-browser-desktop:1.18.0",
"imageId": "sha256:eb6c8e8e43e573ad5e3223619b77328e12fb54fe40c283b9de02c7f6d03f5cda",
"health": "healthy",
"status": "running"
},
{
"name": "umt-qbittorrent-1",
"image": "lscr.io/linuxserver/qbittorrent:5.2.4@sha256:b522f9f4b769f8f36d49d22d5eb6a92e9aa18904c6a1830b1439df511ec21983",
"imageId": "sha256:b522f9f4b769f8f36d49d22d5eb6a92e9aa18904c6a1830b1439df511ec21983",
"health": null,
"status": "running"
},
{
"name": "umt-caddy-1",
"image": "caddy:2.11.4-alpine@sha256:6aeddd44c3078b0f9a35206472a11420648a79c184603ef95957d0a20044cb2b",
"imageId": "sha256:6aeddd44c3078b0f9a35206472a11420648a79c184603ef95957d0a20044cb2b",
"health": null,
"status": "running"
},
{
"name": "umt-proxy-relay-1",
"image": "alpine/socat:1.8.1.3@sha256:5ffbd6ae916cbad86a58fabe0d6d5a6fd5c2b47ddf031e82996baac9300e732f",
"imageId": "sha256:5ffbd6ae916cbad86a58fabe0d6d5a6fd5c2b47ddf031e82996baac9300e732f",
"health": null,
"status": "running"
}
]
@@ -1,59 +0,0 @@
package,declared,locked,registry_latest,candidate,decision
@biomejs/biome,2.5.14,2.5.14,2.5.15,2.5.15,候选升级,未验证
@earendil-works/pi-agent-core,0.87.1,0.87.1,0.99.2,0.99.2,同步升级并验证嵌入接口;另评估 pi-mcp SDK
@earendil-works/pi-ai,0.87.1,0.87.1,0.99.2,0.99.2,同步升级并验证嵌入接口;另评估 pi-mcp SDK
@fontsource-variable/inter-tight,^5.2.7,5.3.0,5.3.0,5.3.0,保持
@fontsource-variable/space-grotesk,^5.2.10,5.3.0,5.3.0,5.3.0,保持
@hono/swagger-ui,0.6.1,0.6.1,0.6.1,0.6.1,保持
@hono/zod-openapi,1.6.3,1.6.3,1.6.3,1.6.3,保持
@modelcontextprotocol/sdk,1.30.1,1.30.1,1.31.0,1.31.0,候选升级,未验证
@react-router/dev,7.12.0,7.12.0,8.4.0,7.18.4,先同主版本安全修复;8.4.0 另评估
@react-router/node,7.12.0,7.12.0,8.4.0,7.18.4,先同主版本安全修复;8.4.0 另评估
@simplewebauthn/browser,^13.3.0,13.3.0,14.0.0,14.0.0,独立兼容评估后决定,不直接更新
@simplewebauthn/server,13.3.3,13.3.3,14.0.3,14.0.3,独立兼容评估后决定,不直接更新
@tailwindcss/vite,^4.1.13,4.3.3,4.3.3,4.3.3,保持
@tanstack/react-query,^5.90.21,5.104.0,5.104.0,5.104.0,保持
@types/node,24.19.0,24.19.0,26.6.3,24.x,维持与 Node 24 兼容目标;不跟随 26
@types/nunjucks,3.2.6,3.2.6,3.2.6,3.2.6,保持
@types/pg,8.23.1,8.23.1,8.23.1,8.23.1,保持
@types/react,^19.2.7,19.3.0,19.3.0,19.3.0,保持
@types/react-dom,^19.2.3,19.3.0,19.3.0,19.3.0,保持
@xmldom/xmldom,0.9.12,0.9.12,0.9.12,0.9.12,保持
cheerio,1.2.0,1.2.0,1.2.0,1.2.0,保持
class-variance-authority,~0.7.1,0.7.1,0.7.1,0.7.1,保持
clsx,^2.1.1,2.1.1,2.1.1,2.1.1,保持
date-fns,^4.1.0,4.4.0,4.4.0,4.4.0,保持
fast-xml-parser,5.11.1,5.11.1,5.11.2,5.11.2,候选升级,未验证
hash-wasm,4.12.0,4.12.0,4.12.0,4.12.0,保持
hono,4.13.9,4.13.9,4.13.12,4.13.12,候选升级,未验证
isbot,^5.1.31,5.2.2,5.2.2,5.2.2,保持
jpeg-js,0.4.4,0.4.4,0.4.4,0.4.4,保持
kysely,0.29.6,0.29.6,0.29.6,0.29.6,保持
lucide-react,0.576,0.576.0,1.49.0,1.49.0,独立兼容评估后决定,不直接更新
next-themes,~0.4.6,0.4.6,0.4.6,0.4.6,保持
nunjucks,3.2.4,3.2.4,3.2.4,3.2.4,保持
openworkflow,0.10.1,0.10.1,0.10.1,0.10.1,保持
pg,8.23.0,8.23.0,8.23.1,8.23.1,候选升级,未验证
pino,10.3.1,10.3.1,10.3.1,10.3.1,保持
pino-pretty,13.1.3,13.1.3,13.1.3,13.1.3,保持
playwright-core,1.63.0,1.63.0,1.63.0,1.63.0,保持
postgres,3.4.9,3.4.9,3.4.9,3.4.9,保持
qr-code-styling,^1.9.2,1.9.2,1.9.2,1.9.2,保持
radix-ui,^1.4.3,1.6.7,1.6.7,1.6.7,保持
react,^19.2.4,19.3.0,19.3.0,19.3.0,保持
react-day-picker,^10.0.1,10.0.1,10.0.2,10.0.2,候选升级,未验证
react-dom,^19.2.4,19.3.0,19.3.0,19.3.0,保持
react-router,7.12.0,7.12.0,8.4.0,7.18.4,先同主版本安全修复;8.4.0 另评估
recharts,^3.10.1,3.10.1,3.10.1,3.10.1,保持
sonner,^2.0.7,2.0.8,2.0.8,2.0.8,保持
tailwind-merge,^3.5.0,3.7.0,3.7.0,3.7.0,保持
tailwindcss,^4.1.13,4.3.3,4.3.3,4.3.3,保持
tw-animate-css,^1.4.0,1.4.0,1.4.0,1.4.0,保持
typebox,1.3.27,1.3.27,1.3.34,1.3.34,候选升级,未验证
typescript,7.0.2,7.0.2,7.0.2,7.0.2,保持
vite,^7.1.7,7.3.6,8.3.1,8.3.1,独立兼容评估后决定,不直接更新
vite-tsconfig-paths,^5.1.4,5.1.4,6.1.1,6.1.1,独立兼容评估后决定,不直接更新
vitest,5.0.2,5.0.2,5.0.3,5.0.3,候选升级,未验证
xpath,0.0.34,0.0.34,0.0.34,0.0.34,保持
yaml,2.9.1,2.9.1,2.9.1,2.9.1,保持
zod,4.6.5;^4.3.6,4.6.5,4.6.5,4.6.5,保持
1 package declared locked registry_latest candidate decision
2 @biomejs/biome 2.5.14 2.5.14 2.5.15 2.5.15 候选升级,未验证
3 @earendil-works/pi-agent-core 0.87.1 0.87.1 0.99.2 0.99.2 同步升级并验证嵌入接口;另评估 pi-mcp SDK
4 @earendil-works/pi-ai 0.87.1 0.87.1 0.99.2 0.99.2 同步升级并验证嵌入接口;另评估 pi-mcp SDK
5 @fontsource-variable/inter-tight ^5.2.7 5.3.0 5.3.0 5.3.0 保持
6 @fontsource-variable/space-grotesk ^5.2.10 5.3.0 5.3.0 5.3.0 保持
7 @hono/swagger-ui 0.6.1 0.6.1 0.6.1 0.6.1 保持
8 @hono/zod-openapi 1.6.3 1.6.3 1.6.3 1.6.3 保持
9 @modelcontextprotocol/sdk 1.30.1 1.30.1 1.31.0 1.31.0 候选升级,未验证
10 @react-router/dev 7.12.0 7.12.0 8.4.0 7.18.4 先同主版本安全修复;8.4.0 另评估
11 @react-router/node 7.12.0 7.12.0 8.4.0 7.18.4 先同主版本安全修复;8.4.0 另评估
12 @simplewebauthn/browser ^13.3.0 13.3.0 14.0.0 14.0.0 独立兼容评估后决定,不直接更新
13 @simplewebauthn/server 13.3.3 13.3.3 14.0.3 14.0.3 独立兼容评估后决定,不直接更新
14 @tailwindcss/vite ^4.1.13 4.3.3 4.3.3 4.3.3 保持
15 @tanstack/react-query ^5.90.21 5.104.0 5.104.0 5.104.0 保持
16 @types/node 24.19.0 24.19.0 26.6.3 24.x 维持与 Node 24 兼容目标;不跟随 26
17 @types/nunjucks 3.2.6 3.2.6 3.2.6 3.2.6 保持
18 @types/pg 8.23.1 8.23.1 8.23.1 8.23.1 保持
19 @types/react ^19.2.7 19.3.0 19.3.0 19.3.0 保持
20 @types/react-dom ^19.2.3 19.3.0 19.3.0 19.3.0 保持
21 @xmldom/xmldom 0.9.12 0.9.12 0.9.12 0.9.12 保持
22 cheerio 1.2.0 1.2.0 1.2.0 1.2.0 保持
23 class-variance-authority ~0.7.1 0.7.1 0.7.1 0.7.1 保持
24 clsx ^2.1.1 2.1.1 2.1.1 2.1.1 保持
25 date-fns ^4.1.0 4.4.0 4.4.0 4.4.0 保持
26 fast-xml-parser 5.11.1 5.11.1 5.11.2 5.11.2 候选升级,未验证
27 hash-wasm 4.12.0 4.12.0 4.12.0 4.12.0 保持
28 hono 4.13.9 4.13.9 4.13.12 4.13.12 候选升级,未验证
29 isbot ^5.1.31 5.2.2 5.2.2 5.2.2 保持
30 jpeg-js 0.4.4 0.4.4 0.4.4 0.4.4 保持
31 kysely 0.29.6 0.29.6 0.29.6 0.29.6 保持
32 lucide-react 0.576 0.576.0 1.49.0 1.49.0 独立兼容评估后决定,不直接更新
33 next-themes ~0.4.6 0.4.6 0.4.6 0.4.6 保持
34 nunjucks 3.2.4 3.2.4 3.2.4 3.2.4 保持
35 openworkflow 0.10.1 0.10.1 0.10.1 0.10.1 保持
36 pg 8.23.0 8.23.0 8.23.1 8.23.1 候选升级,未验证
37 pino 10.3.1 10.3.1 10.3.1 10.3.1 保持
38 pino-pretty 13.1.3 13.1.3 13.1.3 13.1.3 保持
39 playwright-core 1.63.0 1.63.0 1.63.0 1.63.0 保持
40 postgres 3.4.9 3.4.9 3.4.9 3.4.9 保持
41 qr-code-styling ^1.9.2 1.9.2 1.9.2 1.9.2 保持
42 radix-ui ^1.4.3 1.6.7 1.6.7 1.6.7 保持
43 react ^19.2.4 19.3.0 19.3.0 19.3.0 保持
44 react-day-picker ^10.0.1 10.0.1 10.0.2 10.0.2 候选升级,未验证
45 react-dom ^19.2.4 19.3.0 19.3.0 19.3.0 保持
46 react-router 7.12.0 7.12.0 8.4.0 7.18.4 先同主版本安全修复;8.4.0 另评估
47 recharts ^3.10.1 3.10.1 3.10.1 3.10.1 保持
48 sonner ^2.0.7 2.0.8 2.0.8 2.0.8 保持
49 tailwind-merge ^3.5.0 3.7.0 3.7.0 3.7.0 保持
50 tailwindcss ^4.1.13 4.3.3 4.3.3 4.3.3 保持
51 tw-animate-css ^1.4.0 1.4.0 1.4.0 1.4.0 保持
52 typebox 1.3.27 1.3.27 1.3.34 1.3.34 候选升级,未验证
53 typescript 7.0.2 7.0.2 7.0.2 7.0.2 保持
54 vite ^7.1.7 7.3.6 8.3.1 8.3.1 独立兼容评估后决定,不直接更新
55 vite-tsconfig-paths ^5.1.4 5.1.4 6.1.1 6.1.1 独立兼容评估后决定,不直接更新
56 vitest 5.0.2 5.0.2 5.0.3 5.0.3 候选升级,未验证
57 xpath 0.0.34 0.0.34 0.0.34 0.0.34 保持
58 yaml 2.9.1 2.9.1 2.9.1 2.9.1 保持
59 zod 4.6.5;^4.3.6 4.6.5 4.6.5 4.6.5 保持
@@ -1,634 +0,0 @@
package,locked_versions,registry_latest,workspace_direct,has_older_or_different_version
@anthropic-ai/sdk,0.124.0,0.131.0,False,True
@asteasolutions/zod-to-openapi,9.1.0,9.1.0,False,False
@aws-sdk/client-bedrock-runtime,3.1127.0,3.1144.0,False,True
@aws-sdk/core,3.978.1,3.978.1,False,False
@aws-sdk/credential-provider-env,3.972.72,3.972.72,False,False
@aws-sdk/credential-provider-http,3.972.74,3.972.74,False,False
@aws-sdk/credential-provider-ini,3.973.17,3.973.17,False,False
@aws-sdk/credential-provider-login,3.972.79,3.972.79,False,False
@aws-sdk/credential-provider-node,3.972.84,3.972.84,False,False
@aws-sdk/credential-provider-process,3.972.72,3.972.72,False,False
@aws-sdk/credential-provider-sso,3.973.16,3.973.16,False,False
@aws-sdk/credential-provider-web-identity,3.972.78,3.972.78,False,False
@aws-sdk/eventstream-handler-node,3.972.35,3.972.35,False,False
@aws-sdk/middleware-eventstream,3.972.30,3.972.30,False,False
@aws-sdk/middleware-websocket,3.972.54,3.972.54,False,False
@aws-sdk/nested-clients,3.997.46,3.997.46,False,False
@aws-sdk/signature-v4-multi-region,3.996.47,3.996.47,False,False
@aws-sdk/token-providers,3.1127.0;3.1138.0,3.1144.0,False,True
@aws-sdk/types,3.974.6,3.974.6,False,False
@aws-sdk/xml-builder,3.972.41,3.972.41,False,False
@aws/lambda-invoke-store,0.3.0,0.3.0,False,False
@babel/code-frame,7.29.7,8.0.6,False,True
@babel/compat-data,7.29.7,8.0.5,False,True
@babel/core,7.29.7,8.0.6,False,True
@babel/generator,7.29.8,8.0.6,False,True
@babel/helper-annotate-as-pure,7.29.7,8.0.0,False,True
@babel/helper-compilation-targets,7.29.7,8.0.6,False,True
@babel/helper-create-class-features-plugin,7.29.7,8.0.6,False,True
@babel/helper-globals,7.29.7,8.0.6,False,True
@babel/helper-member-expression-to-functions,7.29.7,8.0.5,False,True
@babel/helper-module-imports,7.29.7,8.0.0,False,True
@babel/helper-module-transforms,7.29.7,8.0.6,False,True
@babel/helper-optimise-call-expression,7.29.7,8.0.0,False,True
@babel/helper-plugin-utils,7.29.7,8.0.1,False,True
@babel/helper-replace-supers,7.29.7,8.0.1,False,True
@babel/helper-skip-transparent-expression-wrappers,7.29.7,8.0.0,False,True
@babel/helper-string-parser,7.29.7,8.0.6,False,True
@babel/helper-validator-identifier,7.29.7,8.0.6,False,True
@babel/helper-validator-option,7.29.7,8.0.0,False,True
@babel/helpers,7.29.7,8.0.5,False,True
@babel/parser,7.29.9,7.29.9,False,False
@babel/plugin-syntax-jsx,7.29.7,8.0.1,False,True
@babel/plugin-syntax-typescript,7.29.7,8.0.3,False,True
@babel/plugin-transform-modules-commonjs,7.29.7,8.0.1,False,True
@babel/plugin-transform-typescript,7.29.9,7.29.9,False,False
@babel/preset-typescript,7.29.7,8.0.1,False,True
@babel/runtime,7.29.7,8.0.5,False,True
@babel/template,7.29.7,8.0.0,False,True
@babel/traverse,7.29.8,8.0.6,False,True
@babel/types,7.29.8,8.0.6,False,True
@biomejs/biome,2.5.14,2.5.15,True,True
@biomejs/cli-darwin-arm64,2.5.14,2.5.15,False,True
@biomejs/cli-darwin-x64,2.5.14,2.5.15,False,True
@biomejs/cli-linux-arm64,2.5.14,2.5.15,False,True
@biomejs/cli-linux-arm64-musl,2.5.14,2.5.15,False,True
@biomejs/cli-linux-x64,2.5.14,2.5.15,False,True
@biomejs/cli-linux-x64-musl,2.5.14,2.5.15,False,True
@biomejs/cli-win32-arm64,2.5.14,2.5.15,False,True
@biomejs/cli-win32-x64,2.5.14,2.5.15,False,True
@date-fns/tz,1.5.0,1.5.0,False,False
@earendil-works/chord,0.87.1,0.99.2,False,True
@earendil-works/pi-agent-core,0.87.1,0.99.2,True,True
@earendil-works/pi-ai,0.87.1,0.99.2,True,True
@earendil-works/pi-telemetry,0.87.1,0.99.2,False,True
@esbuild/aix-ppc64,0.28.2,0.28.2,False,False
@esbuild/android-arm,0.28.2,0.28.2,False,False
@esbuild/android-arm64,0.28.2,0.28.2,False,False
@esbuild/android-x64,0.28.2,0.28.2,False,False
@esbuild/darwin-arm64,0.28.2,0.28.2,False,False
@esbuild/darwin-x64,0.28.2,0.28.2,False,False
@esbuild/freebsd-arm64,0.28.2,0.28.2,False,False
@esbuild/freebsd-x64,0.28.2,0.28.2,False,False
@esbuild/linux-arm,0.28.2,0.28.2,False,False
@esbuild/linux-arm64,0.28.2,0.28.2,False,False
@esbuild/linux-ia32,0.28.2,0.28.2,False,False
@esbuild/linux-loong64,0.28.2,0.28.2,False,False
@esbuild/linux-mips64el,0.28.2,0.28.2,False,False
@esbuild/linux-ppc64,0.28.2,0.28.2,False,False
@esbuild/linux-riscv64,0.28.2,0.28.2,False,False
@esbuild/linux-s390x,0.28.2,0.28.2,False,False
@esbuild/linux-x64,0.28.2,0.28.2,False,False
@esbuild/netbsd-arm64,0.28.2,0.28.2,False,False
@esbuild/netbsd-x64,0.28.2,0.28.2,False,False
@esbuild/openbsd-arm64,0.28.2,0.28.2,False,False
@esbuild/openbsd-x64,0.28.2,0.28.2,False,False
@esbuild/openharmony-arm64,0.28.2,0.28.2,False,False
@esbuild/sunos-x64,0.28.2,0.28.2,False,False
@esbuild/win32-arm64,0.28.2,0.28.2,False,False
@esbuild/win32-ia32,0.28.2,0.28.2,False,False
@esbuild/win32-x64,0.28.2,0.28.2,False,False
@floating-ui/core,1.8.0,1.8.0,False,False
@floating-ui/dom,1.8.0,1.8.0,False,False
@floating-ui/react-dom,2.1.9,2.1.9,False,False
@floating-ui/utils,0.2.12,0.2.12,False,False
@fontsource-variable/inter-tight,5.3.0,5.3.0,True,False
@fontsource-variable/space-grotesk,5.3.0,5.3.0,True,False
@google/genai,2.21.0,2.25.0,False,True
@hexagon/base64,1.1.28,2.0.4,False,True
@hono/node-server,2.1.1,2.1.3,False,True
@hono/swagger-ui,0.6.1,0.6.1,True,False
@hono/zod-openapi,1.6.3,1.6.3,True,False
@hono/zod-validator,0.9.1,0.9.1,False,False
@jridgewell/gen-mapping,0.3.13,0.3.13,False,False
@jridgewell/remapping,2.3.5,2.3.5,False,False
@jridgewell/resolve-uri,3.1.2,3.1.2,False,False
@jridgewell/sourcemap-codec,1.6.0,1.6.0,False,False
@jridgewell/trace-mapping,0.3.31,0.3.31,False,False
@levischuck/tiny-cbor,0.2.11,0.3.6,False,True
@mjackson/node-fetch-server,0.2.0,0.7.0,False,True
@modelcontextprotocol/sdk,1.30.1,1.31.0,True,True
@napi-rs/lzma-linux-x64-gnu,1.5.1,1.5.1,False,False
@nodable/entities,3.0.0,3.1.0,False,True
@peculiar/asn1-android,2.9.5,2.10.0,False,True
@peculiar/asn1-cms,2.9.5,2.10.0,False,True
@peculiar/asn1-csr,2.9.5,2.10.0,False,True
@peculiar/asn1-ecc,2.9.5,2.10.0,False,True
@peculiar/asn1-pfx,2.9.5,2.10.0,False,True
@peculiar/asn1-pkcs8,2.9.5,2.10.0,False,True
@peculiar/asn1-pkcs9,2.9.5,2.10.0,False,True
@peculiar/asn1-rsa,2.9.5,2.10.0,False,True
@peculiar/asn1-schema,2.9.5,2.10.0,False,True
@peculiar/asn1-x509,2.9.5,2.10.0,False,True
@peculiar/asn1-x509-attr,2.9.5,2.10.0,False,True
@peculiar/utils,2.0.3,2.0.3,False,False
@peculiar/x509,1.14.3,2.1.0,False,True
@pinojs/redact,0.4.0,0.4.0,False,False
@protobufjs/aspromise,1.1.2,1.1.2,False,False
@protobufjs/base64,1.1.2,1.1.2,False,False
@protobufjs/codegen,2.0.5,2.0.5,False,False
@protobufjs/eventemitter,1.1.1,1.1.1,False,False
@protobufjs/fetch,1.1.1,1.1.1,False,False
@protobufjs/float,1.0.2,1.0.2,False,False
@protobufjs/path,1.1.2,1.1.2,False,False
@protobufjs/pool,1.1.0,1.1.0,False,False
@protobufjs/utf8,1.1.2,1.1.2,False,False
@radix-ui/number,1.1.3,1.1.3,False,False
@radix-ui/primitive,1.1.7,1.1.7,False,False
@radix-ui/react-accessible-icon,1.1.15,1.1.15,False,False
@radix-ui/react-accordion,1.2.20,1.2.20,False,False
@radix-ui/react-alert-dialog,1.1.23,1.1.23,False,False
@radix-ui/react-arrow,1.1.15,1.1.15,False,False
@radix-ui/react-aspect-ratio,1.1.15,1.1.15,False,False
@radix-ui/react-avatar,1.2.6,1.2.6,False,False
@radix-ui/react-checkbox,1.3.11,1.3.11,False,False
@radix-ui/react-collapsible,1.1.20,1.1.20,False,False
@radix-ui/react-collection,1.1.15,1.1.15,False,False
@radix-ui/react-compose-refs,1.1.5,1.1.5,False,False
@radix-ui/react-context,1.2.2,1.2.2,False,False
@radix-ui/react-context-menu,2.3.7,2.3.7,False,False
@radix-ui/react-dialog,1.1.23,1.1.23,False,False
@radix-ui/react-direction,1.1.4,1.1.4,False,False
@radix-ui/react-dismissable-layer,1.1.19,1.1.19,False,False
@radix-ui/react-dropdown-menu,2.1.24,2.1.24,False,False
@radix-ui/react-focus-guards,1.1.6,1.1.6,False,False
@radix-ui/react-focus-scope,1.1.16,1.1.16,False,False
@radix-ui/react-form,0.1.16,0.1.16,False,False
@radix-ui/react-hover-card,1.1.23,1.1.23,False,False
@radix-ui/react-id,1.1.4,1.1.4,False,False
@radix-ui/react-label,2.1.15,2.1.15,False,False
@radix-ui/react-menu,2.1.24,2.1.24,False,False
@radix-ui/react-menubar,1.1.24,1.1.24,False,False
@radix-ui/react-navigation-menu,1.2.22,1.2.22,False,False
@radix-ui/react-one-time-password-field,0.1.16,0.1.16,False,False
@radix-ui/react-password-toggle-field,0.1.11,0.1.11,False,False
@radix-ui/react-popover,1.1.23,1.1.23,False,False
@radix-ui/react-popper,1.3.7,1.3.7,False,False
@radix-ui/react-portal,1.1.17,1.1.17,False,False
@radix-ui/react-presence,1.1.10,1.1.10,False,False
@radix-ui/react-primitive,2.1.10,2.1.10,False,False
@radix-ui/react-progress,1.1.16,1.1.16,False,False
@radix-ui/react-radio-group,1.4.7,1.4.7,False,False
@radix-ui/react-roving-focus,1.1.19,1.1.19,False,False
@radix-ui/react-scroll-area,1.2.18,1.2.18,False,False
@radix-ui/react-select,2.3.7,2.3.7,False,False
@radix-ui/react-separator,1.1.15,1.1.15,False,False
@radix-ui/react-slider,1.4.7,1.4.7,False,False
@radix-ui/react-slot,1.3.3,1.3.3,False,False
@radix-ui/react-switch,1.3.7,1.3.7,False,False
@radix-ui/react-tabs,1.1.21,1.1.21,False,False
@radix-ui/react-toast,1.2.23,1.2.23,False,False
@radix-ui/react-toggle,1.1.18,1.1.18,False,False
@radix-ui/react-toggle-group,1.1.19,1.1.19,False,False
@radix-ui/react-toolbar,1.1.19,1.1.19,False,False
@radix-ui/react-tooltip,1.2.16,1.2.16,False,False
@radix-ui/react-use-callback-ref,1.1.4,1.1.4,False,False
@radix-ui/react-use-controllable-state,1.2.6,1.2.6,False,False
@radix-ui/react-use-effect-event,0.0.5,0.0.5,False,False
@radix-ui/react-use-escape-keydown,1.1.5,1.1.5,False,False
@radix-ui/react-use-is-hydrated,0.1.3,0.1.3,False,False
@radix-ui/react-use-layout-effect,1.1.4,1.1.4,False,False
@radix-ui/react-use-previous,1.1.4,1.1.4,False,False
@radix-ui/react-use-rect,1.1.4,1.1.4,False,False
@radix-ui/react-use-size,1.1.4,1.1.4,False,False
@radix-ui/react-visually-hidden,1.2.11,1.2.11,False,False
@radix-ui/rect,1.1.3,1.1.3,False,False
@react-router/dev,7.12.0,8.4.0,True,True
@react-router/node,7.12.0,8.4.0,True,True
@reduxjs/toolkit,2.12.0,2.13.0,False,True
@remix-run/node-fetch-server,0.9.0,0.14.2,False,True
@rollup/rollup-android-arm-eabi,4.63.5,4.63.5,False,False
@rollup/rollup-android-arm64,4.63.5,4.63.5,False,False
@rollup/rollup-darwin-arm64,4.63.5,4.63.5,False,False
@rollup/rollup-darwin-x64,4.63.5,4.63.5,False,False
@rollup/rollup-freebsd-arm64,4.63.5,4.63.5,False,False
@rollup/rollup-freebsd-x64,4.63.5,4.63.5,False,False
@rollup/rollup-linux-arm-gnueabihf,4.63.5,4.63.5,False,False
@rollup/rollup-linux-arm-musleabihf,4.63.5,4.63.5,False,False
@rollup/rollup-linux-arm64-gnu,4.63.5,4.63.5,False,False
@rollup/rollup-linux-arm64-musl,4.63.5,4.63.5,False,False
@rollup/rollup-linux-loong64-gnu,4.63.5,4.63.5,False,False
@rollup/rollup-linux-loong64-musl,4.63.5,4.63.5,False,False
@rollup/rollup-linux-ppc64-gnu,4.63.5,4.63.5,False,False
@rollup/rollup-linux-ppc64-musl,4.63.5,4.63.5,False,False
@rollup/rollup-linux-riscv64-gnu,4.63.5,4.63.5,False,False
@rollup/rollup-linux-riscv64-musl,4.63.5,4.63.5,False,False
@rollup/rollup-linux-s390x-gnu,4.63.5,4.63.5,False,False
@rollup/rollup-linux-x64-gnu,4.63.5,4.63.5,False,False
@rollup/rollup-linux-x64-musl,4.63.5,4.63.5,False,False
@rollup/rollup-openbsd-x64,4.63.5,4.63.5,False,False
@rollup/rollup-openharmony-arm64,4.63.5,4.63.5,False,False
@rollup/rollup-win32-arm64-msvc,4.63.5,4.63.5,False,False
@rollup/rollup-win32-ia32-msvc,4.63.5,4.63.5,False,False
@rollup/rollup-win32-x64-gnu,4.63.5,4.63.5,False,False
@rollup/rollup-win32-x64-msvc,4.63.5,4.63.5,False,False
@simplewebauthn/browser,13.3.0,14.0.0,True,True
@simplewebauthn/server,13.3.3,14.0.3,True,True
@smithy/core,3.35.0,3.35.1,False,True
@smithy/credential-provider-imds,4.5.2,4.5.2,False,False
@smithy/fetch-http-handler,5.8.0,5.8.0,False,False
@smithy/node-http-handler,4.12.1,4.12.1,False,False
@smithy/signature-v4,5.7.4,5.7.4,False,False
@smithy/types,4.19.0,4.19.0,False,False
@stablelib/base64,1.0.1,2.0.1,False,True
@standard-schema/spec,1.1.0,1.1.0,False,False
@standard-schema/utils,0.3.0,0.3.0,False,False
@tailwindcss/node,4.3.3,4.3.3,False,False
@tailwindcss/oxide,4.3.3,4.3.3,False,False
@tailwindcss/oxide-android-arm64,4.3.3,4.3.3,False,False
@tailwindcss/oxide-darwin-arm64,4.3.3,4.3.3,False,False
@tailwindcss/oxide-darwin-x64,4.3.3,4.3.3,False,False
@tailwindcss/oxide-freebsd-x64,4.3.3,4.3.3,False,False
@tailwindcss/oxide-linux-arm-gnueabihf,4.3.3,4.3.3,False,False
@tailwindcss/oxide-linux-arm64-gnu,4.3.3,4.3.3,False,False
@tailwindcss/oxide-linux-arm64-musl,4.3.3,4.3.3,False,False
@tailwindcss/oxide-linux-x64-gnu,4.3.3,4.3.3,False,False
@tailwindcss/oxide-linux-x64-musl,4.3.3,4.3.3,False,False
@tailwindcss/oxide-wasm32-wasi,4.3.3,4.3.3,False,False
@tailwindcss/oxide-win32-arm64-msvc,4.3.3,4.3.3,False,False
@tailwindcss/oxide-win32-x64-msvc,4.3.3,4.3.3,False,False
@tailwindcss/vite,4.3.3,4.3.3,True,False
@tanstack/query-core,5.104.0,5.104.0,False,False
@tanstack/react-query,5.104.0,5.104.0,True,False
@types/chai,5.2.3,5.2.3,False,False
@types/d3-array,3.2.2,3.2.2,False,False
@types/d3-color,3.1.3,3.1.3,False,False
@types/d3-ease,3.0.2,3.0.2,False,False
@types/d3-interpolate,3.0.4,3.0.4,False,False
@types/d3-path,3.1.1,3.1.1,False,False
@types/d3-scale,4.0.9,4.0.9,False,False
@types/d3-shape,3.2.0,3.2.0,False,False
@types/d3-time,3.0.4,3.0.4,False,False
@types/d3-timer,3.0.2,3.0.2,False,False
@types/deep-eql,4.0.2,4.0.2,False,False
@types/estree,1.0.9,1.0.9,False,False
@types/node,24.19.0,26.6.3,True,True
@types/nunjucks,3.2.6,3.2.6,True,False
@types/pg,8.23.1,8.23.1,True,False
@types/react,19.3.0,19.3.0,True,False
@types/react-dom,19.3.0,19.3.0,True,False
@types/retry,0.12.0,0.12.5,False,True
@types/use-sync-external-store,0.0.6,1.7.0,False,True
@typescript/typescript-aix-ppc64,7.0.2,7.0.2,False,False
@typescript/typescript-darwin-arm64,7.0.2,7.0.2,False,False
@typescript/typescript-darwin-x64,7.0.2,7.0.2,False,False
@typescript/typescript-freebsd-arm64,7.0.2,7.0.2,False,False
@typescript/typescript-freebsd-x64,7.0.2,7.0.2,False,False
@typescript/typescript-linux-arm,7.0.2,7.0.2,False,False
@typescript/typescript-linux-arm64,7.0.2,7.0.2,False,False
@typescript/typescript-linux-loong64,7.0.2,7.0.2,False,False
@typescript/typescript-linux-mips64el,7.0.2,7.0.2,False,False
@typescript/typescript-linux-ppc64,7.0.2,7.0.2,False,False
@typescript/typescript-linux-riscv64,7.0.2,7.0.2,False,False
@typescript/typescript-linux-s390x,7.0.2,7.0.2,False,False
@typescript/typescript-linux-x64,7.0.2,7.0.2,False,False
@typescript/typescript-netbsd-arm64,7.0.2,7.0.2,False,False
@typescript/typescript-netbsd-x64,7.0.2,7.0.2,False,False
@typescript/typescript-openbsd-arm64,7.0.2,7.0.2,False,False
@typescript/typescript-openbsd-x64,7.0.2,7.0.2,False,False
@typescript/typescript-sunos-x64,7.0.2,7.0.2,False,False
@typescript/typescript-win32-arm64,7.0.2,7.0.2,False,False
@typescript/typescript-win32-x64,7.0.2,7.0.2,False,False
@vitest/mocker,5.0.2,5.0.3,False,True
@vitest/spy,5.0.2,5.0.3,False,True
@xmldom/xmldom,0.9.12,0.9.12,True,False
a-sync-waterfall,1.0.1,1.0.1,False,False
accepts,2.0.0,1.3.8,False,True
agent-base,7.1.4;9.0.0,9.0.0,False,True
ajv,8.20.0,8.20.0,False,False
ajv-formats,3.0.1,3.0.1,False,False
anynum,1.0.1,1.0.1,False,False
arg,5.0.2,5.0.2,False,False
aria-hidden,1.2.6,1.2.6,False,False
asap,2.0.6,2.0.6,False,False
asn1js,3.0.10,3.0.10,False,False
assertion-error,2.0.1,2.0.1,False,False
atomic-sleep,1.0.0,1.0.0,False,False
babel-dead-code-elimination,1.0.12,2.0.0,False,True
base64-js,1.5.1,1.5.1,False,False
baseline-browser-mapping,2.11.26,2.11.26,False,False
bignumber.js,9.3.1,11.1.5,False,True
body-parser,2.3.0,2.3.0,False,False
boolbase,1.0.0,2.0.0,False,True
bowser,2.14.1,2.14.1,False,False
browserslist,4.29.1,4.29.3,False,True
buffer-equal-constant-time,1.0.1,1.0.1,False,False
bytes,3.1.2,3.1.2,False,False
cac,6.7.14,7.0.0,False,True
call-bind-apply-helpers,1.0.2,1.0.2,False,False
call-bound,1.0.4,1.0.4,False,False
caniuse-lite,1.0.30001812,1.0.30001814,False,True
chai,6.2.2,6.3.0,False,True
cheerio,1.2.0,1.2.0,True,False
cheerio-select,2.1.0,2.1.0,False,False
chokidar,4.0.3,5.0.0,False,True
class-variance-authority,0.7.1,0.7.1,True,False
clsx,2.1.1,2.1.1,True,False
colorette,2.0.20,2.0.20,False,False
commander,5.1.0,15.0.0,False,True
confbox,0.3.1,0.3.1,False,False
content-disposition,1.1.0,3.0.0,False,True
content-type,1.0.5;2.1.0,3.1.1,False,True
convert-source-map,2.0.0,2.0.0,False,False
cookie,0.7.2;1.1.1,2.0.1,False,True
cookie-signature,1.2.2,1.2.2,False,False
cors,2.8.6,2.8.6,False,False
cross-spawn,7.0.6,7.0.6,False,False
css-select,5.2.2,7.0.0,False,True
css-what,6.2.2,8.0.0,False,True
csstype,3.2.3,3.2.3,False,False
d3-array,3.2.4,3.2.4,False,False
d3-color,3.1.0,3.1.0,False,False
d3-ease,3.0.1,3.0.1,False,False
d3-format,3.1.2,3.1.2,False,False
d3-interpolate,3.0.1,3.0.1,False,False
d3-path,3.1.0,3.1.0,False,False
d3-scale,4.0.2,4.0.2,False,False
d3-shape,3.2.0,3.2.0,False,False
d3-time,3.1.0,3.1.0,False,False
d3-time-format,4.1.0,4.1.0,False,False
d3-timer,3.0.1,3.0.1,False,False
data-uri-to-buffer,4.0.1,8.0.0,False,True
date-fns,4.4.0,4.4.0,True,False
dateformat,4.6.3,5.0.3,False,True
debug,4.4.3,4.4.3,False,False
decimal.js-light,2.5.1,2.5.1,False,False
dedent,1.7.2,1.7.2,False,False
depd,2.0.0,2.0.0,False,False
detect-libc,2.1.2,2.1.2,False,False
detect-node-es,1.1.0,1.1.0,False,False
diff,8.0.4,9.0.0,False,True
dom-serializer,2.0.0,3.1.1,False,True
domelementtype,2.3.0,3.0.0,False,True
domhandler,5.0.3,6.0.1,False,True
domutils,3.2.2,4.0.2,False,True
dunder-proto,1.0.1,1.0.1,False,False
ecdsa-sig-formatter,1.0.11,1.0.11,False,False
ee-first,1.1.1,1.1.1,False,False
electron-to-chromium,1.5.439,1.5.443,False,True
encodeurl,2.0.0,2.0.0,False,False
encoding-sniffer,0.2.1,1.0.2,False,True
end-of-stream,1.4.5,1.4.5,False,False
enhanced-resolve,5.25.1,5.26.0,False,True
entities,4.5.0;6.0.1;7.0.1,8.1.0,False,True
es-define-property,1.0.1,1.0.1,False,False
es-errors,1.3.0,1.3.0,False,False
es-module-lexer,1.7.0;2.3.2,3.0.2,False,True
es-object-atoms,1.1.2,1.1.2,False,False
es-toolkit,1.52.0,1.52.0,False,False
esbuild,0.28.2,0.28.2,False,False
escalade,3.2.0,3.2.0,False,False
escape-html,1.0.3,1.0.3,False,False
estree-walker,3.0.3,3.0.3,False,False
etag,1.8.1,1.8.1,False,False
eventemitter3,5.0.4,5.0.4,False,False
eventsource,3.0.7,5.1.2,False,True
eventsource-parser,3.1.1,4.1.1,False,True
exit-hook,2.2.1,5.1.0,False,True
expect-type,1.4.0,1.4.0,False,False
express,5.2.1,5.2.1,False,False
express-rate-limit,8.7.0,8.7.0,False,False
exsolve,1.1.1,1.1.1,False,False
extend,3.0.2,3.0.2,False,False
fast-copy,4.1.1,4.1.1,False,False
fast-deep-equal,3.1.3,3.1.3,False,False
fast-safe-stringify,2.1.1,2.1.1,False,False
fast-sha256,1.3.0,1.3.0,False,False
fast-uri,3.1.8,4.2.1,False,True
fast-xml-builder,1.3.1,1.3.1,False,False
fast-xml-parser,5.11.1,5.11.2,True,True
fdir,6.5.0,6.5.0,False,False
fetch-blob,3.2.0,4.0.0,False,True
finalhandler,2.1.1,2.1.1,False,False
formdata-polyfill,4.0.10,4.0.10,False,False
forwarded,0.2.0,0.2.0,False,False
fresh,2.0.0,0.5.2,False,True
fsevents,2.3.3,2.3.3,False,False
function-bind,1.1.2,1.1.2,False,False
gaxios,7.3.1,8.1.0,False,True
gcp-metadata,8.1.2,9.0.4,False,True
gensync,1.0.0-beta.2,1.0.0-beta.2,False,False
get-intrinsic,1.3.0,1.3.0,False,False
get-nonce,1.0.1,1.0.1,False,False
get-proto,1.0.1,1.0.1,False,False
globrex,0.1.2,0.1.2,False,False
google-auth-library,10.9.1,11.1.0,False,True
google-logging-utils,1.1.3,2.0.1,False,True
gopd,1.2.0,1.2.0,False,False
graceful-fs,4.2.11,4.2.11,False,False
has-symbols,1.1.0,1.1.0,False,False
hash-wasm,4.12.0,4.12.0,True,False
hasown,2.0.4,2.0.4,False,False
help-me,5.0.0,5.0.0,False,False
hono,4.13.9,4.13.12,True,True
htmlparser2,10.1.0,12.0.0,False,True
http-errors,2.0.1,2.0.1,False,False
http-proxy-agent,9.1.0,9.1.0,False,False
https-proxy-agent,7.0.6;9.1.0,9.1.0,False,True
iconv-lite,0.6.3;0.7.3,0.7.3,False,True
ignore,7.0.8,7.0.11,False,True
immer,11.1.18,11.1.18,False,False
inherits,2.0.4,2.0.4,False,False
internmap,2.0.3,2.0.3,False,False
ip-address,10.7.2,10.7.2,False,False
ipaddr.js,1.9.1,2.5.0,False,True
is-promise,4.0.0,4.0.0,False,False
is-unsafe,2.0.2,2.0.2,False,False
isbot,5.2.2,5.2.2,True,False
isexe,2.0.0,4.0.0,False,True
jiti,2.7.0,2.7.0,False,False
jose,6.2.12,6.2.12,False,False
joycon,3.1.1,3.1.1,False,False
jpeg-js,0.4.4,0.4.4,True,False
js-tokens,4.0.0,10.0.0,False,True
jsesc,3.0.2,3.1.0,False,True
json-bigint,1.0.0,1.0.0,False,False
json-schema-to-ts,3.1.1,3.1.1,False,False
json-schema-traverse,1.0.0,1.0.0,False,False
json-schema-typed,8.0.2,8.0.2,False,False
json5,2.2.3,2.2.3,False,False
jwa,2.0.1,2.0.1,False,False
jws,4.0.1,4.0.1,False,False
kysely,0.29.6,0.29.6,True,False
lightningcss,1.32.0,1.33.0,False,True
lightningcss-android-arm64,1.32.0,1.33.0,False,True
lightningcss-darwin-arm64,1.32.0,1.33.0,False,True
lightningcss-darwin-x64,1.32.0,1.33.0,False,True
lightningcss-freebsd-x64,1.32.0,1.33.0,False,True
lightningcss-linux-arm-gnueabihf,1.32.0,1.33.0,False,True
lightningcss-linux-arm64-gnu,1.32.0,1.33.0,False,True
lightningcss-linux-arm64-musl,1.32.0,1.33.0,False,True
lightningcss-linux-x64-gnu,1.32.0,1.33.0,False,True
lightningcss-linux-x64-musl,1.32.0,1.33.0,False,True
lightningcss-win32-arm64-msvc,1.32.0,1.33.0,False,True
lightningcss-win32-x64-msvc,1.32.0,1.33.0,False,True
lodash,4.18.1,4.18.1,False,False
long,5.3.2,5.3.2,False,False
lru-cache,5.1.1,11.5.3,False,True
lucide-react,0.576.0,1.49.0,True,True
magic-string,0.30.21;1.4.2,1.4.2,False,True
math-intrinsics,1.1.0,1.1.0,False,False
media-typer,1.1.1,2.0.0,False,True
merge-descriptors,2.0.0,2.0.0,False,False
mime-db,1.54.0,1.54.0,False,False
mime-types,3.0.2,3.0.2,False,False
minimist,1.2.8,1.2.8,False,False
ms,2.1.3,2.1.3,False,False
nanoid,3.3.19,6.0.1,False,True
negotiator,1.1.0,1.1.0,False,False
next-themes,0.4.6,0.4.6,True,False
node-domexception,1.0.0,2.0.2,False,True
node-fetch,3.3.2,3.3.2,False,False
node-releases,2.0.57,2.0.57,False,False
nth-check,2.1.1,3.0.1,False,True
nunjucks,3.2.4,3.2.4,True,False
object-assign,4.1.1,4.1.1,False,False
object-inspect,1.13.4,1.13.4,False,False
obug,2.2.1,3.0.0,False,True
on-exit-leak-free,2.1.2,2.1.2,False,False
on-finished,2.4.1,2.4.1,False,False
once,1.4.0,1.4.0,False,False
openai,6.40.0,7.25.0,False,True
openapi3-ts,4.6.1,4.6.1,False,False
openworkflow,0.10.1,0.10.1,True,False
p-map,7.0.8,7.0.8,False,False
p-retry,4.6.2,8.0.1,False,True
parse5,7.3.0,8.0.1,False,True
parse5-htmlparser2-tree-adapter,7.1.0,8.0.1,False,True
parse5-parser-stream,7.1.2,8.0.0,False,True
parseurl,1.3.3,1.3.3,False,False
partial-json,0.1.7,0.1.7,False,False
path-expression-matcher,1.6.2,1.6.2,False,False
path-key,3.1.1,4.0.0,False,True
path-to-regexp,8.4.2,8.4.2,False,False
pathe,1.1.2;2.0.3,2.0.3,False,True
pg,8.23.0,8.23.1,True,True
pg-cloudflare,1.4.0,1.4.1,False,True
pg-connection-string,2.14.0,2.14.1,False,True
pg-int8,1.0.1,1.0.1,False,False
pg-pool,3.14.0,3.14.0,False,False
pg-protocol,1.16.0,1.16.1,False,True
pg-types,2.2.0,4.1.0,False,True
pgpass,1.0.5,1.0.6,False,True
picocolors,1.1.1,1.1.1,False,False
picomatch,4.0.7,4.0.7,False,False
pino,10.3.1,10.3.1,True,False
pino-abstract-transport,3.0.0,3.0.0,False,False
pino-pretty,13.1.3,13.1.3,True,False
pino-std-serializers,7.1.0,7.1.0,False,False
pkce-challenge,5.0.1,6.0.0,False,True
pkg-types,2.3.3,2.3.3,False,False
playwright-core,1.63.0,1.63.0,True,False
postcss,8.5.28,8.5.28,False,False
postgres,3.4.9,3.4.9,True,False
postgres-array,2.0.0,3.0.4,False,True
postgres-bytea,1.0.1,3.0.0,False,True
postgres-date,1.0.7,2.1.0,False,True
postgres-interval,1.2.0,4.1.0,False,True
prettier,3.9.9,3.9.9,False,False
process-warning,5.1.0,5.1.0,False,False
protobufjs,7.6.6,8.8.0,False,True
proxy-addr,2.0.8,2.0.8,False,False
proxy-agent-negotiate,1.1.0,1.1.0,False,False
pump,3.0.4,3.0.4,False,False
pvtsutils,1.3.6,1.3.6,False,False
pvutils,1.2.0,1.2.0,False,False
qr-code-styling,1.9.2,1.9.2,True,False
qrcode-generator,1.5.2,2.0.4,False,True
qs,6.16.0,6.16.0,False,False
quick-format-unescaped,4.0.4,4.0.4,False,False
radix-ui,1.6.7,1.6.7,True,False
range-parser,1.3.0,1.3.0,False,False
raw-body,3.0.2,4.0.0,False,True
react,19.3.0,19.3.0,True,False
react-day-picker,10.0.1,10.0.2,True,True
react-dom,19.3.0,19.3.0,True,False
react-is,19.3.0,19.3.0,False,False
react-redux,9.3.0,9.3.0,False,False
react-refresh,0.14.2,0.19.0,False,True
react-remove-scroll,2.7.2,2.7.2,False,False
react-remove-scroll-bar,2.3.8,2.3.8,False,False
react-router,7.12.0,8.4.0,True,True
react-style-singleton,2.2.3,2.2.3,False,False
readdirp,4.1.2,5.1.1,False,True
real-require,0.2.0;1.0.0,1.0.0,False,True
recharts,3.10.1,3.10.1,True,False
redux,5.0.1,5.0.1,False,False
redux-thunk,3.1.0,3.1.0,False,False
reflect-metadata,0.2.2,0.2.2,False,False
require-from-string,2.0.2,2.0.2,False,False
reselect,5.2.0,5.3.0,False,True
retry,0.13.1,0.13.1,False,False
rollup,4.63.5,4.63.5,False,False
router,2.2.0,2.2.0,False,False
safe-buffer,5.2.1,5.2.1,False,False
safe-stable-stringify,2.5.0,2.5.0,False,False
safer-buffer,2.1.2,2.1.2,False,False
scheduler,0.28.0,0.28.0,False,False
secure-json-parse,4.1.0,4.1.0,False,False
semver,6.3.1;7.8.5,7.8.5,False,True
send,1.2.1,1.2.1,False,False
serve-static,2.2.1,2.2.1,False,False
set-cookie-parser,2.7.2,3.1.2,False,True
setprototypeof,1.2.0,1.2.0,False,False
shebang-command,2.0.0,2.0.0,False,False
shebang-regex,3.0.0,4.0.0,False,True
side-channel,1.1.1,1.1.1,False,False
side-channel-list,1.0.1,1.0.1,False,False
side-channel-map,1.0.1,1.0.1,False,False
side-channel-weakmap,1.0.2,1.0.2,False,False
sonic-boom,4.2.1,5.0.1,False,True
sonner,2.0.8,2.0.8,True,False
source-map-js,1.2.1,1.2.2,False,True
split2,4.2.0,4.2.0,False,False
standardwebhooks,1.1.1,1.1.1,False,False
statuses,2.0.2,2.0.2,False,False
std-env,4.2.0,4.3.0,False,True
strip-json-comments,5.0.3,5.0.3,False,False
strnum,2.4.2,2.4.2,False,False
tailwind-merge,3.7.0,3.7.0,True,False
tailwindcss,4.3.3,4.3.3,True,False
tapable,2.3.3,2.3.3,False,False
thread-stream,4.2.0,4.2.0,False,False
tiny-invariant,1.3.3,1.3.3,False,False
tinybench,6.2.0,6.2.0,False,False
tinyexec,1.3.1,1.3.1,False,False
tinyglobby,0.2.17,0.2.17,False,False
toidentifier,1.0.1,1.0.1,False,False
ts-algebra,2.0.0,2.0.0,False,False
tsconfck,3.1.6,3.1.6,False,False
tslib,1.14.1;2.8.1,2.8.1,False,True
tsyringe,4.10.0,4.10.0,False,False
tw-animate-css,1.4.0,1.4.0,True,False
type-is,2.1.0,3.0.0,False,True
typebox,1.3.27,1.3.34,True,True
typescript,7.0.2,7.0.2,True,False
undici,7.30.0,8.11.2,False,True
undici-types,7.24.6,8.11.2,False,True
unpipe,1.0.0,1.0.0,False,False
update-browserslist-db,1.3.3,1.3.3,False,False
use-callback-ref,1.3.3,1.3.3,False,False
use-sidecar,1.1.3,1.1.3,False,False
use-sync-external-store,1.7.0,1.7.0,False,False
valibot,1.5.0,1.5.0,False,False
vary,1.1.2,1.1.2,False,False
victory-vendor,37.3.6,37.3.6,False,False
vite,7.3.6,8.3.1,True,True
vite-node,3.2.4,6.0.0,False,True
vite-tsconfig-paths,5.1.4,6.1.1,True,True
vitest,5.0.2,5.0.3,True,True
web-streams-polyfill,3.3.3,4.3.0,False,True
whatwg-encoding,3.1.1,3.1.1,False,False
whatwg-mimetype,4.0.0,5.0.0,False,True
which,2.0.2,7.0.0,False,True
why-is-node-running,3.2.2,3.2.2,False,False
wrappy,1.0.2,1.0.2,False,False
ws,8.21.3,8.22.0,False,True
xml-naming,0.3.0,0.3.0,False,False
xpath,0.0.34,0.0.34,True,False
xtend,4.0.2,4.0.2,False,False
yallist,3.1.1,5.0.0,False,True
yaml,2.9.0;2.9.1,2.9.1,True,True
zod,4.6.5,4.6.5,True,False
zod-to-json-schema,3.25.2,3.25.2,False,False
1 package locked_versions registry_latest workspace_direct has_older_or_different_version
2 @anthropic-ai/sdk 0.124.0 0.131.0 False True
3 @asteasolutions/zod-to-openapi 9.1.0 9.1.0 False False
4 @aws-sdk/client-bedrock-runtime 3.1127.0 3.1144.0 False True
5 @aws-sdk/core 3.978.1 3.978.1 False False
6 @aws-sdk/credential-provider-env 3.972.72 3.972.72 False False
7 @aws-sdk/credential-provider-http 3.972.74 3.972.74 False False
8 @aws-sdk/credential-provider-ini 3.973.17 3.973.17 False False
9 @aws-sdk/credential-provider-login 3.972.79 3.972.79 False False
10 @aws-sdk/credential-provider-node 3.972.84 3.972.84 False False
11 @aws-sdk/credential-provider-process 3.972.72 3.972.72 False False
12 @aws-sdk/credential-provider-sso 3.973.16 3.973.16 False False
13 @aws-sdk/credential-provider-web-identity 3.972.78 3.972.78 False False
14 @aws-sdk/eventstream-handler-node 3.972.35 3.972.35 False False
15 @aws-sdk/middleware-eventstream 3.972.30 3.972.30 False False
16 @aws-sdk/middleware-websocket 3.972.54 3.972.54 False False
17 @aws-sdk/nested-clients 3.997.46 3.997.46 False False
18 @aws-sdk/signature-v4-multi-region 3.996.47 3.996.47 False False
19 @aws-sdk/token-providers 3.1127.0;3.1138.0 3.1144.0 False True
20 @aws-sdk/types 3.974.6 3.974.6 False False
21 @aws-sdk/xml-builder 3.972.41 3.972.41 False False
22 @aws/lambda-invoke-store 0.3.0 0.3.0 False False
23 @babel/code-frame 7.29.7 8.0.6 False True
24 @babel/compat-data 7.29.7 8.0.5 False True
25 @babel/core 7.29.7 8.0.6 False True
26 @babel/generator 7.29.8 8.0.6 False True
27 @babel/helper-annotate-as-pure 7.29.7 8.0.0 False True
28 @babel/helper-compilation-targets 7.29.7 8.0.6 False True
29 @babel/helper-create-class-features-plugin 7.29.7 8.0.6 False True
30 @babel/helper-globals 7.29.7 8.0.6 False True
31 @babel/helper-member-expression-to-functions 7.29.7 8.0.5 False True
32 @babel/helper-module-imports 7.29.7 8.0.0 False True
33 @babel/helper-module-transforms 7.29.7 8.0.6 False True
34 @babel/helper-optimise-call-expression 7.29.7 8.0.0 False True
35 @babel/helper-plugin-utils 7.29.7 8.0.1 False True
36 @babel/helper-replace-supers 7.29.7 8.0.1 False True
37 @babel/helper-skip-transparent-expression-wrappers 7.29.7 8.0.0 False True
38 @babel/helper-string-parser 7.29.7 8.0.6 False True
39 @babel/helper-validator-identifier 7.29.7 8.0.6 False True
40 @babel/helper-validator-option 7.29.7 8.0.0 False True
41 @babel/helpers 7.29.7 8.0.5 False True
42 @babel/parser 7.29.9 7.29.9 False False
43 @babel/plugin-syntax-jsx 7.29.7 8.0.1 False True
44 @babel/plugin-syntax-typescript 7.29.7 8.0.3 False True
45 @babel/plugin-transform-modules-commonjs 7.29.7 8.0.1 False True
46 @babel/plugin-transform-typescript 7.29.9 7.29.9 False False
47 @babel/preset-typescript 7.29.7 8.0.1 False True
48 @babel/runtime 7.29.7 8.0.5 False True
49 @babel/template 7.29.7 8.0.0 False True
50 @babel/traverse 7.29.8 8.0.6 False True
51 @babel/types 7.29.8 8.0.6 False True
52 @biomejs/biome 2.5.14 2.5.15 True True
53 @biomejs/cli-darwin-arm64 2.5.14 2.5.15 False True
54 @biomejs/cli-darwin-x64 2.5.14 2.5.15 False True
55 @biomejs/cli-linux-arm64 2.5.14 2.5.15 False True
56 @biomejs/cli-linux-arm64-musl 2.5.14 2.5.15 False True
57 @biomejs/cli-linux-x64 2.5.14 2.5.15 False True
58 @biomejs/cli-linux-x64-musl 2.5.14 2.5.15 False True
59 @biomejs/cli-win32-arm64 2.5.14 2.5.15 False True
60 @biomejs/cli-win32-x64 2.5.14 2.5.15 False True
61 @date-fns/tz 1.5.0 1.5.0 False False
62 @earendil-works/chord 0.87.1 0.99.2 False True
63 @earendil-works/pi-agent-core 0.87.1 0.99.2 True True
64 @earendil-works/pi-ai 0.87.1 0.99.2 True True
65 @earendil-works/pi-telemetry 0.87.1 0.99.2 False True
66 @esbuild/aix-ppc64 0.28.2 0.28.2 False False
67 @esbuild/android-arm 0.28.2 0.28.2 False False
68 @esbuild/android-arm64 0.28.2 0.28.2 False False
69 @esbuild/android-x64 0.28.2 0.28.2 False False
70 @esbuild/darwin-arm64 0.28.2 0.28.2 False False
71 @esbuild/darwin-x64 0.28.2 0.28.2 False False
72 @esbuild/freebsd-arm64 0.28.2 0.28.2 False False
73 @esbuild/freebsd-x64 0.28.2 0.28.2 False False
74 @esbuild/linux-arm 0.28.2 0.28.2 False False
75 @esbuild/linux-arm64 0.28.2 0.28.2 False False
76 @esbuild/linux-ia32 0.28.2 0.28.2 False False
77 @esbuild/linux-loong64 0.28.2 0.28.2 False False
78 @esbuild/linux-mips64el 0.28.2 0.28.2 False False
79 @esbuild/linux-ppc64 0.28.2 0.28.2 False False
80 @esbuild/linux-riscv64 0.28.2 0.28.2 False False
81 @esbuild/linux-s390x 0.28.2 0.28.2 False False
82 @esbuild/linux-x64 0.28.2 0.28.2 False False
83 @esbuild/netbsd-arm64 0.28.2 0.28.2 False False
84 @esbuild/netbsd-x64 0.28.2 0.28.2 False False
85 @esbuild/openbsd-arm64 0.28.2 0.28.2 False False
86 @esbuild/openbsd-x64 0.28.2 0.28.2 False False
87 @esbuild/openharmony-arm64 0.28.2 0.28.2 False False
88 @esbuild/sunos-x64 0.28.2 0.28.2 False False
89 @esbuild/win32-arm64 0.28.2 0.28.2 False False
90 @esbuild/win32-ia32 0.28.2 0.28.2 False False
91 @esbuild/win32-x64 0.28.2 0.28.2 False False
92 @floating-ui/core 1.8.0 1.8.0 False False
93 @floating-ui/dom 1.8.0 1.8.0 False False
94 @floating-ui/react-dom 2.1.9 2.1.9 False False
95 @floating-ui/utils 0.2.12 0.2.12 False False
96 @fontsource-variable/inter-tight 5.3.0 5.3.0 True False
97 @fontsource-variable/space-grotesk 5.3.0 5.3.0 True False
98 @google/genai 2.21.0 2.25.0 False True
99 @hexagon/base64 1.1.28 2.0.4 False True
100 @hono/node-server 2.1.1 2.1.3 False True
101 @hono/swagger-ui 0.6.1 0.6.1 True False
102 @hono/zod-openapi 1.6.3 1.6.3 True False
103 @hono/zod-validator 0.9.1 0.9.1 False False
104 @jridgewell/gen-mapping 0.3.13 0.3.13 False False
105 @jridgewell/remapping 2.3.5 2.3.5 False False
106 @jridgewell/resolve-uri 3.1.2 3.1.2 False False
107 @jridgewell/sourcemap-codec 1.6.0 1.6.0 False False
108 @jridgewell/trace-mapping 0.3.31 0.3.31 False False
109 @levischuck/tiny-cbor 0.2.11 0.3.6 False True
110 @mjackson/node-fetch-server 0.2.0 0.7.0 False True
111 @modelcontextprotocol/sdk 1.30.1 1.31.0 True True
112 @napi-rs/lzma-linux-x64-gnu 1.5.1 1.5.1 False False
113 @nodable/entities 3.0.0 3.1.0 False True
114 @peculiar/asn1-android 2.9.5 2.10.0 False True
115 @peculiar/asn1-cms 2.9.5 2.10.0 False True
116 @peculiar/asn1-csr 2.9.5 2.10.0 False True
117 @peculiar/asn1-ecc 2.9.5 2.10.0 False True
118 @peculiar/asn1-pfx 2.9.5 2.10.0 False True
119 @peculiar/asn1-pkcs8 2.9.5 2.10.0 False True
120 @peculiar/asn1-pkcs9 2.9.5 2.10.0 False True
121 @peculiar/asn1-rsa 2.9.5 2.10.0 False True
122 @peculiar/asn1-schema 2.9.5 2.10.0 False True
123 @peculiar/asn1-x509 2.9.5 2.10.0 False True
124 @peculiar/asn1-x509-attr 2.9.5 2.10.0 False True
125 @peculiar/utils 2.0.3 2.0.3 False False
126 @peculiar/x509 1.14.3 2.1.0 False True
127 @pinojs/redact 0.4.0 0.4.0 False False
128 @protobufjs/aspromise 1.1.2 1.1.2 False False
129 @protobufjs/base64 1.1.2 1.1.2 False False
130 @protobufjs/codegen 2.0.5 2.0.5 False False
131 @protobufjs/eventemitter 1.1.1 1.1.1 False False
132 @protobufjs/fetch 1.1.1 1.1.1 False False
133 @protobufjs/float 1.0.2 1.0.2 False False
134 @protobufjs/path 1.1.2 1.1.2 False False
135 @protobufjs/pool 1.1.0 1.1.0 False False
136 @protobufjs/utf8 1.1.2 1.1.2 False False
137 @radix-ui/number 1.1.3 1.1.3 False False
138 @radix-ui/primitive 1.1.7 1.1.7 False False
139 @radix-ui/react-accessible-icon 1.1.15 1.1.15 False False
140 @radix-ui/react-accordion 1.2.20 1.2.20 False False
141 @radix-ui/react-alert-dialog 1.1.23 1.1.23 False False
142 @radix-ui/react-arrow 1.1.15 1.1.15 False False
143 @radix-ui/react-aspect-ratio 1.1.15 1.1.15 False False
144 @radix-ui/react-avatar 1.2.6 1.2.6 False False
145 @radix-ui/react-checkbox 1.3.11 1.3.11 False False
146 @radix-ui/react-collapsible 1.1.20 1.1.20 False False
147 @radix-ui/react-collection 1.1.15 1.1.15 False False
148 @radix-ui/react-compose-refs 1.1.5 1.1.5 False False
149 @radix-ui/react-context 1.2.2 1.2.2 False False
150 @radix-ui/react-context-menu 2.3.7 2.3.7 False False
151 @radix-ui/react-dialog 1.1.23 1.1.23 False False
152 @radix-ui/react-direction 1.1.4 1.1.4 False False
153 @radix-ui/react-dismissable-layer 1.1.19 1.1.19 False False
154 @radix-ui/react-dropdown-menu 2.1.24 2.1.24 False False
155 @radix-ui/react-focus-guards 1.1.6 1.1.6 False False
156 @radix-ui/react-focus-scope 1.1.16 1.1.16 False False
157 @radix-ui/react-form 0.1.16 0.1.16 False False
158 @radix-ui/react-hover-card 1.1.23 1.1.23 False False
159 @radix-ui/react-id 1.1.4 1.1.4 False False
160 @radix-ui/react-label 2.1.15 2.1.15 False False
161 @radix-ui/react-menu 2.1.24 2.1.24 False False
162 @radix-ui/react-menubar 1.1.24 1.1.24 False False
163 @radix-ui/react-navigation-menu 1.2.22 1.2.22 False False
164 @radix-ui/react-one-time-password-field 0.1.16 0.1.16 False False
165 @radix-ui/react-password-toggle-field 0.1.11 0.1.11 False False
166 @radix-ui/react-popover 1.1.23 1.1.23 False False
167 @radix-ui/react-popper 1.3.7 1.3.7 False False
168 @radix-ui/react-portal 1.1.17 1.1.17 False False
169 @radix-ui/react-presence 1.1.10 1.1.10 False False
170 @radix-ui/react-primitive 2.1.10 2.1.10 False False
171 @radix-ui/react-progress 1.1.16 1.1.16 False False
172 @radix-ui/react-radio-group 1.4.7 1.4.7 False False
173 @radix-ui/react-roving-focus 1.1.19 1.1.19 False False
174 @radix-ui/react-scroll-area 1.2.18 1.2.18 False False
175 @radix-ui/react-select 2.3.7 2.3.7 False False
176 @radix-ui/react-separator 1.1.15 1.1.15 False False
177 @radix-ui/react-slider 1.4.7 1.4.7 False False
178 @radix-ui/react-slot 1.3.3 1.3.3 False False
179 @radix-ui/react-switch 1.3.7 1.3.7 False False
180 @radix-ui/react-tabs 1.1.21 1.1.21 False False
181 @radix-ui/react-toast 1.2.23 1.2.23 False False
182 @radix-ui/react-toggle 1.1.18 1.1.18 False False
183 @radix-ui/react-toggle-group 1.1.19 1.1.19 False False
184 @radix-ui/react-toolbar 1.1.19 1.1.19 False False
185 @radix-ui/react-tooltip 1.2.16 1.2.16 False False
186 @radix-ui/react-use-callback-ref 1.1.4 1.1.4 False False
187 @radix-ui/react-use-controllable-state 1.2.6 1.2.6 False False
188 @radix-ui/react-use-effect-event 0.0.5 0.0.5 False False
189 @radix-ui/react-use-escape-keydown 1.1.5 1.1.5 False False
190 @radix-ui/react-use-is-hydrated 0.1.3 0.1.3 False False
191 @radix-ui/react-use-layout-effect 1.1.4 1.1.4 False False
192 @radix-ui/react-use-previous 1.1.4 1.1.4 False False
193 @radix-ui/react-use-rect 1.1.4 1.1.4 False False
194 @radix-ui/react-use-size 1.1.4 1.1.4 False False
195 @radix-ui/react-visually-hidden 1.2.11 1.2.11 False False
196 @radix-ui/rect 1.1.3 1.1.3 False False
197 @react-router/dev 7.12.0 8.4.0 True True
198 @react-router/node 7.12.0 8.4.0 True True
199 @reduxjs/toolkit 2.12.0 2.13.0 False True
200 @remix-run/node-fetch-server 0.9.0 0.14.2 False True
201 @rollup/rollup-android-arm-eabi 4.63.5 4.63.5 False False
202 @rollup/rollup-android-arm64 4.63.5 4.63.5 False False
203 @rollup/rollup-darwin-arm64 4.63.5 4.63.5 False False
204 @rollup/rollup-darwin-x64 4.63.5 4.63.5 False False
205 @rollup/rollup-freebsd-arm64 4.63.5 4.63.5 False False
206 @rollup/rollup-freebsd-x64 4.63.5 4.63.5 False False
207 @rollup/rollup-linux-arm-gnueabihf 4.63.5 4.63.5 False False
208 @rollup/rollup-linux-arm-musleabihf 4.63.5 4.63.5 False False
209 @rollup/rollup-linux-arm64-gnu 4.63.5 4.63.5 False False
210 @rollup/rollup-linux-arm64-musl 4.63.5 4.63.5 False False
211 @rollup/rollup-linux-loong64-gnu 4.63.5 4.63.5 False False
212 @rollup/rollup-linux-loong64-musl 4.63.5 4.63.5 False False
213 @rollup/rollup-linux-ppc64-gnu 4.63.5 4.63.5 False False
214 @rollup/rollup-linux-ppc64-musl 4.63.5 4.63.5 False False
215 @rollup/rollup-linux-riscv64-gnu 4.63.5 4.63.5 False False
216 @rollup/rollup-linux-riscv64-musl 4.63.5 4.63.5 False False
217 @rollup/rollup-linux-s390x-gnu 4.63.5 4.63.5 False False
218 @rollup/rollup-linux-x64-gnu 4.63.5 4.63.5 False False
219 @rollup/rollup-linux-x64-musl 4.63.5 4.63.5 False False
220 @rollup/rollup-openbsd-x64 4.63.5 4.63.5 False False
221 @rollup/rollup-openharmony-arm64 4.63.5 4.63.5 False False
222 @rollup/rollup-win32-arm64-msvc 4.63.5 4.63.5 False False
223 @rollup/rollup-win32-ia32-msvc 4.63.5 4.63.5 False False
224 @rollup/rollup-win32-x64-gnu 4.63.5 4.63.5 False False
225 @rollup/rollup-win32-x64-msvc 4.63.5 4.63.5 False False
226 @simplewebauthn/browser 13.3.0 14.0.0 True True
227 @simplewebauthn/server 13.3.3 14.0.3 True True
228 @smithy/core 3.35.0 3.35.1 False True
229 @smithy/credential-provider-imds 4.5.2 4.5.2 False False
230 @smithy/fetch-http-handler 5.8.0 5.8.0 False False
231 @smithy/node-http-handler 4.12.1 4.12.1 False False
232 @smithy/signature-v4 5.7.4 5.7.4 False False
233 @smithy/types 4.19.0 4.19.0 False False
234 @stablelib/base64 1.0.1 2.0.1 False True
235 @standard-schema/spec 1.1.0 1.1.0 False False
236 @standard-schema/utils 0.3.0 0.3.0 False False
237 @tailwindcss/node 4.3.3 4.3.3 False False
238 @tailwindcss/oxide 4.3.3 4.3.3 False False
239 @tailwindcss/oxide-android-arm64 4.3.3 4.3.3 False False
240 @tailwindcss/oxide-darwin-arm64 4.3.3 4.3.3 False False
241 @tailwindcss/oxide-darwin-x64 4.3.3 4.3.3 False False
242 @tailwindcss/oxide-freebsd-x64 4.3.3 4.3.3 False False
243 @tailwindcss/oxide-linux-arm-gnueabihf 4.3.3 4.3.3 False False
244 @tailwindcss/oxide-linux-arm64-gnu 4.3.3 4.3.3 False False
245 @tailwindcss/oxide-linux-arm64-musl 4.3.3 4.3.3 False False
246 @tailwindcss/oxide-linux-x64-gnu 4.3.3 4.3.3 False False
247 @tailwindcss/oxide-linux-x64-musl 4.3.3 4.3.3 False False
248 @tailwindcss/oxide-wasm32-wasi 4.3.3 4.3.3 False False
249 @tailwindcss/oxide-win32-arm64-msvc 4.3.3 4.3.3 False False
250 @tailwindcss/oxide-win32-x64-msvc 4.3.3 4.3.3 False False
251 @tailwindcss/vite 4.3.3 4.3.3 True False
252 @tanstack/query-core 5.104.0 5.104.0 False False
253 @tanstack/react-query 5.104.0 5.104.0 True False
254 @types/chai 5.2.3 5.2.3 False False
255 @types/d3-array 3.2.2 3.2.2 False False
256 @types/d3-color 3.1.3 3.1.3 False False
257 @types/d3-ease 3.0.2 3.0.2 False False
258 @types/d3-interpolate 3.0.4 3.0.4 False False
259 @types/d3-path 3.1.1 3.1.1 False False
260 @types/d3-scale 4.0.9 4.0.9 False False
261 @types/d3-shape 3.2.0 3.2.0 False False
262 @types/d3-time 3.0.4 3.0.4 False False
263 @types/d3-timer 3.0.2 3.0.2 False False
264 @types/deep-eql 4.0.2 4.0.2 False False
265 @types/estree 1.0.9 1.0.9 False False
266 @types/node 24.19.0 26.6.3 True True
267 @types/nunjucks 3.2.6 3.2.6 True False
268 @types/pg 8.23.1 8.23.1 True False
269 @types/react 19.3.0 19.3.0 True False
270 @types/react-dom 19.3.0 19.3.0 True False
271 @types/retry 0.12.0 0.12.5 False True
272 @types/use-sync-external-store 0.0.6 1.7.0 False True
273 @typescript/typescript-aix-ppc64 7.0.2 7.0.2 False False
274 @typescript/typescript-darwin-arm64 7.0.2 7.0.2 False False
275 @typescript/typescript-darwin-x64 7.0.2 7.0.2 False False
276 @typescript/typescript-freebsd-arm64 7.0.2 7.0.2 False False
277 @typescript/typescript-freebsd-x64 7.0.2 7.0.2 False False
278 @typescript/typescript-linux-arm 7.0.2 7.0.2 False False
279 @typescript/typescript-linux-arm64 7.0.2 7.0.2 False False
280 @typescript/typescript-linux-loong64 7.0.2 7.0.2 False False
281 @typescript/typescript-linux-mips64el 7.0.2 7.0.2 False False
282 @typescript/typescript-linux-ppc64 7.0.2 7.0.2 False False
283 @typescript/typescript-linux-riscv64 7.0.2 7.0.2 False False
284 @typescript/typescript-linux-s390x 7.0.2 7.0.2 False False
285 @typescript/typescript-linux-x64 7.0.2 7.0.2 False False
286 @typescript/typescript-netbsd-arm64 7.0.2 7.0.2 False False
287 @typescript/typescript-netbsd-x64 7.0.2 7.0.2 False False
288 @typescript/typescript-openbsd-arm64 7.0.2 7.0.2 False False
289 @typescript/typescript-openbsd-x64 7.0.2 7.0.2 False False
290 @typescript/typescript-sunos-x64 7.0.2 7.0.2 False False
291 @typescript/typescript-win32-arm64 7.0.2 7.0.2 False False
292 @typescript/typescript-win32-x64 7.0.2 7.0.2 False False
293 @vitest/mocker 5.0.2 5.0.3 False True
294 @vitest/spy 5.0.2 5.0.3 False True
295 @xmldom/xmldom 0.9.12 0.9.12 True False
296 a-sync-waterfall 1.0.1 1.0.1 False False
297 accepts 2.0.0 1.3.8 False True
298 agent-base 7.1.4;9.0.0 9.0.0 False True
299 ajv 8.20.0 8.20.0 False False
300 ajv-formats 3.0.1 3.0.1 False False
301 anynum 1.0.1 1.0.1 False False
302 arg 5.0.2 5.0.2 False False
303 aria-hidden 1.2.6 1.2.6 False False
304 asap 2.0.6 2.0.6 False False
305 asn1js 3.0.10 3.0.10 False False
306 assertion-error 2.0.1 2.0.1 False False
307 atomic-sleep 1.0.0 1.0.0 False False
308 babel-dead-code-elimination 1.0.12 2.0.0 False True
309 base64-js 1.5.1 1.5.1 False False
310 baseline-browser-mapping 2.11.26 2.11.26 False False
311 bignumber.js 9.3.1 11.1.5 False True
312 body-parser 2.3.0 2.3.0 False False
313 boolbase 1.0.0 2.0.0 False True
314 bowser 2.14.1 2.14.1 False False
315 browserslist 4.29.1 4.29.3 False True
316 buffer-equal-constant-time 1.0.1 1.0.1 False False
317 bytes 3.1.2 3.1.2 False False
318 cac 6.7.14 7.0.0 False True
319 call-bind-apply-helpers 1.0.2 1.0.2 False False
320 call-bound 1.0.4 1.0.4 False False
321 caniuse-lite 1.0.30001812 1.0.30001814 False True
322 chai 6.2.2 6.3.0 False True
323 cheerio 1.2.0 1.2.0 True False
324 cheerio-select 2.1.0 2.1.0 False False
325 chokidar 4.0.3 5.0.0 False True
326 class-variance-authority 0.7.1 0.7.1 True False
327 clsx 2.1.1 2.1.1 True False
328 colorette 2.0.20 2.0.20 False False
329 commander 5.1.0 15.0.0 False True
330 confbox 0.3.1 0.3.1 False False
331 content-disposition 1.1.0 3.0.0 False True
332 content-type 1.0.5;2.1.0 3.1.1 False True
333 convert-source-map 2.0.0 2.0.0 False False
334 cookie 0.7.2;1.1.1 2.0.1 False True
335 cookie-signature 1.2.2 1.2.2 False False
336 cors 2.8.6 2.8.6 False False
337 cross-spawn 7.0.6 7.0.6 False False
338 css-select 5.2.2 7.0.0 False True
339 css-what 6.2.2 8.0.0 False True
340 csstype 3.2.3 3.2.3 False False
341 d3-array 3.2.4 3.2.4 False False
342 d3-color 3.1.0 3.1.0 False False
343 d3-ease 3.0.1 3.0.1 False False
344 d3-format 3.1.2 3.1.2 False False
345 d3-interpolate 3.0.1 3.0.1 False False
346 d3-path 3.1.0 3.1.0 False False
347 d3-scale 4.0.2 4.0.2 False False
348 d3-shape 3.2.0 3.2.0 False False
349 d3-time 3.1.0 3.1.0 False False
350 d3-time-format 4.1.0 4.1.0 False False
351 d3-timer 3.0.1 3.0.1 False False
352 data-uri-to-buffer 4.0.1 8.0.0 False True
353 date-fns 4.4.0 4.4.0 True False
354 dateformat 4.6.3 5.0.3 False True
355 debug 4.4.3 4.4.3 False False
356 decimal.js-light 2.5.1 2.5.1 False False
357 dedent 1.7.2 1.7.2 False False
358 depd 2.0.0 2.0.0 False False
359 detect-libc 2.1.2 2.1.2 False False
360 detect-node-es 1.1.0 1.1.0 False False
361 diff 8.0.4 9.0.0 False True
362 dom-serializer 2.0.0 3.1.1 False True
363 domelementtype 2.3.0 3.0.0 False True
364 domhandler 5.0.3 6.0.1 False True
365 domutils 3.2.2 4.0.2 False True
366 dunder-proto 1.0.1 1.0.1 False False
367 ecdsa-sig-formatter 1.0.11 1.0.11 False False
368 ee-first 1.1.1 1.1.1 False False
369 electron-to-chromium 1.5.439 1.5.443 False True
370 encodeurl 2.0.0 2.0.0 False False
371 encoding-sniffer 0.2.1 1.0.2 False True
372 end-of-stream 1.4.5 1.4.5 False False
373 enhanced-resolve 5.25.1 5.26.0 False True
374 entities 4.5.0;6.0.1;7.0.1 8.1.0 False True
375 es-define-property 1.0.1 1.0.1 False False
376 es-errors 1.3.0 1.3.0 False False
377 es-module-lexer 1.7.0;2.3.2 3.0.2 False True
378 es-object-atoms 1.1.2 1.1.2 False False
379 es-toolkit 1.52.0 1.52.0 False False
380 esbuild 0.28.2 0.28.2 False False
381 escalade 3.2.0 3.2.0 False False
382 escape-html 1.0.3 1.0.3 False False
383 estree-walker 3.0.3 3.0.3 False False
384 etag 1.8.1 1.8.1 False False
385 eventemitter3 5.0.4 5.0.4 False False
386 eventsource 3.0.7 5.1.2 False True
387 eventsource-parser 3.1.1 4.1.1 False True
388 exit-hook 2.2.1 5.1.0 False True
389 expect-type 1.4.0 1.4.0 False False
390 express 5.2.1 5.2.1 False False
391 express-rate-limit 8.7.0 8.7.0 False False
392 exsolve 1.1.1 1.1.1 False False
393 extend 3.0.2 3.0.2 False False
394 fast-copy 4.1.1 4.1.1 False False
395 fast-deep-equal 3.1.3 3.1.3 False False
396 fast-safe-stringify 2.1.1 2.1.1 False False
397 fast-sha256 1.3.0 1.3.0 False False
398 fast-uri 3.1.8 4.2.1 False True
399 fast-xml-builder 1.3.1 1.3.1 False False
400 fast-xml-parser 5.11.1 5.11.2 True True
401 fdir 6.5.0 6.5.0 False False
402 fetch-blob 3.2.0 4.0.0 False True
403 finalhandler 2.1.1 2.1.1 False False
404 formdata-polyfill 4.0.10 4.0.10 False False
405 forwarded 0.2.0 0.2.0 False False
406 fresh 2.0.0 0.5.2 False True
407 fsevents 2.3.3 2.3.3 False False
408 function-bind 1.1.2 1.1.2 False False
409 gaxios 7.3.1 8.1.0 False True
410 gcp-metadata 8.1.2 9.0.4 False True
411 gensync 1.0.0-beta.2 1.0.0-beta.2 False False
412 get-intrinsic 1.3.0 1.3.0 False False
413 get-nonce 1.0.1 1.0.1 False False
414 get-proto 1.0.1 1.0.1 False False
415 globrex 0.1.2 0.1.2 False False
416 google-auth-library 10.9.1 11.1.0 False True
417 google-logging-utils 1.1.3 2.0.1 False True
418 gopd 1.2.0 1.2.0 False False
419 graceful-fs 4.2.11 4.2.11 False False
420 has-symbols 1.1.0 1.1.0 False False
421 hash-wasm 4.12.0 4.12.0 True False
422 hasown 2.0.4 2.0.4 False False
423 help-me 5.0.0 5.0.0 False False
424 hono 4.13.9 4.13.12 True True
425 htmlparser2 10.1.0 12.0.0 False True
426 http-errors 2.0.1 2.0.1 False False
427 http-proxy-agent 9.1.0 9.1.0 False False
428 https-proxy-agent 7.0.6;9.1.0 9.1.0 False True
429 iconv-lite 0.6.3;0.7.3 0.7.3 False True
430 ignore 7.0.8 7.0.11 False True
431 immer 11.1.18 11.1.18 False False
432 inherits 2.0.4 2.0.4 False False
433 internmap 2.0.3 2.0.3 False False
434 ip-address 10.7.2 10.7.2 False False
435 ipaddr.js 1.9.1 2.5.0 False True
436 is-promise 4.0.0 4.0.0 False False
437 is-unsafe 2.0.2 2.0.2 False False
438 isbot 5.2.2 5.2.2 True False
439 isexe 2.0.0 4.0.0 False True
440 jiti 2.7.0 2.7.0 False False
441 jose 6.2.12 6.2.12 False False
442 joycon 3.1.1 3.1.1 False False
443 jpeg-js 0.4.4 0.4.4 True False
444 js-tokens 4.0.0 10.0.0 False True
445 jsesc 3.0.2 3.1.0 False True
446 json-bigint 1.0.0 1.0.0 False False
447 json-schema-to-ts 3.1.1 3.1.1 False False
448 json-schema-traverse 1.0.0 1.0.0 False False
449 json-schema-typed 8.0.2 8.0.2 False False
450 json5 2.2.3 2.2.3 False False
451 jwa 2.0.1 2.0.1 False False
452 jws 4.0.1 4.0.1 False False
453 kysely 0.29.6 0.29.6 True False
454 lightningcss 1.32.0 1.33.0 False True
455 lightningcss-android-arm64 1.32.0 1.33.0 False True
456 lightningcss-darwin-arm64 1.32.0 1.33.0 False True
457 lightningcss-darwin-x64 1.32.0 1.33.0 False True
458 lightningcss-freebsd-x64 1.32.0 1.33.0 False True
459 lightningcss-linux-arm-gnueabihf 1.32.0 1.33.0 False True
460 lightningcss-linux-arm64-gnu 1.32.0 1.33.0 False True
461 lightningcss-linux-arm64-musl 1.32.0 1.33.0 False True
462 lightningcss-linux-x64-gnu 1.32.0 1.33.0 False True
463 lightningcss-linux-x64-musl 1.32.0 1.33.0 False True
464 lightningcss-win32-arm64-msvc 1.32.0 1.33.0 False True
465 lightningcss-win32-x64-msvc 1.32.0 1.33.0 False True
466 lodash 4.18.1 4.18.1 False False
467 long 5.3.2 5.3.2 False False
468 lru-cache 5.1.1 11.5.3 False True
469 lucide-react 0.576.0 1.49.0 True True
470 magic-string 0.30.21;1.4.2 1.4.2 False True
471 math-intrinsics 1.1.0 1.1.0 False False
472 media-typer 1.1.1 2.0.0 False True
473 merge-descriptors 2.0.0 2.0.0 False False
474 mime-db 1.54.0 1.54.0 False False
475 mime-types 3.0.2 3.0.2 False False
476 minimist 1.2.8 1.2.8 False False
477 ms 2.1.3 2.1.3 False False
478 nanoid 3.3.19 6.0.1 False True
479 negotiator 1.1.0 1.1.0 False False
480 next-themes 0.4.6 0.4.6 True False
481 node-domexception 1.0.0 2.0.2 False True
482 node-fetch 3.3.2 3.3.2 False False
483 node-releases 2.0.57 2.0.57 False False
484 nth-check 2.1.1 3.0.1 False True
485 nunjucks 3.2.4 3.2.4 True False
486 object-assign 4.1.1 4.1.1 False False
487 object-inspect 1.13.4 1.13.4 False False
488 obug 2.2.1 3.0.0 False True
489 on-exit-leak-free 2.1.2 2.1.2 False False
490 on-finished 2.4.1 2.4.1 False False
491 once 1.4.0 1.4.0 False False
492 openai 6.40.0 7.25.0 False True
493 openapi3-ts 4.6.1 4.6.1 False False
494 openworkflow 0.10.1 0.10.1 True False
495 p-map 7.0.8 7.0.8 False False
496 p-retry 4.6.2 8.0.1 False True
497 parse5 7.3.0 8.0.1 False True
498 parse5-htmlparser2-tree-adapter 7.1.0 8.0.1 False True
499 parse5-parser-stream 7.1.2 8.0.0 False True
500 parseurl 1.3.3 1.3.3 False False
501 partial-json 0.1.7 0.1.7 False False
502 path-expression-matcher 1.6.2 1.6.2 False False
503 path-key 3.1.1 4.0.0 False True
504 path-to-regexp 8.4.2 8.4.2 False False
505 pathe 1.1.2;2.0.3 2.0.3 False True
506 pg 8.23.0 8.23.1 True True
507 pg-cloudflare 1.4.0 1.4.1 False True
508 pg-connection-string 2.14.0 2.14.1 False True
509 pg-int8 1.0.1 1.0.1 False False
510 pg-pool 3.14.0 3.14.0 False False
511 pg-protocol 1.16.0 1.16.1 False True
512 pg-types 2.2.0 4.1.0 False True
513 pgpass 1.0.5 1.0.6 False True
514 picocolors 1.1.1 1.1.1 False False
515 picomatch 4.0.7 4.0.7 False False
516 pino 10.3.1 10.3.1 True False
517 pino-abstract-transport 3.0.0 3.0.0 False False
518 pino-pretty 13.1.3 13.1.3 True False
519 pino-std-serializers 7.1.0 7.1.0 False False
520 pkce-challenge 5.0.1 6.0.0 False True
521 pkg-types 2.3.3 2.3.3 False False
522 playwright-core 1.63.0 1.63.0 True False
523 postcss 8.5.28 8.5.28 False False
524 postgres 3.4.9 3.4.9 True False
525 postgres-array 2.0.0 3.0.4 False True
526 postgres-bytea 1.0.1 3.0.0 False True
527 postgres-date 1.0.7 2.1.0 False True
528 postgres-interval 1.2.0 4.1.0 False True
529 prettier 3.9.9 3.9.9 False False
530 process-warning 5.1.0 5.1.0 False False
531 protobufjs 7.6.6 8.8.0 False True
532 proxy-addr 2.0.8 2.0.8 False False
533 proxy-agent-negotiate 1.1.0 1.1.0 False False
534 pump 3.0.4 3.0.4 False False
535 pvtsutils 1.3.6 1.3.6 False False
536 pvutils 1.2.0 1.2.0 False False
537 qr-code-styling 1.9.2 1.9.2 True False
538 qrcode-generator 1.5.2 2.0.4 False True
539 qs 6.16.0 6.16.0 False False
540 quick-format-unescaped 4.0.4 4.0.4 False False
541 radix-ui 1.6.7 1.6.7 True False
542 range-parser 1.3.0 1.3.0 False False
543 raw-body 3.0.2 4.0.0 False True
544 react 19.3.0 19.3.0 True False
545 react-day-picker 10.0.1 10.0.2 True True
546 react-dom 19.3.0 19.3.0 True False
547 react-is 19.3.0 19.3.0 False False
548 react-redux 9.3.0 9.3.0 False False
549 react-refresh 0.14.2 0.19.0 False True
550 react-remove-scroll 2.7.2 2.7.2 False False
551 react-remove-scroll-bar 2.3.8 2.3.8 False False
552 react-router 7.12.0 8.4.0 True True
553 react-style-singleton 2.2.3 2.2.3 False False
554 readdirp 4.1.2 5.1.1 False True
555 real-require 0.2.0;1.0.0 1.0.0 False True
556 recharts 3.10.1 3.10.1 True False
557 redux 5.0.1 5.0.1 False False
558 redux-thunk 3.1.0 3.1.0 False False
559 reflect-metadata 0.2.2 0.2.2 False False
560 require-from-string 2.0.2 2.0.2 False False
561 reselect 5.2.0 5.3.0 False True
562 retry 0.13.1 0.13.1 False False
563 rollup 4.63.5 4.63.5 False False
564 router 2.2.0 2.2.0 False False
565 safe-buffer 5.2.1 5.2.1 False False
566 safe-stable-stringify 2.5.0 2.5.0 False False
567 safer-buffer 2.1.2 2.1.2 False False
568 scheduler 0.28.0 0.28.0 False False
569 secure-json-parse 4.1.0 4.1.0 False False
570 semver 6.3.1;7.8.5 7.8.5 False True
571 send 1.2.1 1.2.1 False False
572 serve-static 2.2.1 2.2.1 False False
573 set-cookie-parser 2.7.2 3.1.2 False True
574 setprototypeof 1.2.0 1.2.0 False False
575 shebang-command 2.0.0 2.0.0 False False
576 shebang-regex 3.0.0 4.0.0 False True
577 side-channel 1.1.1 1.1.1 False False
578 side-channel-list 1.0.1 1.0.1 False False
579 side-channel-map 1.0.1 1.0.1 False False
580 side-channel-weakmap 1.0.2 1.0.2 False False
581 sonic-boom 4.2.1 5.0.1 False True
582 sonner 2.0.8 2.0.8 True False
583 source-map-js 1.2.1 1.2.2 False True
584 split2 4.2.0 4.2.0 False False
585 standardwebhooks 1.1.1 1.1.1 False False
586 statuses 2.0.2 2.0.2 False False
587 std-env 4.2.0 4.3.0 False True
588 strip-json-comments 5.0.3 5.0.3 False False
589 strnum 2.4.2 2.4.2 False False
590 tailwind-merge 3.7.0 3.7.0 True False
591 tailwindcss 4.3.3 4.3.3 True False
592 tapable 2.3.3 2.3.3 False False
593 thread-stream 4.2.0 4.2.0 False False
594 tiny-invariant 1.3.3 1.3.3 False False
595 tinybench 6.2.0 6.2.0 False False
596 tinyexec 1.3.1 1.3.1 False False
597 tinyglobby 0.2.17 0.2.17 False False
598 toidentifier 1.0.1 1.0.1 False False
599 ts-algebra 2.0.0 2.0.0 False False
600 tsconfck 3.1.6 3.1.6 False False
601 tslib 1.14.1;2.8.1 2.8.1 False True
602 tsyringe 4.10.0 4.10.0 False False
603 tw-animate-css 1.4.0 1.4.0 True False
604 type-is 2.1.0 3.0.0 False True
605 typebox 1.3.27 1.3.34 True True
606 typescript 7.0.2 7.0.2 True False
607 undici 7.30.0 8.11.2 False True
608 undici-types 7.24.6 8.11.2 False True
609 unpipe 1.0.0 1.0.0 False False
610 update-browserslist-db 1.3.3 1.3.3 False False
611 use-callback-ref 1.3.3 1.3.3 False False
612 use-sidecar 1.1.3 1.1.3 False False
613 use-sync-external-store 1.7.0 1.7.0 False False
614 valibot 1.5.0 1.5.0 False False
615 vary 1.1.2 1.1.2 False False
616 victory-vendor 37.3.6 37.3.6 False False
617 vite 7.3.6 8.3.1 True True
618 vite-node 3.2.4 6.0.0 False True
619 vite-tsconfig-paths 5.1.4 6.1.1 True True
620 vitest 5.0.2 5.0.3 True True
621 web-streams-polyfill 3.3.3 4.3.0 False True
622 whatwg-encoding 3.1.1 3.1.1 False False
623 whatwg-mimetype 4.0.0 5.0.0 False True
624 which 2.0.2 7.0.0 False True
625 why-is-node-running 3.2.2 3.2.2 False False
626 wrappy 1.0.2 1.0.2 False False
627 ws 8.21.3 8.22.0 False True
628 xml-naming 0.3.0 0.3.0 False False
629 xpath 0.0.34 0.0.34 True False
630 xtend 4.0.2 4.0.2 False False
631 yallist 3.1.1 5.0.0 False True
632 yaml 2.9.0;2.9.1 2.9.1 True True
633 zod 4.6.5 4.6.5 True False
634 zod-to-json-schema 3.25.2 3.25.2 False False
@@ -1,63 +0,0 @@
{
"removedContainers": [
"umt-upgrade-app",
"umt-upgrade-browser",
"umt-upgrade-mock",
"umt-upgrade-qb",
"umt-upgrade-pg18"
],
"removedVolumes": [
"umt_umt-pg-data",
"umt-dev_umt-pg-data",
"umt-upgrade-pg18-data",
"umt-upgrade-app-data",
"umt-upgrade-restore-data",
"umt-upgrade-production-restore-data",
"umt-upgrade-browser-profiles",
"umt-upgrade-browser-config"
],
"removedImageTags": [
"uhd-media-tool:upgrade-20261001",
"uhd-media-tool:pre-upgrade-20261001",
"umt-browser-desktop:1.17.0",
"uhd-media-tool:v0.2.4",
"uhd-media-tool:v0.2.3",
"camofox-browser:1.17.0",
"uhd-media-tool:v0.2.2",
"uhd-media-tool:v0.2.1",
"uhd-media-tool:v0.2.0",
"uhd-media-tool:v0.2.0-rc",
"uhd-media-tool:v0.1.0",
"lscr.io/linuxserver/qbittorrent:5.1.2",
"caddy:2-alpine",
"alpine/socat:1.8.0.0"
],
"removedBuildImages": [],
"preservedSharedImages": [
"postgres:16-alpine"
],
"removedBackups": [
"/data/backups/20260929T103808Z",
"/data/backups/20261001T072253Z",
".local/upgrade-20261001/rollback-docker-compose.yml",
".local/upgrade-20261001/dev-pg16.dump",
".local/upgrade-20261001/rollback-images.json",
".local/upgrade-20261001/umt_umt-camofox.tar.gz",
".local/upgrade-20261001/umt_umt-qb-config.tar.gz",
".local/upgrade-20261001/production-app.env",
".local/upgrade-20261001/rollback-development.sh",
".local/upgrade-20261001/umt_umt-data.tar.gz",
".local/upgrade-20261001/container-inventory-private.json",
".local/upgrade-20261001/rollback-docker-compose.dev.yml",
".local/upgrade-20261001/rollback-production.sh",
".local/upgrade-20261001/current-container-inventory-private.json",
".local/upgrade-20261001/umt_umt-caddy.tar.gz",
".local/upgrade-20261001/4af29d25d939f585d86fb8382ec6f00e8d3b0fe14725e611ce9ae3c0875c0ecf.tar.gz",
".local/upgrade-20261001/original-env.deploy",
".local/upgrade-20261001/original-docker-compose.override.yml"
],
"retainedBackups": [
"/data/backups/20261001T072536Z",
".local/backups/dev-pg18-20261001.dump"
]
}
@@ -1,91 +0,0 @@
{
"workspace": {
"@biomejs/biome": "npm:@biomejs/[email protected]",
"@earendil-works/pi-agent-core": "npm:@earendil-works/[email protected]",
"@earendil-works/pi-ai": "npm:@earendil-works/[email protected]",
"@hono/swagger-ui": "npm:@hono/[email protected]",
"@hono/zod-openapi": "npm:@hono/[email protected]",
"@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/[email protected]",
"@simplewebauthn/server": "npm:@simplewebauthn/[email protected]",
"@types/node": "npm:@types/[email protected]",
"@types/nunjucks": "npm:@types/[email protected]",
"@types/pg": "npm:@types/[email protected]",
"@xmldom/xmldom": "npm:@xmldom/[email protected]",
"cheerio": "npm:[email protected]",
"fast-xml-parser": "npm:[email protected]",
"hash-wasm": "npm:[email protected]",
"hono": "npm:[email protected]",
"jpeg-js": "npm:[email protected]",
"kysely": "npm:[email protected]",
"nunjucks": "npm:[email protected]",
"openworkflow": "npm:[email protected]",
"pg": "npm:[email protected]",
"pino": "npm:[email protected]",
"pino-pretty": "npm:[email protected]",
"playwright-core": "npm:[email protected]",
"postgres": "npm:[email protected]",
"typebox": "npm:[email protected]",
"typescript": "npm:[email protected]",
"vitest": "npm:[email protected]",
"xpath": "npm:[email protected]",
"yaml": "npm:[email protected]",
"zod": "npm:[email protected]"
},
"web": {
"dependencies": {
"@fontsource-variable/inter-tight": "^5.3.0",
"@fontsource-variable/space-grotesk": "^5.3.0",
"@react-router/node": "8.4.0",
"@simplewebauthn/browser": "^14.0.0",
"@tanstack/react-query": "^5.104.0",
"@umt/contracts": "workspace:*",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.4.0",
"isbot": "^5.2.2",
"lucide-react": "^1.49.0",
"next-themes": "^0.4.6",
"qr-code-styling": "^1.9.2",
"radix-ui": "^1.6.7",
"react": "^19.3.0",
"react-day-picker": "^10.0.2",
"react-dom": "^19.3.0",
"react-router": "8.4.0",
"recharts": "^3.10.1",
"sonner": "^2.0.8",
"tailwind-merge": "^3.7.0",
"zod": "^4.6.5"
},
"devDependencies": {
"@react-router/dev": "8.4.0",
"@tailwindcss/vite": "^4.3.3",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"tailwindcss": "^4.3.3",
"tw-animate-css": "^1.4.0",
"vite": "^8.3.1",
"vite-tsconfig-paths": "^6.1.1"
}
},
"browser": {
"dependencies": {
"better-sqlite3": "13.0.3",
"camoufox-js": "0.12.0",
"express": "5.2.1",
"playwright-core": "1.60.0",
"prom-client": "15.1.3",
"swagger-jsdoc": "6.3.0"
},
"optionalDependencies": {
"@modelcontextprotocol/sdk": "1.31.0"
}
},
"sha256": {
"deno.json": "1a136fefa2afecb8084dd41bbbfd415994ca4c073402c00e604452aca2bcd4a0",
"deno.lock": "c18843d7db6039a22c97634b7e112b7dee2ae5acfac08a61cebd11c3f6d23e4c",
"apps/web/package.json": "fe3ab24d9f77beee36e7872a261bd96261161cba84aa08dcf65e4a0136cf8e58",
"docker/camofox/package.json": "911a051f515b68abc3301fa11caaa3b446b4af8895c76e4f0a1f8373541464b5",
"docker/camofox/package-lock.json": "040b7a1c91b4536149b67a7291be70c089171752c639e8231b01b4ef4c695b46"
},
"policy": "Latest verified direct stable dependencies, preserving upstream transitive version constraints; Node type definitions target runtime24, browser Playwright1.60 is required by official Camoufox protocol."
}
@@ -1,173 +0,0 @@
{
"applicationVersion": "0.3.8",
"hostUpgrade": false,
"ci": {
"exitCode": 0,
"passed": 3264,
"skipped": 4
},
"workspaceAudit": {
"vulnerabilities": 0
},
"webSmoke": {
"passed": 83,
"skipped": 1,
"reason": "no subscription detail data"
},
"piSessionCompatibility": {
"oldJsonlOpened": 35,
"passed": 35
},
"qbittorrentRealApi": {
"target": "5.2.4",
"passed": 3,
"skipped": 1,
"reason": "no API key configured"
},
"browserInstalledAudit": {
"dependencyCount": 174,
"vulnerabilities": 0
},
"backupRestoreE2E": {
"server": "18.6",
"client": "18.6",
"mappingCountsMatch": true,
"checksumsVerified": true
},
"developmentDatabaseMigration": {
"source": "16.14",
"target": "18.6",
"tables": 88,
"allCountsAndContentFingerprintsMatch": true,
"newBackupRestore": "PG18 dump restored on isolated PG18; all 88 table fingerprints match"
},
"productionDatabaseMigration": {
"source": "16.14",
"target": "18.6",
"tables": 89,
"allCountsAndContentFingerprintsMatch": true,
"finalStoppedWriteBackup": "20261001T072253Z",
"deployedVersion": "0.3.8",
"mode": "original readonly=false restored; deployment accepted",
"oldPG16Volumes": "removed after acceptance with user authorization"
},
"oldCredentialsCompatibility": {
"clonedDatabaseSecrets": "original key preserved",
"downloaderConnection": "v5.1.2 existing deployment authenticated successfully"
},
"browserDesktopIsolated": {
"sourceProfiles": "independent copy of existing production volume",
"version": "1.18.0",
"node": "24.21.0",
"camoufox": "152.0.4-beta.30",
"playwright": "1.60.0",
"beforeAfterRestart": "passed",
"checks": [
"authorization",
"fixed identity preserved",
"new context and page",
"evaluate",
"snapshot",
"PNG screenshot",
"selector click",
"trusted mouse click",
"profile isolation",
"persisted cookies after graceful restart"
],
"mcpInitialize": "passed SDK1.31.0"
},
"browserServiceRealVerification": {
"version": "1.18.0",
"phases": {
"setup": {
"passed": 2,
"failed": 0,
"items": [
"provider:env 登记 + 探测",
"auth:无 Bearer 被拒"
]
},
"a": {
"passed": 16,
"failed": 0,
"items": [
"public:导航/快照/截图",
"sessions:关闭公开页会话",
"login:未登录识别为登录页",
"screenshot:登录页",
"takeover:接管期间自动化被拒",
"takeover:坐标点击/焦点输入/回车",
"redact:密码框值不出现在快照",
"same-tab:交还后 Agent 读到人工操作结果(同一 tabId)",
"login:登录后 Cookie 生效(Agent 直接打开受保护列表页)",
"screenshot:列表页",
"snapshot:列表页",
"extract:列表",
"extract:详情",
"download:捕获 + sha256 一致 + 匹配本地资源",
"download:已消费的下载不重复返回",
"interact:后退/前进/刷新/滚动"
]
},
"b": {
"passed": 2,
"failed": 0,
"items": [
"restart(SIGTERM):自动重连 + 旧标签页 lost + 旧引用不重放",
"restart(SIGTERM):登录状态保持"
]
},
"idle": {
"passed": 2,
"failed": 0,
"items": [
"idle(SESSION_TIMEOUT_MS):自动重连 + 旧标签页 lost + 旧引用不重放",
"idle(SESSION_TIMEOUT_MS):登录状态保持"
]
},
"c": {
"passed": 1,
"failed": 0,
"items": [
"agent-login:凭证注入登录(账号 2)"
]
},
"d": {
"passed": 2,
"failed": 0,
"items": [
"restart(SIGKILL) 账号 2:自动重连 + 旧标签页 lost + 旧引用不重放",
"restart(SIGKILL) 账号 2:未落盘登录丢失被明确识别"
]
}
},
"totalPassed": 25,
"verifiedCapabilities": 10,
"regressionTests": {
"passed": 100,
"files": 14
},
"sigkill": "new login since last save is lost and correctly detected; not claimed lossless"
},
"productionAcceptance": {
"httpAndHttpsHealth": "0.3.8 DB ok",
"mappingCountsMatch": true,
"originalMasterKeyAndAllNonDatabaseMountsPreserved": true,
"oldEncryptedDownloaderCredentials": "successful authentication to v5.2.4",
"apiWorkerAndAgentRoles": "all alive as uid10001",
"appErrorLevelMessages": 0,
"latestPG18Backup": "20261001T072536Z",
"latestBackupRestore": "passed on isolated PG18, checksums and mappings verified",
"restoredTables": 89,
"restoredAgentSessionFiles": 35,
"allSessionFilesByteIdentical": true,
"releaseWebSmoke": {
"passed": 83,
"skipped": 1
},
"cleanup": "completed; see upgrade-cleanup.json",
"originalReadonlyStateRestored": true,
"readonlyValue": false
},
"status": "completed"
}
@@ -1,80 +0,0 @@
╭ React Router: Open redirect via backslash in <Link> and useNavigate (CVE-2025-68470 bypass)
│ Severity: moderate
│ Package: react-router
│ Vulnerable: >=6.0.0 <7.18.0
╰ Info: https://github.com/advisories/GHSA-wrjc-x8rr-h8h6
╭ React Router: Arbitrary Constructor Injection via deserializeErrors() in React Router SSR Hydration
│ Severity: moderate
│ Package: react-router
│ Vulnerable: >=6.4.0 <7.18.0
╰ Info: https://github.com/advisories/GHSA-337j-9hxr-rhxg
╭ React Router vulnerable to Denial of Service via reflected user input in single-fetch
│ Severity: high
│ Package: react-router
│ Vulnerable: >=7.0.0 <7.14.0
╰ Info: https://github.com/advisories/GHSA-rxv8-25v2-qmq8
╭ React Router's same-origin redirect with path starting // causes open redirect via protocol-relative URL reinterpretation
│ Severity: moderate
│ Package: react-router
│ Vulnerable: >=7.0.0 <7.14.1
╰ Info: https://github.com/advisories/GHSA-2j2x-hqr9-3h42
╭ React Router vulnerable to DoS via unbounded path expansion in __manifest endpoint
│ Severity: high
│ Package: react-router
│ Vulnerable: >=7.0.0 <7.15.0
╰ Info: https://github.com/advisories/GHSA-8x6r-g9mw-2r78
╭ React Router: Unauthenticated Denial of Service via Inefficient Route Matching
│ Severity: high
│ Package: react-router
│ Vulnerable: >=7.0.0 <7.18.0
╰ Info: https://github.com/advisories/GHSA-chx6-hx7r-mcp5
╭ React Router's vendored turbo-stream v2 allows arbitrary constructor invocation via TYPE_ERROR deserialization leading to Unauth RCE
│ Severity: high
│ Package: react-router
│ Vulnerable: >=7.0.0 <=7.14.1
╰ Info: https://github.com/advisories/GHSA-49rj-9fvp-4h2h
╭ React Router: RSCErrorHandler Missing Protocol Validation (XSS)
│ Severity: moderate
│ Package: react-router
│ Vulnerable: >=7.11.0 <7.18.0
╰ Info: https://github.com/advisories/GHSA-h8fp-f39c-q6mh
╭ React Router: Potential CSRF via PUT/PATCH/DELETE document requests
│ Severity: low
│ Package: react-router
│ Vulnerable: >=7.12.0 <7.15.1
╰ Info: https://github.com/advisories/GHSA-84g9-w2xq-vcv6
╭ React Router: RSC Mode CSRF Bypass Allows Action Execution Before 400 Response
│ Severity: high
│ Package: react-router
│ Vulnerable: >=7.12.0 <7.18.2
╰ Info: https://github.com/advisories/GHSA-qwww-vcr4-c8h2
╭ React Router has stored XSS via unescaped Location header in prerendered redirect HTML
│ Severity: moderate
│ Package: react-router
│ Vulnerable: >=7.5.1 <7.13.2
╰ Info: https://github.com/advisories/GHSA-f22v-gfqf-p8f3
╭ React Router vulnerable to XSS in unstable RSC redirect handling via javascript: redirect targets
│ Severity: high
│ Package: react-router
│ Vulnerable: >=7.7.0 <7.13.2
╰ Info: https://github.com/advisories/GHSA-8646-j5j9-6r62
╭ React Router: Open redirect leading to XSS
│ Severity: moderate
│ Package: react-router
│ Vulnerable: >=7.9.6 <=7.12.0
╰ Info: https://github.com/advisories/GHSA-jjmj-jmhj-qwj2
Found 13 vulnerabilities
Severity: 1 low, 6 moderate, 6 high, 0 critical
File renamed without changes.