fix(workflow): satisfy architecture gates for issue 6931

This commit is contained in:
jxxghp
2026-10-09 23:58:23 +08:00
parent b2dfde5999
commit 9924e5e7ff
4 changed files with 44 additions and 38 deletions

View File

@@ -1,5 +1,5 @@
from dataclasses import fields
from typing import Optional
from typing import Any, Optional
from pydantic import Field
@@ -82,10 +82,11 @@ class AddDownloadAction(BaseAction):
logger.warning(f"{t.torrent_info.title} 未识别到媒体信息,无法下载")
continue
download_context = self._to_domain_context(t)
if params.only_lack:
exists_info = DownloadChain().media_exists(download_context.media_info)
download_media_info = download_context.media_info
if params.only_lack and isinstance(download_media_info, DownloadMediaInfo):
exists_info = DownloadChain().media_exists(download_media_info)
if exists_info:
if download_context.media_info.type == MediaType.MOVIE:
if download_media_info.type == MediaType.MOVIE:
# 电影
logger.warning(f"{t.torrent_info.title} 媒体库中已存在,跳过")
continue
@@ -136,49 +137,49 @@ class AddDownloadAction(BaseAction):
if context.media_info is None or context.torrent_info is None:
raise ValueError("工作流下载上下文缺少媒体或种子信息")
media_info = context.media_info
if isinstance(media_info, (DownloadMediaInfo, DownloadMusicInfo)):
domain_media_info = media_info
context_data: dict[str, Any] = context.model_dump()
media_data = context_data.get("media_info")
if not isinstance(media_data, dict):
raise ValueError("工作流下载上下文缺少有效媒体信息")
domain_media_info: DownloadMediaInfo | DownloadMusicInfo
if media_data.get("type") == MediaType.MUSIC.value:
domain_media_info = DownloadMusicInfo.from_dict(media_data)
else:
media_data = media_info.model_dump()
if media_data.get("type") == MediaType.MUSIC.value:
domain_media_info = DownloadMusicInfo.from_dict(media_data)
else:
domain_media_info = DownloadMediaInfo()
domain_media_info.from_dict(media_data)
domain_media_info = DownloadMediaInfo()
domain_media_info.from_dict(media_data)
torrent_info = context.torrent_info
if isinstance(torrent_info, DownloadTorrentInfo):
domain_torrent_info = torrent_info
else:
domain_torrent_info = DownloadTorrentInfo()
domain_torrent_info.from_dict(torrent_info.model_dump())
torrent_data = context_data.get("torrent_info")
if not isinstance(torrent_data, dict):
raise ValueError("工作流下载上下文缺少有效种子信息")
domain_torrent_info = DownloadTorrentInfo()
domain_torrent_info.from_dict(torrent_data)
meta_info = context.meta_info
if isinstance(meta_info, MetaBase):
domain_meta_info = meta_info
elif meta_info is None:
domain_meta_info = None
else:
meta_data = meta_info.model_dump()
domain_meta_info: MetaBase | None = None
meta_data = context_data.get("meta_info")
if isinstance(meta_data, dict):
meta_type = meta_data.get("type")
if meta_type == MediaType.MUSIC.value:
domain_meta_info = MetaMusic.from_dict(meta_data)
else:
domain_meta_info = MetaInfo(
title=meta_data.get("org_string") or meta_data.get("title") or torrent_info.title or "",
title = meta_data.get("org_string") or meta_data.get("title") or torrent_data.get("title")
video_meta_info = MetaInfo(
title=title if isinstance(title, str) else "",
subtitle=meta_data.get("subtitle"),
mtype=MediaType(meta_type) if meta_type else None,
)
for item in fields(domain_meta_info):
for item in fields(video_meta_info):
if item.name not in meta_data:
continue
value = meta_data[item.name]
if item.name == "type" and value:
value = MediaType(value)
setattr(domain_meta_info, item.name, value)
if meta_data.get("name"):
domain_meta_info.name = meta_data["name"]
if item.name == "title" and value is None:
continue
setattr(video_meta_info, item.name, value)
name = meta_data.get("name")
if isinstance(name, str) and name:
video_meta_info.name = name
domain_meta_info = video_meta_info
return DownloadContext(
meta_info=domain_meta_info,

View File

@@ -755,7 +755,7 @@ flowchart LR
SDK 导出(若公开)、`docs/rules/05-architecture.md` 与上述架构测试。
- 延迟导入不被接受为隐藏循环依赖的手段。
### 10.1 2026-09-11 当前收口状态与后续边界
### 10.1 2026-10-09 当前收口状态与后续边界
当前宿主架构基线(排除 `app/plugins/**`)如下;数字来自
`tests/fixtures/architecture/`,更新基线前必须先审查语义变化:
@@ -763,7 +763,7 @@ flowchart LR
| 指标 | 当前值 |
|---|---:|
| Python 模块 | 1114 |
| 内部导入边 | 9,416 |
| 内部导入边 | 9,421 |
| 非平凡 SCC | 1(精确 containment 的 TMDB 移植包环) |
| Application / Chain 具体 Adapter 直连 | 0 / 0 |
| Direct egress | 49(债务已清零,49 条精确 containment) |
@@ -773,7 +773,7 @@ flowchart LR
| Model/Oper 自动事务与自建 Session | 0 |
| 组合根外 `SystemConfigOper()` | 0 |
整理失败反馈由 `app.application.transfer.feedback` 集中投影;已整理下载的站点字幕复用 `app.chain.transfer` 自动整理队列;网络连通性检测按 `app.application.nettest` 归档,并覆盖 HTTP 与 WebSocket 探测。未启用智能助手的消息音乐交互由 `app.chain.music_interaction` 包中的解析展示与订阅协调模块处理;音乐标签准入与批次共识由 `app.chain.transfer.music` 负责,预览证据由 `app.application.transfer.projection` 只读投影;宿主依赖边现为 9,416,音乐来源链与识别缓存复用领域身份核验,音乐交互包未新增 SCC,插件一次性任务 owner `app.scheduler.oncejob` 带来 1 个模块和 9 条依赖边。Agent 持久回执新增 Application 端口及 DB Model/Oper/Adapter 四个冷导入模块。缓存组合根只在启动时选用 Redis 缓存才导入 Redis 适配器,依赖 langchain_core 的对话记忆模型归属 `app.agent.memory`,文件缓存且未启用智能体的实例不再加载 redis 与 langchain_core。Telegram 渠道改用模块内 `app.modules.telegram.botapi` 精简 Bot API 客户端,经 `RequestUtils` 出站,宿主不再导入 pyTelegramBotAPI,启用 Telegram 的实例也不会因该 SDK 加载 redis;该依赖暂时保留给插件使用。Web Push 协议(aes128gcm 加密、VAPID 签名与经 `RequestUtils` 投递)由网络适配器 `app.adapters.network.webpush` 直接基于 http-ece 与 py-vapid 实现,消息端点与 `WebPushModule` 共用,宿主不再导入 pywebpush 及其连带的 aiohttp;该依赖同样暂时保留给插件使用。飞书渠道改用模块内 `app.modules.feishu.openapi` OpenAPI 客户端(经 `RequestUtils` 出站)与网络适配器 `app.adapters.network.feishu` 事件长连接(websocket-client 同步收发 pbbp2 帧),宿主不再导入 lark-oapi 及其上万个生成模块,长连接也不再依赖 SDK 的模块级事件循环;插件经 `app.sdk.feishu` 复用同一长连接,lark-oapi 暂时保留给尚未迁移的插件使用。API 文档路由由 `app.adapters.web.docs` 按 `API_DOCS_ENABLE` 逐请求开放,默认关闭,未开放时不生成文档。当前 `app.startup.lifecycle` 为 582、`app.factory` 为 595、`app.main` 为 597。音乐调用观察 `app.application.music.observation` 新增一个轻量模块,记录来源结果及请求预算;AcoustID及路径识别复用此上下文传递有界指纹候选,保留旧单ID模块合同。站点图片域名快照新增 `app.application.security.image`,各启动入口增加一个模块;性能基线只同步模块数量,原有耗时预算、历史采样和生命周期资源约束保持有效。
整理失败反馈由 `app.application.transfer.feedback` 集中投影;已整理下载的站点字幕复用 `app.chain.transfer` 自动整理队列;网络连通性检测按 `app.application.nettest` 归档,并覆盖 HTTP 与 WebSocket 探测。未启用智能助手的消息音乐交互由 `app.chain.music_interaction` 包中的解析展示与订阅协调模块处理;音乐标签准入与批次共识由 `app.chain.transfer.music` 负责,预览证据由 `app.application.transfer.projection` 只读投影;宿主依赖边现为 9,421;工作流添加下载动作由 `app.workflow.actions.add_download` 转换工作流 Schema 为下载链领域上下文,新增 5 条模块导入边。音乐来源链与识别缓存复用领域身份核验,音乐交互包未新增 SCC,插件一次性任务 owner `app.scheduler.oncejob` 带来 1 个模块和 9 条依赖边。Agent 持久回执新增 Application 端口及 DB Model/Oper/Adapter 四个冷导入模块。缓存组合根只在启动时选用 Redis 缓存才导入 Redis 适配器,依赖 langchain_core 的对话记忆模型归属 `app.agent.memory`,文件缓存且未启用智能体的实例不再加载 redis 与 langchain_core。Telegram 渠道改用模块内 `app.modules.telegram.botapi` 精简 Bot API 客户端,经 `RequestUtils` 出站,宿主不再导入 pyTelegramBotAPI,启用 Telegram 的实例也不会因该 SDK 加载 redis;该依赖暂时保留给插件使用。Web Push 协议(aes128gcm 加密、VAPID 签名与经 `RequestUtils` 投递)由网络适配器 `app.adapters.network.webpush` 直接基于 http-ece 与 py-vapid 实现,消息端点与 `WebPushModule` 共用,宿主不再导入 pywebpush 及其连带的 aiohttp;该依赖同样暂时保留给插件使用。飞书渠道改用模块内 `app.modules.feishu.openapi` OpenAPI 客户端(经 `RequestUtils` 出站)与网络适配器 `app.adapters.network.feishu` 事件长连接(websocket-client 同步收发 pbbp2 帧),宿主不再导入 lark-oapi 及其上万个生成模块,长连接也不再依赖 SDK 的模块级事件循环;插件经 `app.sdk.feishu` 复用同一长连接,lark-oapi 暂时保留给尚未迁移的插件使用。API 文档路由由 `app.adapters.web.docs` 按 `API_DOCS_ENABLE` 逐请求开放,默认关闭,未开放时不生成文档。当前 `app.startup.lifecycle` 为 582、`app.factory` 为 595、`app.main` 为 597。音乐调用观察 `app.application.music.observation` 新增一个轻量模块,记录来源结果及请求预算;AcoustID及路径识别复用此上下文传递有界指纹候选,保留旧单ID模块合同。站点图片域名快照新增 `app.application.security.image`,各启动入口增加一个模块;性能基线只同步模块数量,原有耗时预算、历史采样和生命周期资源约束保持有效。
架构专项验证分为两个 CI 投影:`Check event semantic policy` 先运行依赖、Adapter、出口和 Event
语义门禁,`Check host architecture snapshot` 再执行快照测试及一次

View File

@@ -98,7 +98,7 @@ ARCH-201 至 ARCH-204 均达到实现、验证、提交、推送和远端门禁
| 指标 | 当前值 | 解释 |
|---|---:|---|
| 宿主 Python 模块 / 内部依赖边 | 1114 / 9,416 | `dependency-baseline.json` 当前快照;系统设置合同按 `app.application.settings` 包组织,系统设置 API 端点独立按 `app.api.endpoints.settings` 归档;消息音乐交互由 `app.chain.music_interaction` 包中的音乐解析/展示与订阅协调模块处理;已整理下载的站点字幕经 `app.chain.transfer` 自动整理队列完成命名与归档;网络连通性检测按 `app.application.nettest` 归档;LLM 独占连接关闭复用宿主线程池;歌词字形转换复用 `app.foundation.text`;订阅创建使用 `app.domain.context.MediaInfo` 和运行时配置端口,将非 TMDB 影视身份投影为 TMDB 身份 |
| 宿主 Python 模块 / 内部依赖边 | 1114 / 9,421 | `dependency-baseline.json` 当前快照;系统设置合同按 `app.application.settings` 包组织,系统设置 API 端点独立按 `app.api.endpoints.settings` 归档;消息音乐交互由 `app.chain.music_interaction` 包中的音乐解析/展示与订阅协调模块处理;已整理下载的站点字幕经 `app.chain.transfer` 自动整理队列完成命名与归档;网络连通性检测按 `app.application.nettest` 归档;工作流添加下载动作由 `app.workflow.actions.add_download` 转换工作流 Schema 为下载链领域上下文,新增 5 条合法模块导入边;LLM 独占连接关闭复用宿主线程池;歌词字形转换复用 `app.foundation.text`;订阅创建使用 `app.domain.context.MediaInfo` 和运行时配置端口,将非 TMDB 影视身份投影为 TMDB 身份 |
| 非平凡 SCC | 1 | 仅保留精确 containment 的 29 模块 TMDB 移植包环 |
| 跨层 DB 边界债务 | 0 | Application、Chain、API、Agent、Runtime、Workflow 到 DB 的受控债务均为零 |
| Model/Oper 事务债务 | 0 | 自建 Session、自动事务装饰器、直接 commit/rollback 等基线均为零 |

View File

@@ -941,8 +941,8 @@
"runtime_only": true
}
},
"edge_count": 9416,
"edge_sha256": "b1ea9dffbfa52f3cf110294c10113b24f592aca38a97d4025e3fa99f9bee7005",
"edge_count": 9421,
"edge_sha256": "605aad14dd551c25ca9d9bb87680f4ca05c68d00b613ce552abb19f0449c8669",
"edges": [
"app -> app.foundation",
"app -> app.foundation.environment",
@@ -10205,11 +10205,16 @@
"app.workflow.actions.add_download -> app.chain.download",
"app.workflow.actions.add_download -> app.chain.media",
"app.workflow.actions.add_download -> app.domain",
"app.workflow.actions.add_download -> app.domain.context",
"app.workflow.actions.add_download -> app.domain.meta",
"app.workflow.actions.add_download -> app.domain.meta.metabase",
"app.workflow.actions.add_download -> app.domain.meta.metamusic",
"app.workflow.actions.add_download -> app.domain.metainfo",
"app.workflow.actions.add_download -> app.runtime",
"app.workflow.actions.add_download -> app.runtime.log",
"app.workflow.actions.add_download -> app.runtime.stop",
"app.workflow.actions.add_download -> app.schemas",
"app.workflow.actions.add_download -> app.schemas.context",
"app.workflow.actions.add_download -> app.schemas.types",
"app.workflow.actions.add_download -> app.schemas.workflow",
"app.workflow.actions.add_download -> app.workflow",