From 8555d0da3e2b3cf6592b2f51dbcd00168c270866 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Mon, 24 Aug 2026 04:04:35 +0800 Subject: [PATCH] refactor: use explicit system config getter --- app/agent/llm/provider.py | 8 +++---- app/agent/mcp.py | 6 ++--- app/agent/tools/impl/_filter_rule_utils.py | 14 +++++------ app/agent/tools/impl/_plugin_tool_utils.py | 8 +++---- .../tools/impl/query_custom_identifiers.py | 4 ++-- app/agent/tools/impl/query_downloaders.py | 4 ++-- app/agent/tools/impl/query_system_settings.py | 4 ++-- app/agent/tools/impl/search_torrents.py | 4 ++-- .../tools/impl/update_custom_identifiers.py | 4 ++-- .../tools/impl/update_system_settings.py | 4 ++-- app/chain/subscribe.py | 10 +------- app/modules/indexer/spider/haidan.py | 4 ++-- app/modules/indexer/spider/hddolby.py | 4 ++-- app/modules/indexer/spider/mtorrent.py | 4 ++-- app/modules/indexer/spider/rousi.py | 4 ++-- app/modules/ugreen/ugreen.py | 6 ++--- app/workflow/actions/__init__.py | 4 ++-- .../backend-architecture-next-stage.md | 13 ++++++++++- docs/rules/05-architecture.md | 13 ++++++----- tests/test_agent_plugin_tools.py | 4 ++-- tests/test_agent_resource_flow_permissions.py | 6 +++-- tests/test_agent_system_settings_tools.py | 14 +++++------ tests/test_architecture_dependencies.py | 23 ++++++++++++++++++- tests/test_haidan_spider.py | 4 +++- tests/test_hddolby_spider.py | 4 +++- tests/test_mtorrent_spider.py | 4 +++- tests/test_rousi_spider.py | 4 +++- tests/test_subscribe_chain.py | 2 +- 28 files changed, 111 insertions(+), 76 deletions(-) diff --git a/app/agent/llm/provider.py b/app/agent/llm/provider.py index f947a8333..b60d3640a 100644 --- a/app/agent/llm/provider.py +++ b/app/agent/llm/provider.py @@ -23,7 +23,7 @@ import jwt from app.runtime.settings import RuntimeSettingsCompat settings = RuntimeSettingsCompat() -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.runtime.log import logger from app.schemas.types import LlmProviderAction, SystemConfigKey from app.foundation.singleton import Singleton @@ -1510,7 +1510,7 @@ class LLMProviderManager(metaclass=Singleton): @staticmethod def _read_agent_config() -> dict[str, Any]: """读取 AI Agent 配置信息。""" - config = SystemConfigOper().get(SystemConfigKey.AIAgentConfig) + config = get_configured_system_config().get(SystemConfigKey.AIAgentConfig) if isinstance(config, dict): return config return {} @@ -1520,10 +1520,10 @@ class LLMProviderManager(metaclass=Singleton): """ 使用异步持久化写回 provider 鉴权配置。 - `SystemConfigOper().get()` 读取的是内存缓存,这里保留同步调用; + `get_configured_system_config().get()` 读取的是内存缓存,这里保留同步调用; 但写入需要落库,因此统一走 `async_set()`。 """ - await SystemConfigOper().async_set( + await get_configured_system_config().async_set( SystemConfigKey.AIAgentConfig, copy.deepcopy(value) or None, ) diff --git a/app/agent/mcp.py b/app/agent/mcp.py index 6a71337a9..d04f4861e 100644 --- a/app/agent/mcp.py +++ b/app/agent/mcp.py @@ -12,7 +12,7 @@ from dataclasses import dataclass from typing import Any, Optional from urllib.parse import urljoin -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.runtime.log import logger from app.schemas.agent import ( AgentMcpServerConfig, @@ -456,7 +456,7 @@ class AgentMcpManager: def get_servers(self) -> list[AgentMcpServerConfig]: """读取已保存的外部 MCP 服务器配置。""" - raw_servers = SystemConfigOper().get(SystemConfigKey.AIAgentMcpServers) or [] + raw_servers = get_configured_system_config().get(SystemConfigKey.AIAgentMcpServers) or [] if not isinstance(raw_servers, list): return [] servers: list[AgentMcpServerConfig] = [] @@ -470,7 +470,7 @@ class AgentMcpManager: async def save_servers(self, servers: list[AgentMcpServerConfig]) -> bool: """保存外部 MCP 服务器配置。""" normalized_servers = [self.normalize_server(server).model_dump() for server in servers] - return await SystemConfigOper().async_set( + return await get_configured_system_config().async_set( SystemConfigKey.AIAgentMcpServers, normalized_servers or None, ) diff --git a/app/agent/tools/impl/_filter_rule_utils.py b/app/agent/tools/impl/_filter_rule_utils.py index d5cc8b60d..6ded2fbca 100644 --- a/app/agent/tools/impl/_filter_rule_utils.py +++ b/app/agent/tools/impl/_filter_rule_utils.py @@ -6,7 +6,7 @@ from typing import Any, Dict, Iterable, Optional from app.runtime.events import eventmanager from app.application.agentdata import SubscribePort as SubscribeOper -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.application.rules import RuleHelper from app.application.rules import RuleParser from app.application.rules import BUILTIN_RULE_SET @@ -252,13 +252,13 @@ async def collect_rule_group_usages( """收集规则组在全局配置和订阅上的引用情况。""" target_names = set(group_names or []) search_groups = set( - SystemConfigOper().get(SystemConfigKey.SearchFilterRuleGroups) or [] + get_configured_system_config().get(SystemConfigKey.SearchFilterRuleGroups) or [] ) subscribe_groups = set( - SystemConfigOper().get(SystemConfigKey.SubscribeFilterRuleGroups) or [] + get_configured_system_config().get(SystemConfigKey.SubscribeFilterRuleGroups) or [] ) best_version_groups = set( - SystemConfigOper().get(SystemConfigKey.BestVersionFilterRuleGroups) or [] + get_configured_system_config().get(SystemConfigKey.BestVersionFilterRuleGroups) or [] ) usage_map = { @@ -428,7 +428,7 @@ async def save_system_config( ] normalized_value = normalized_value or None - success = await SystemConfigOper().async_set(key, normalized_value) + success = await get_configured_system_config().async_set(key, normalized_value) if success: await eventmanager.async_send_event( etype=EventType.ConfigChanged, @@ -475,7 +475,7 @@ async def rename_rule_group_references(old_name: str, new_name: str) -> dict: SystemConfigKey.SubscribeFilterRuleGroups, SystemConfigKey.BestVersionFilterRuleGroups, ): - original = SystemConfigOper().get(config_key) or [] + original = get_configured_system_config().get(config_key) or [] updated = replace_group_name_in_list(original, old_name, new_name) if updated != original: await save_system_config(config_key, updated) @@ -513,7 +513,7 @@ async def remove_rule_group_references(group_name: str) -> dict: SystemConfigKey.SubscribeFilterRuleGroups, SystemConfigKey.BestVersionFilterRuleGroups, ): - original = SystemConfigOper().get(config_key) or [] + original = get_configured_system_config().get(config_key) or [] updated = [value for value in original if value != group_name] if updated != original: await save_system_config(config_key, updated) diff --git a/app/agent/tools/impl/_plugin_tool_utils.py b/app/agent/tools/impl/_plugin_tool_utils.py index 1647064ec..3a356e2a8 100644 --- a/app/agent/tools/impl/_plugin_tool_utils.py +++ b/app/agent/tools/impl/_plugin_tool_utils.py @@ -11,7 +11,7 @@ from app.runtime.settings import RuntimeSettingsCompat settings = RuntimeSettingsCompat() from app.application.plugin.runtime import get_plugin_manager from app.application.plugin.install import PluginInstallCommand -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.adapters.external.server import MoviePilotServerHelper from app.adapters.external.market import PluginHelper from app.adapters.system.plugin.package import PluginPackageManager @@ -327,7 +327,7 @@ async def install_plugin_runtime( async def save_installed_plugins(plugin_ids: list[str]) -> object: """保存智能体安装用例确认后的插件列表。""" - return await SystemConfigOper().async_set( + return await get_configured_system_config().async_set( SystemConfigKey.UserInstalledPlugins, plugin_ids, ) @@ -377,7 +377,7 @@ async def install_plugin_runtime( return result result = await PluginInstallCommand( - installed_plugins_reader=lambda: SystemConfigOper().get( + installed_plugins_reader=lambda: get_configured_system_config().get( SystemConfigKey.UserInstalledPlugins ) or [], installed_plugins_writer=save_installed_plugins, @@ -422,7 +422,7 @@ async def uninstall_plugin_runtime(plugin_id: str) -> dict[str, Any]: instance_ids = "、".join(item.instance_id for item in source_instances) raise ValueError(f"请先卸载该插件的分身:{instance_ids}") - config_oper = SystemConfigOper() + config_oper = get_configured_system_config() install_plugins = config_oper.get(SystemConfigKey.UserInstalledPlugins) or [] if plugin_id in install_plugins: install_plugins = [ diff --git a/app/agent/tools/impl/query_custom_identifiers.py b/app/agent/tools/impl/query_custom_identifiers.py index 078cba0bd..4b97e726f 100644 --- a/app/agent/tools/impl/query_custom_identifiers.py +++ b/app/agent/tools/impl/query_custom_identifiers.py @@ -7,7 +7,7 @@ from pydantic import BaseModel from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.runtime.log import logger from app.schemas.types import SystemConfigKey @@ -39,7 +39,7 @@ class QueryCustomIdentifiersTool(MoviePilotTool): @staticmethod def _load_custom_identifiers(): """从内存配置缓存中读取自定义识别词。""" - return SystemConfigOper().get(SystemConfigKey.CustomIdentifiers) + return get_configured_system_config().get(SystemConfigKey.CustomIdentifiers) async def run(self, **kwargs) -> str: logger.info(f"执行工具: {self.name}") diff --git a/app/agent/tools/impl/query_downloaders.py b/app/agent/tools/impl/query_downloaders.py index 590eefde1..130254f02 100644 --- a/app/agent/tools/impl/query_downloaders.py +++ b/app/agent/tools/impl/query_downloaders.py @@ -7,7 +7,7 @@ from pydantic import BaseModel from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.runtime.log import logger from app.schemas.types import SystemConfigKey @@ -34,7 +34,7 @@ class QueryDownloadersTool(MoviePilotTool): @staticmethod def _load_downloaders_config(): """从内存配置缓存中读取下载器配置。""" - return SystemConfigOper().get(SystemConfigKey.Downloaders) + return get_configured_system_config().get(SystemConfigKey.Downloaders) @staticmethod def _sanitize_downloaders_config(downloaders_config: list) -> list: diff --git a/app/agent/tools/impl/query_system_settings.py b/app/agent/tools/impl/query_system_settings.py index 65f5cccbf..37fb64c5f 100644 --- a/app/agent/tools/impl/query_system_settings.py +++ b/app/agent/tools/impl/query_system_settings.py @@ -17,7 +17,7 @@ from app.agent.tools.impl._system_setting_utils import ( ) from app.application.configuration import ( SystemConfigReader, - get_configured_system_config as SystemConfigOper, + get_configured_system_config, get_runtime_settings, ) from app.runtime.log import logger @@ -103,7 +103,7 @@ class QuerySystemSettingsTool(MoviePilotTool): def _get_system_config(self) -> SystemConfigReader: """返回显式注入端口,旧构造形态则延迟读取组合根服务。""" - return self._system_config or SystemConfigOper() + return self._system_config or get_configured_system_config() async def _run_confirmed(self, **kwargs) -> str: """仅供宿主在消费有效确认后执行一次未脱敏读取。""" diff --git a/app/agent/tools/impl/search_torrents.py b/app/agent/tools/impl/search_torrents.py index c514b7533..156422335 100644 --- a/app/agent/tools/impl/search_torrents.py +++ b/app/agent/tools/impl/search_torrents.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.chain.search import SearchChain -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module from app.runtime.log import logger from app.schemas.types import MediaSource, MediaType, SystemConfigKey @@ -63,7 +63,7 @@ class SearchTorrentsTool(MoviePilotTool): @staticmethod def _load_configured_sites() -> List[int]: """同步读取默认搜索站点列表。""" - return SystemConfigOper().get(SystemConfigKey.IndexerSites) or [] + return get_configured_system_config().get(SystemConfigKey.IndexerSites) or [] async def run(self, media_source: MediaSource, media_id: str, media_type: Optional[str] = None, area: Optional[str] = None, diff --git a/app/agent/tools/impl/update_custom_identifiers.py b/app/agent/tools/impl/update_custom_identifiers.py index f0481611c..d04852daa 100644 --- a/app/agent/tools/impl/update_custom_identifiers.py +++ b/app/agent/tools/impl/update_custom_identifiers.py @@ -8,7 +8,7 @@ from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag from app.domain.metainfo import clear_rust_parse_options_cache -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.runtime.log import logger from app.schemas.types import SystemConfigKey @@ -80,7 +80,7 @@ class UpdateCustomIdentifiersTool(MoviePilotTool): # 过滤空字符串 identifiers = [i for i in identifiers if i is not None] - system_config_oper = SystemConfigOper() + system_config_oper = get_configured_system_config() # 保存 value = identifiers if identifiers else None diff --git a/app/agent/tools/impl/update_system_settings.py b/app/agent/tools/impl/update_system_settings.py index f51c03af4..45f1889ea 100644 --- a/app/agent/tools/impl/update_system_settings.py +++ b/app/agent/tools/impl/update_system_settings.py @@ -19,7 +19,7 @@ from app.agent.tools.impl._system_setting_utils import ( from app.runtime.events import eventmanager from app.application.configuration import ( SystemConfigService, - get_configured_system_config as SystemConfigOper, + get_configured_system_config, get_runtime_settings, ) from app.application.plugin.runtime import plugin_system_config_mutation @@ -109,7 +109,7 @@ class UpdateSystemSettingsTool(MoviePilotTool): def _get_system_config(self) -> SystemConfigService: """返回显式注入服务,旧构造形态则延迟读取组合根服务。""" - return self._system_config or SystemConfigOper() + return self._system_config or get_configured_system_config() def get_tool_message(self, **kwargs) -> Optional[str]: """根据更新参数生成友好的提示消息。""" diff --git a/app/chain/subscribe.py b/app/chain/subscribe.py index f8fc352fa..6012ca28d 100644 --- a/app/chain/subscribe.py +++ b/app/chain/subscribe.py @@ -92,12 +92,6 @@ else: """返回快照字段,兼容整理链的响应转换。""" return dict(self.__dict__) -# 旧测试与第三方扩展可能替换该名字;它现在只是应用配置端口的本地别名, -# 不再指向数据库 Oper。 -SystemConfigOper = get_configured_system_config -_DEFAULT_SYSTEM_CONFIG_PROVIDER = get_configured_system_config - - @dataclass(frozen=True, slots=True) class _SubscribePostCommitContext: """订阅提交后副作用所需的不可变业务快照。""" @@ -142,9 +136,7 @@ class _SubscribeCreateContext: def _system_config(): - """返回配置端口,并兼容旧测试对本地别名的替换。""" - if SystemConfigOper is not _DEFAULT_SYSTEM_CONFIG_PROVIDER: - return SystemConfigOper() + """返回启动组合根登记的配置端口。""" return get_configured_system_config() diff --git a/app/modules/indexer/spider/haidan.py b/app/modules/indexer/spider/haidan.py index bad502493..a585b0cfd 100644 --- a/app/modules/indexer/spider/haidan.py +++ b/app/modules/indexer/spider/haidan.py @@ -4,7 +4,7 @@ from typing import Tuple, List from app.runtime.settings import RuntimeSettingsCompat settings = RuntimeSettingsCompat() -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.runtime.log import logger from app.schemas.types import MediaType from app.adapters.network.http import RequestUtils, AsyncRequestUtils @@ -62,7 +62,7 @@ class HaiDanSpider: return None def __init__(self, indexer: dict): - self.systemconfig = SystemConfigOper() + self.systemconfig = get_configured_system_config() if indexer: self._indexerid = indexer.get('id') self._url = indexer.get('domain') diff --git a/app/modules/indexer/spider/hddolby.py b/app/modules/indexer/spider/hddolby.py index 76367095b..a333dea44 100644 --- a/app/modules/indexer/spider/hddolby.py +++ b/app/modules/indexer/spider/hddolby.py @@ -3,7 +3,7 @@ from typing import Tuple, List, Optional from app.runtime.settings import RuntimeSettingsCompat settings = RuntimeSettingsCompat() -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.runtime.log import logger from app.schemas.types import MediaType from app.adapters.network.http import RequestUtils, AsyncRequestUtils @@ -69,7 +69,7 @@ class HddolbySpider: return cls._size def __init__(self, indexer: dict): - self.systemconfig = SystemConfigOper() + self.systemconfig = get_configured_system_config() if indexer: self._indexerid = indexer.get('id') self._domain = indexer.get('domain') diff --git a/app/modules/indexer/spider/mtorrent.py b/app/modules/indexer/spider/mtorrent.py index cd5313604..578eedc6b 100644 --- a/app/modules/indexer/spider/mtorrent.py +++ b/app/modules/indexer/spider/mtorrent.py @@ -7,7 +7,7 @@ from urllib.parse import urlparse from app.runtime.settings import RuntimeSettingsCompat settings = RuntimeSettingsCompat() -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.runtime.log import logger from app.schemas.types import MediaType from app.adapters.network.http import RequestUtils, AsyncRequestUtils @@ -67,7 +67,7 @@ class MTorrentSpider: return cls._size def __init__(self, indexer: dict): - self.systemconfig = SystemConfigOper() + self.systemconfig = get_configured_system_config() if indexer: self._indexerid = indexer.get('id') self._url = indexer.get('domain') diff --git a/app/modules/indexer/spider/rousi.py b/app/modules/indexer/spider/rousi.py index 4040f585d..982a247df 100644 --- a/app/modules/indexer/spider/rousi.py +++ b/app/modules/indexer/spider/rousi.py @@ -5,7 +5,7 @@ from typing import List, Optional, Tuple from app.runtime.settings import RuntimeSettingsCompat settings = RuntimeSettingsCompat() -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.runtime.log import logger from app.schemas.types import MediaType from app.adapters.network.http import RequestUtils, AsyncRequestUtils @@ -51,7 +51,7 @@ class RousiSpider: return cls._size def __init__(self, indexer: dict): - self.systemconfig = SystemConfigOper() + self.systemconfig = get_configured_system_config() if indexer: self._indexerid = indexer.get('id') self._url = indexer.get('domain') diff --git a/app/modules/ugreen/ugreen.py b/app/modules/ugreen/ugreen.py index 0492960c7..8926181b9 100644 --- a/app/modules/ugreen/ugreen.py +++ b/app/modules/ugreen/ugreen.py @@ -12,7 +12,7 @@ from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibr from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem from app.schemas.mediaserver import RefreshMediaItem as _SchemaRefreshMediaItem from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.application.mediaserver import MediaServerIdentityHelper, MusicMediaServerHelper from app.runtime.log import logger from app.modules.ugreen.api import Api @@ -116,12 +116,12 @@ class Ugreen: @staticmethod def __load_all_session_cache() -> dict: - sessions = SystemConfigOper().get(SystemConfigKey.UgreenSessionCache) + sessions = get_configured_system_config().get(SystemConfigKey.UgreenSessionCache) return sessions if isinstance(sessions, dict) else {} @staticmethod def __save_all_session_cache(sessions: dict): - SystemConfigOper().set(SystemConfigKey.UgreenSessionCache, sessions) + get_configured_system_config().set(SystemConfigKey.UgreenSessionCache, sessions) def __remove_persisted_session(self): cache_key = self.__session_cache_key() diff --git a/app/workflow/actions/__init__.py b/app/workflow/actions/__init__.py index 92cc94cf4..9cf18a20b 100644 --- a/app/workflow/actions/__init__.py +++ b/app/workflow/actions/__init__.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from typing import Any, ClassVar, Union from app.chain import ChainBase -from app.application.configuration import get_configured_system_config as SystemConfigOper +from app.application.configuration import get_configured_system_config from app.schemas.workflow import ActionContext from app.schemas.workflow import ActionParams from app.schemas.workflow import ActionResult @@ -37,7 +37,7 @@ class BaseAction(ABC): self._action_id = action_id self._done_flag = False self._message = "" - self.systemconfigoper = SystemConfigOper() + self.systemconfigoper = get_configured_system_config() @classmethod def get_contract(cls) -> dict: diff --git a/docs/refactor/backend-architecture-next-stage.md b/docs/refactor/backend-architecture-next-stage.md index 02b9e07d2..ce8f06ccc 100644 --- a/docs/refactor/backend-architecture-next-stage.md +++ b/docs/refactor/backend-architecture-next-stage.md @@ -6,7 +6,7 @@ > 审计范围:宿主后端;排除 `app/plugins/**` 运行时插件副本 > 规范优先级:`AGENTS.md` 与 `docs/rules/` 高于本文 > 相关文档:`docs/architecture-overview.md`、`docs/refactor/backend-architecture-governance.md`、`docs/refactor/backend-module-refactor-compatibility.md` -> 实施进度:阶段 0~6 的宿主架构能力已完成收口;API/Application 公共复杂度基线已清零,启动组合根的 SystemConfigOper 构造点已由 14 降至 1;API 进程内后台任务已完成首批统一登记,插件仓适配和 Outbox 外围扩展仍按风险切片推进。Model/Base 查询与写装饰器、legacy 隐式会话外壳均已清零,插件 SDK 也不再导出宿主 Model。2026-08-23 的长期整改阶段 0 已恢复宿主、启动性能、官方插件和 SDK 契约门禁的可信基线;阶段 1a 已补齐 TaskRegistry owner 零债务门禁和诚实的关停超时语义;阶段 1b1 已收口整理 worker、pending 回放、失败通知、进程内 AI 重试、插件监控与事件投递的生命周期所有权;2026-08-24 的阶段 2 已将 212 个已观察宿主模块方法的 legacy aggregation 清零,并补齐可执行 fanout 与下载器文件 DTO 边界;阶段 3 已将消息交互和远程命令的订阅删除统一到 Application/UoW/outbox,宿主不再调用裸线程统计入口;阶段 4 已统一七种消息渠道的宿主回环与后台执行边界;阶段 5 已补齐事件窗口聚合任务的生命周期所有权;阶段 6 已统一插件文件操作的取消完成语义;阶段 7 已统一插件协程补偿的终态等待;阶段 8 已统一宿主同步函数的异步线程池入口;阶段 9 已统一工作流运行时的宿主获取路径;阶段 10 已统一模块、插件与调度运行时的显式 getter 调用。 +> 实施进度:阶段 0~6 的宿主架构能力已完成收口;API/Application 公共复杂度基线已清零,启动组合根的 SystemConfigOper 构造点已由 14 降至 1;API 进程内后台任务已完成首批统一登记,插件仓适配和 Outbox 外围扩展仍按风险切片推进。Model/Base 查询与写装饰器、legacy 隐式会话外壳均已清零,插件 SDK 也不再导出宿主 Model。2026-08-23 的长期整改阶段 0 已恢复宿主、启动性能、官方插件和 SDK 契约门禁的可信基线;阶段 1a 已补齐 TaskRegistry owner 零债务门禁和诚实的关停超时语义;阶段 1b1 已收口整理 worker、pending 回放、失败通知、进程内 AI 重试、插件监控与事件投递的生命周期所有权;2026-08-24 的阶段 2 已将 212 个已观察宿主模块方法的 legacy aggregation 清零,并补齐可执行 fanout 与下载器文件 DTO 边界;阶段 3 已将消息交互和远程命令的订阅删除统一到 Application/UoW/outbox,宿主不再调用裸线程统计入口;阶段 4 已统一七种消息渠道的宿主回环与后台执行边界;阶段 5 已补齐事件窗口聚合任务的生命周期所有权;阶段 6 已统一插件文件操作的取消完成语义;阶段 7 已统一插件协程补偿的终态等待;阶段 8 已统一宿主同步函数的异步线程池入口;阶段 9 已统一工作流运行时的宿主获取路径;阶段 10 已统一模块、插件与调度运行时的显式 getter 调用;阶段 11 已清除系统配置 getter 的 Oper 形别名。 ## 当前复核结论(2026-08-24) @@ -129,6 +129,17 @@ - 兼容边界不变:Application 的 `ModuleManager`、`Scheduler` 类形 Facade 和 concrete 插件管理器类路径 继续保留,旧插件、V1/V2/V3 索引加载及 SDK/Compat 映射无需迁移;本阶段仅统一宿主生产路径。 +### 长期整改阶段 11:系统配置端口命名统一(2026-08-24) + +- Agent、Chain、Module 和 Workflow 的 17 个 canonical 文件原先把 + `get_configured_system_config()` 别名或赋值为 `SystemConfigOper`,使 Application 配置端口在调用处 + 看起来仍像数据库 Oper。宿主生产路径现统一显式调用 getter,测试也改为替换真实组合根接缝。 +- 架构门禁禁止为 `get_configured_system_config` 建立别名,也禁止把它赋给本地 + `SystemConfigOper`;真正的 `app.db.oper.systemconfig.SystemConfigOper` 只留在 DB 实现、startup 装配和 + testing bootstrap,不再形成第二种 canonical 获取方式。 +- 兼容边界不变:DB Oper 类、`app.db.oper` 懒导出、SDK/Compat 旧路径和 V1/V2/V3 插件加载均未改动; + 已注入 `SystemConfigReader/SystemConfigService` 的 Agent 工具构造合同保持原样。 + ### 总体判断 当前架构总体合理,已经从跨层混合的遗留单体收敛为**边界清晰的模块化单体**: diff --git a/docs/rules/05-architecture.md b/docs/rules/05-architecture.md index bad16c4e6..f936ca3a5 100644 --- a/docs/rules/05-architecture.md +++ b/docs/rules/05-architecture.md @@ -107,12 +107,13 @@ API dependencies must narrow that object to a domain runtime (for example, `AgentChatRuntime`) instead of adding a string key to a global service map. Legacy registries may delegate the same object while domains migrate, but they must not construct a second set of service instances. -Canonical host consumers of the process-wide module, plugin and scheduler -runtimes must call `get_module_manager()`, `get_plugin_manager()` and -`get_scheduler()` explicitly. The class-shaped `ModuleManager` and `Scheduler` -application facades, and concrete plugin manager class paths, remain compatibility -boundaries for plugins and startup composition; host code must not import those -facades or alias a getter back to a manager class name. +Canonical host consumers of the process-wide module, plugin, scheduler and +system-configuration runtimes must call `get_module_manager()`, +`get_plugin_manager()`, `get_scheduler()` and `get_configured_system_config()` +explicitly. The class-shaped `ModuleManager` and `Scheduler` application facades, +the concrete plugin manager class paths and DB `SystemConfigOper` remain +compatibility or composition boundaries; host code must not import those facades +or alias a getter back to a manager/Oper class name. API, Scheduler and Chain deployment values are exposed as frozen snapshots from `HostRuntime.configuration`; canonical callers must not add a fresh direct `settings` import when the required field belongs to an existing snapshot. diff --git a/tests/test_agent_plugin_tools.py b/tests/test_agent_plugin_tools.py index 94a3ea85c..2f413d6cc 100644 --- a/tests/test_agent_plugin_tools.py +++ b/tests/test_agent_plugin_tools.py @@ -303,7 +303,7 @@ def test_install_plugin_runtime_reloads_in_threadpool() -> None: with ( patch( - "app.agent.tools.impl._plugin_tool_utils.SystemConfigOper", + "app.agent.tools.impl._plugin_tool_utils.get_configured_system_config", return_value=config_oper, ), patch( @@ -394,7 +394,7 @@ def test_sealed_agent_uninstall_rejects_before_persistence() -> None: return_value=plugin_manager, ), patch( - "app.agent.tools.impl._plugin_tool_utils.SystemConfigOper", + "app.agent.tools.impl._plugin_tool_utils.get_configured_system_config", return_value=config_oper, ) as config_provider, pytest.raises(PluginMutationRejectedError), diff --git a/tests/test_agent_resource_flow_permissions.py b/tests/test_agent_resource_flow_permissions.py index a2928a16b..aace0f9ca 100644 --- a/tests/test_agent_resource_flow_permissions.py +++ b/tests/test_agent_resource_flow_permissions.py @@ -1,5 +1,7 @@ """Agent 资源流程工具权限测试。""" +# pylint: disable=no-name-in-module + import asyncio import json from types import SimpleNamespace @@ -308,7 +310,7 @@ def test_query_downloaders_hides_sensitive_fields_for_non_admin_user(): ] with patch( - "app.agent.tools.impl.query_downloaders.SystemConfigOper" + "app.agent.tools.impl.query_downloaders.get_configured_system_config" ) as system_config_oper: system_config_oper.return_value.get.return_value = downloaders result = asyncio.run(tool.run()) @@ -345,7 +347,7 @@ def test_query_downloaders_keeps_full_fields_for_admin_context(): ] with patch( - "app.agent.tools.impl.query_downloaders.SystemConfigOper" + "app.agent.tools.impl.query_downloaders.get_configured_system_config" ) as system_config_oper: system_config_oper.return_value.get.return_value = downloaders result = asyncio.run(tool.run()) diff --git a/tests/test_agent_system_settings_tools.py b/tests/test_agent_system_settings_tools.py index d94a5bc4f..c038dbf20 100644 --- a/tests/test_agent_system_settings_tools.py +++ b/tests/test_agent_system_settings_tools.py @@ -35,7 +35,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase): tool = QuerySystemSettingsTool(session_id="session-1", user_id="10001") with patch( - "app.agent.tools.impl.query_system_settings.SystemConfigOper" + "app.agent.tools.impl.query_system_settings.get_configured_system_config" ) as system_config_oper: system_config_oper.return_value.get.return_value = [{"name": "qb", "enabled": True}] result = asyncio.run(tool.run(setting_key="Downloaders")) @@ -54,7 +54,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase): tool = QuerySystemSettingsTool(session_id="session-1", user_id="10001") with patch( - "app.agent.tools.impl.query_system_settings.SystemConfigOper" + "app.agent.tools.impl.query_system_settings.get_configured_system_config" ) as system_config_oper: system_config_oper.return_value.get.return_value = [ { @@ -84,7 +84,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase): tool.set_agent_context({"is_admin": True}) with patch( - "app.agent.tools.impl.query_system_settings.SystemConfigOper" + "app.agent.tools.impl.query_system_settings.get_configured_system_config" ) as system_config_oper: system_config_oper.return_value.get.return_value = [ {"name": "site-a", "apikey": "site-api-key"} @@ -156,7 +156,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase): tool = QuerySystemSettingsTool(session_id="session-1", user_id="10001") with patch( - "app.agent.tools.impl.query_system_settings.SystemConfigOper" + "app.agent.tools.impl.query_system_settings.get_configured_system_config" ) as system_config_oper: system_config_oper.return_value.get.return_value = [] result = asyncio.run(tool.run(group="systemconfig")) @@ -231,7 +231,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase): config_oper.async_set = AsyncMock(return_value=True) with patch( - "app.agent.tools.impl.update_system_settings.SystemConfigOper", + "app.agent.tools.impl.update_system_settings.get_configured_system_config", return_value=config_oper, ), patch( "app.agent.tools.impl.update_system_settings.eventmanager.async_send_event", @@ -264,7 +264,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase): config_oper.async_set = AsyncMock(return_value=True) with patch( - "app.agent.tools.impl.update_system_settings.SystemConfigOper", + "app.agent.tools.impl.update_system_settings.get_configured_system_config", return_value=config_oper, ), patch( "app.agent.tools.impl.update_system_settings.eventmanager.async_send_event", @@ -297,7 +297,7 @@ class TestAgentSystemSettingsTools(unittest.TestCase): config_oper.async_set = AsyncMock(return_value=True) with patch( - "app.agent.tools.impl.update_system_settings.SystemConfigOper", + "app.agent.tools.impl.update_system_settings.get_configured_system_config", return_value=config_oper, ), patch( "app.agent.tools.impl.update_system_settings.eventmanager.async_send_event", diff --git a/tests/test_architecture_dependencies.py b/tests/test_architecture_dependencies.py index 2b54ff9ac..13f426b58 100644 --- a/tests/test_architecture_dependencies.py +++ b/tests/test_architecture_dependencies.py @@ -368,6 +368,18 @@ def test_host_code_uses_explicit_runtime_facade_getters(): continue tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path)) for node in ast.walk(tree): + if isinstance(node, ast.Assign) and isinstance(node.value, ast.Name): + target_names = { + target.id for target in node.targets if isinstance(target, ast.Name) + } + if ( + "SystemConfigOper" in target_names + and node.value.id == "get_configured_system_config" + ): + violations.append( + f"{relative.as_posix()}:{node.lineno}:SystemConfigOper" + ) + continue if not isinstance(node, ast.ImportFrom) or not node.module: continue forbidden_names = forbidden_imports.get(node.module, set()) @@ -377,7 +389,16 @@ def test_host_code_uses_explicit_runtime_facade_getters(): and alias.name == "get_plugin_manager" and alias.asname is not None ) - if alias.name in forbidden_names or class_shaped_plugin_getter: + class_shaped_config_getter = ( + node.module == "app.application.configuration" + and alias.name == "get_configured_system_config" + and alias.asname is not None + ) + if ( + alias.name in forbidden_names + or class_shaped_plugin_getter + or class_shaped_config_getter + ): imported_name = alias.asname or alias.name violations.append( f"{relative.as_posix()}:{node.lineno}:{imported_name}" diff --git a/tests/test_haidan_spider.py b/tests/test_haidan_spider.py index dd0367d84..13a56b06e 100644 --- a/tests/test_haidan_spider.py +++ b/tests/test_haidan_spider.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: disable=no-name-in-module + import pytest from app.modules.indexer.spider import haidan as haidan_module @@ -21,7 +23,7 @@ def _build_indexer() -> dict: @pytest.fixture() def haidan_spider(monkeypatch): """构造不依赖真实数据库配置的 HaiDanSpider。""" - monkeypatch.setattr(haidan_module, "SystemConfigOper", lambda: None) + monkeypatch.setattr(haidan_module, "get_configured_system_config", lambda: None) return HaiDanSpider(_build_indexer()) diff --git a/tests/test_hddolby_spider.py b/tests/test_hddolby_spider.py index 5f8fc6b79..35c0d0068 100644 --- a/tests/test_hddolby_spider.py +++ b/tests/test_hddolby_spider.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: disable=no-name-in-module + import pytest from app.modules.indexer.spider import hddolby as hddolby_module @@ -21,7 +23,7 @@ def _build_indexer() -> dict: @pytest.fixture() def hddolby_spider(monkeypatch): """构造不依赖真实数据库配置的 HddolbySpider。""" - monkeypatch.setattr(hddolby_module, "SystemConfigOper", lambda: None) + monkeypatch.setattr(hddolby_module, "get_configured_system_config", lambda: None) return HddolbySpider(_build_indexer()) diff --git a/tests/test_mtorrent_spider.py b/tests/test_mtorrent_spider.py index 9a8ccbc40..8e5bac6be 100644 --- a/tests/test_mtorrent_spider.py +++ b/tests/test_mtorrent_spider.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: disable=no-name-in-module + import pytest from app.modules.indexer.spider import mtorrent as mtorrent_module @@ -21,7 +23,7 @@ def _build_indexer() -> dict: @pytest.fixture() def mteam_spider(monkeypatch): """构造不依赖真实数据库配置的 MTorrentSpider。""" - monkeypatch.setattr(mtorrent_module, "SystemConfigOper", lambda: None) + monkeypatch.setattr(mtorrent_module, "get_configured_system_config", lambda: None) return MTorrentSpider(_build_indexer()) diff --git a/tests/test_rousi_spider.py b/tests/test_rousi_spider.py index 61c3a29e6..3d749b933 100644 --- a/tests/test_rousi_spider.py +++ b/tests/test_rousi_spider.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: disable=no-name-in-module + import pytest from app.modules.indexer.spider import rousi as rousi_module @@ -21,7 +23,7 @@ def _build_indexer() -> dict: @pytest.fixture() def rousi_spider(monkeypatch): """构造不依赖真实数据库配置的 RousiSpider。""" - monkeypatch.setattr(rousi_module, "SystemConfigOper", lambda: None) + monkeypatch.setattr(rousi_module, "get_configured_system_config", lambda: None) return RousiSpider(_build_indexer()) diff --git a/tests/test_subscribe_chain.py b/tests/test_subscribe_chain.py index 1037fee38..31bcf27bf 100644 --- a/tests/test_subscribe_chain.py +++ b/tests/test_subscribe_chain.py @@ -1056,7 +1056,7 @@ class SubscribeChainTest(TestCase): with patch.object(SUBSCRIBE_CHAIN_MODULE, "SubscribeOper", _SubscribeOper), patch.object( SUBSCRIBE_CHAIN_MODULE, - "SystemConfigOper", + "get_configured_system_config", _SystemConfigOper, ), patch.object( SUBSCRIBE_CHAIN_MODULE,