ForcePilot/backend/package/yuxi/channels/infrastructure/factory.py
Kris 8eead29de0 refactor: 批量清理冗余空行,优化部分枚举使用方式
1.  移除所有适配器文件中多余的空导入行
2.  调整ValidationError继承,移除不必要的ValueError继承
3.  修正多处ChannelType使用方式,从.value改为直接使用枚举实例
4.  优化飞书插件部分硬编码渠道类型为枚举实例
5.  更新wechat_ilink插件清单与适配器配置
6.  新增飞书目录适配器缓存清理支持判断与iLink生命周期适配器凭据轮换支持判断
7.  优化配置处理器历史查询逻辑,区分键不存在与无历史记录场景
2026-07-04 00:14:56 +08:00

1002 lines
46 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""HostBootstrap / HostShutdown 工厂。
本模块提供 ``create_host_bootstrap`` 与 ``create_host_shutdown`` 两个工厂函数,
集中装配渠道网关编排层所需的全量依赖,避免在 lifespan 中散落构造逻辑。
工厂按依赖拓扑顺序构造组件,并将共享依赖注册到 ``DependencyInjectionContainer``
供 ``create_host_shutdown`` 复用INF-006消除模块级可变状态
"""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any, Protocol
from arq import ArqRedis
from redis.asyncio import Redis
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.channels.adapters import DrivenAdapters, create_driven_adapters
from yuxi.channels.adapters.agent_run_execution_adapter import (
AgentRunExecutionAdapter,
)
from yuxi.channels.adapters.arq_queue_adapter import ARQQueueAdapter
from yuxi.channels.adapters.channel_persistence_adapter import (
ChannelPersistenceAdapter,
)
from yuxi.channels.adapters.content_review_repository_adapter import (
ContentReviewRepositoryAdapter,
)
from yuxi.channels.adapters.default_content_moderation_adapter import (
DefaultContentModerationAdapter,
)
from yuxi.channels.adapters.masking_adapter import MaskingAdapter
from yuxi.channels.adapters.opentelemetry_tracer_adapter import (
OpenTelemetryTracerAdapter,
)
from yuxi.channels.adapters.redis_cache_adapter import RedisCacheAdapter
from yuxi.channels.adapters.redis_config_adapter import RedisConfigAdapter
from yuxi.channels.application.circuit_breaker.channel_circuit_breaker import (
ChannelCircuitBreaker,
)
from yuxi.channels.application.extension.config_source_registry import (
ConfigSourceRegistry,
)
from yuxi.channels.application.extension.event_bus import EventBus
from yuxi.channels.application.extension.event_subscription_registry import (
EventSubscriptionRegistry,
)
from yuxi.channels.application.extension.handlers.channel_degraded_handler import (
ChannelDegradedHandler,
)
from yuxi.channels.application.extension.handlers.channel_recovered_handler import (
ChannelRecoveredHandler,
)
from yuxi.channels.application.extension.handlers.outbox_state_audit_handler import (
OutboxStateAuditHandler,
)
from yuxi.channels.application.extension.handlers.pairing_approved_handler import (
PairingApprovedHandler,
)
from yuxi.channels.application.extension.handlers.plugin_lifecycle_audit_handler import (
PluginLifecycleAuditHandler,
)
from yuxi.channels.application.extension.handlers.route_match_cache_handler import (
RouteMatchCacheHandler,
)
from yuxi.channels.application.extension.handlers.whitelist_config_handler import (
WhitelistConfigHandler,
)
from yuxi.channels.application.extension.stage_slot_registry import StageSlotRegistry
from yuxi.channels.application.health.channel_probe import ChannelProbe
from yuxi.channels.application.health.diagnostics_exporter import DiagnosticsExporter
from yuxi.channels.application.health.health_aggregator import HealthAggregator
from yuxi.channels.application.lifecycle.plugin_capability_checker import (
PluginCapabilityChecker,
)
from yuxi.channels.application.lifecycle.plugin_dependency_resolver import (
PluginDependencyResolver,
)
from yuxi.channels.application.lifecycle.plugin_lifecycle_manager import (
PluginLifecycleManager,
)
from yuxi.channels.application.lifecycle.plugin_loader import PluginLoader
from yuxi.channels.application.outbox.audit_log_retention_scanner import (
AuditLogRetentionScanner,
)
from yuxi.channels.application.outbox.outbox_recovery_scanner import (
OutboxRecoveryScanner,
)
from yuxi.channels.application.outbox.pairing_expiration_scanner import (
PairingExpirationScanner,
)
from yuxi.channels.application.pipeline.stage_slot_injector import StageSlotInjector
from yuxi.channels.application.transport import TransportManager
from yuxi.channels.contract.dtos.channel import ChannelType
from yuxi.channels.contract.dtos.inbound import InboundMessageCmd
from yuxi.channels.contract.dtos.outbox import OutboxConfig
from yuxi.channels.contract.dtos.route import MatchTier
from yuxi.channels.contract.plugin.adapters.outbound_adapter import OutboundAdapter
from yuxi.channels.contract.plugin.extension_point import EventSubscription
from yuxi.channels.contract.ports.driven.agent_run_execution_port import (
AgentRunExecutionPort,
)
from yuxi.channels.contract.ports.driven.cache_port import CachePort
from yuxi.channels.contract.ports.driven.config_port import ConfigPort
from yuxi.channels.contract.ports.driven.content_review_repository_port import (
ContentReviewRepositoryPort,
)
from yuxi.channels.contract.ports.driven.event_publisher_port import (
EventPublisherPort,
)
from yuxi.channels.contract.ports.driven.logger_port import LoggerPort
from yuxi.channels.contract.ports.driven.persistence_port import PersistencePort
from yuxi.channels.contract.ports.driven.queue_port import QueuePort
from yuxi.channels.core.registry.capability_registry import CapabilityRegistry
from yuxi.channels.core.registry.command_registry import CommandRegistryTable
from yuxi.channels.core.registry.identity_resolver_registry import (
IdentityResolverEntry,
IdentityResolverRegistry,
)
from yuxi.channels.core.registry.plugin_registry import PluginRegistry
from yuxi.channels.core.registry.route_match_registry import RouteMatchRegistry
from yuxi.channels.core.registry.whitelist_registry import WhitelistRegistry
from yuxi.channels.core.service.capability_verifier import CapabilityVerifier
from yuxi.channels.core.service.degradation_manager import DegradationManager
from yuxi.channels.core.service.explicit_mapping_resolver import ExplicitMappingResolver
from yuxi.channels.core.service.phone_number_resolver import PhoneNumberResolver
from yuxi.channels.infrastructure.channel_use_cases import create_channel_use_cases
from yuxi.channels.infrastructure.dependency_injection import (
DependencyInjectionContainer,
)
from yuxi.channels.infrastructure.host_bootstrap import HostBootstrap
from yuxi.channels.infrastructure.host_shutdown import HostShutdown
from yuxi.config.app import config as app_config
from yuxi.services.run_queue_service import get_arq_pool, get_redis_client
from yuxi.utils.guard import ContentGuard
__all__ = ["create_host_bootstrap", "create_host_shutdown", "load_channel_plugins"]
#: 默认匹配层级FR-04按优先级降序排列session_key 最高800
#: default 最低100。``registerTier`` 内部按升序排序,``RouteResolver``
#: 遍历时反转得到降序。
_DEFAULT_MATCH_TIERS: tuple[MatchTier, ...] = (
MatchTier(name="session_key", priority=800, match_method="exact"),
MatchTier(name="identity_id", priority=700, match_method="exact"),
MatchTier(name="peer_id", priority=600, match_method="exact"),
MatchTier(name="chat_type", priority=500, match_method="exact"),
MatchTier(name="channel_session", priority=400, match_method="exact"),
MatchTier(name="channel_type", priority=300, match_method="exact"),
MatchTier(name="account", priority=200, match_method="exact"),
MatchTier(name="default", priority=100, match_method="exact"),
)
def _register_default_match_tiers(registry: RouteMatchRegistry) -> None:
"""向路由匹配注册表注册 8 个默认层级与 5 个内置 matcherFR-04
层级优先级session_key(800) → identity_id(700) → peer_id(600) →
chat_type(500) → channel_session(400) → channel_type(300) →
account(200) → default(100)。
内置 matcher 注册 5 个session_key/identity_id/peer_id/account/default
其余 3 个层级chat_type/channel_session/channel_type暂未实现匹配逻辑
``RouteResolver`` 遍历时会记录告警并跳过AC-25。插件可通过
``registerMatcher`` 为这些层级注入自定义 matcher。
"""
for tier in _DEFAULT_MATCH_TIERS:
registry.registerTier(tier)
# 注册内置 matcherFR-04通过闭包捕获 registry 查找方法
registry.registerMatcher("session_key", lambda ctx: registry.findBySession(ctx.session_key))
registry.registerMatcher(
"identity_id",
lambda ctx: registry.findByIdentity(ctx.unified_identity_id) if ctx.unified_identity_id else None,
)
registry.registerMatcher("peer_id", lambda ctx: registry.findExplicit(ctx.account_id, ctx.peer_id))
registry.registerMatcher("account", lambda ctx: registry.findDefault(ctx.account_id))
registry.registerMatcher("default", lambda ctx: registry.findDefault(ctx.account_id))
class _EnsureSchemaPort(Protocol):
"""渠道 schema 初始化端口(工厂模块级 Protocol
``PostgresManager`` 满足此 Protocol提供 ``ensure_channel_schema`` 与
``AsyncSession`` 方法)。与 ``host_bootstrap._EnsureSchemaPort`` 一致,
此处独立定义避免跨模块导入私有名,同时让工厂签名显式契约化(消除 ``Any``
弱类型,符合项目硬约束)。
"""
async def ensure_channel_schema(self) -> None:
"""确保渠道网关 schema 就绪。"""
...
def AsyncSession(self) -> AsyncSession: # noqa: N802 - 与 PostgresManager API 一致
"""创建异步数据库会话。"""
...
@dataclass
class _PluginLoadingCore:
"""插件加载核心组件聚合(供两工厂入口共享装配)。
``create_host_bootstrap`` 与 ``load_channel_plugins`` 共享同一装配拓扑:
6 个注册中心、共享 Redis/ARQ 客户端、EventBus、PluginLifecycleManager 及
其子依赖。聚合为单一 dataclass 消除 ~130 行重复构造代码,保证两入口
装配逻辑一致AGENTS.md复用场景允许抽象禁止复制粘贴
"""
plugin_registry: PluginRegistry
stage_slot_registry: StageSlotRegistry
event_subscription_registry: EventSubscriptionRegistry
config_source_registry: ConfigSourceRegistry
route_match_registry: RouteMatchRegistry
whitelist_registry: WhitelistRegistry
capability_registry: CapabilityRegistry
event_bus: EventBus
cache_port: CachePort
degradation_manager: DegradationManager
plugin_lifecycle_manager: PluginLifecycleManager
plugin_dependency_resolver: PluginDependencyResolver
plugin_loader: PluginLoader
outbox_config: OutboxConfig
redis_client: Redis
arq_pool: ArqRedis
execution_port_impl: AgentRunExecutionAdapter
async def _assemble_plugin_loading_core(
logger: LoggerPort,
ensure_schema: _EnsureSchemaPort,
plugin_dir: str | None = None,
) -> _PluginLoadingCore:
"""装配插件加载核心组件(``create_host_bootstrap`` 与 ``load_channel_plugins`` 共享)。
按依赖拓扑顺序构造 6 个注册中心、共享 Redis/ARQ 客户端、EventBus、
PluginLifecycleManager 及其子依赖。``driven_adapters_factory`` 作为请求级
工厂闭包捕获 ``ensure_schema.AsyncSession()``,供 PluginHostImpl 获取端口。
Args:
logger: 日志端口,供所有 framework 层组件注入。
ensure_schema: 数据库会话工厂来源(``PostgresManager`` 满足此 Protocol
plugin_dir: 插件目录路径P1 PLG-INSTALL/UNINSTALL 文件级操作注入),
传入则注入 ``PluginLoader.plugin_dir``,为 ``None`` 时 PluginLoader
仅支持 ``load``,文件级安装/卸载抛 ``ValidationError``。
Returns:
装配完成的 ``_PluginLoadingCore`` 聚合实例。
"""
# 1. 构造无依赖的注册中心
plugin_registry = PluginRegistry()
stage_slot_registry = StageSlotRegistry()
event_subscription_registry = EventSubscriptionRegistry()
config_source_registry = ConfigSourceRegistry(logger=logger)
route_match_registry = RouteMatchRegistry()
whitelist_registry = WhitelistRegistry()
_register_default_match_tiers(route_match_registry)
# 2. 构造共享 Redis 客户端与 ARQ 连接池构造注入INV-5
# 供 RedisConfigAdapter / RedisCacheAdapter / ARQQueueAdapter 与请求级
# create_driven_adapters 复用。获取方式与 services/run_queue_service.py
# 保持一致(单例 + ping 校验HostShutdown 关停时由适配器 close() 释放。
redis_client = await get_redis_client()
arq_pool = await get_arq_pool()
# 构造 AgentRunExecutionPort 实现,委托 yuxi.services 技术操作ADP-001
execution_port_impl = AgentRunExecutionAdapter()
# 3. 构造 cache_port / degradation_manager / EventBus
# EventBus 依赖 degradation_manager 在 FR-36 降级事件分发时联动
cache_port: CachePort = RedisCacheAdapter(redis_client=redis_client, logger=logger)
degradation_manager = DegradationManager(
cache_port=cache_port,
plugin_registry=plugin_registry,
logger=logger,
)
event_bus = EventBus(logger=logger, degradation_manager=degradation_manager)
# 4. 构造 OutboxConfig
# 组合根从配置源构造注入应用层,消除应用层对 yuxi.config.app 的直接依赖
# §6.1 应用服务层禁止依赖具体技术适配器)。
outbox_config = OutboxConfig(
ttl_seconds=app_config.channel_outbox_ttl_seconds,
retry_backoff_schedule=tuple(app_config.channel_outbox_retry_backoff_schedule),
max_retry=app_config.channel_outbox_max_retry,
)
# 5. 构造 PluginLifecycleManager 的子依赖
plugin_loader = PluginLoader(logger=logger, plugin_dir=plugin_dir)
plugin_dependency_resolver = PluginDependencyResolver(logger=logger)
plugin_capability_checker = PluginCapabilityChecker(logger=logger)
capability_registry = CapabilityRegistry()
# driven_adapters_factory 为请求级工厂,每次调用创建独立 AsyncSession
# 与 DrivenAdapters 实例,供 PluginHostImpl 获取端口。
def driven_adapters_factory() -> DrivenAdapters:
db = ensure_schema.AsyncSession()
return create_driven_adapters(
db,
outbox_config=outbox_config,
redis_client=redis_client,
arq_pool=arq_pool,
execution_port=execution_port_impl,
)
# 6. 构造 PluginLifecycleManager
plugin_lifecycle_manager = PluginLifecycleManager(
loader=plugin_loader,
resolver=plugin_dependency_resolver,
capability_checker=plugin_capability_checker,
plugin_registry=plugin_registry,
stage_slot_registry=stage_slot_registry,
event_subscription_registry=event_subscription_registry,
config_source_registry=config_source_registry,
event_bus=event_bus,
driven_adapters_factory=driven_adapters_factory,
degradation_manager=degradation_manager,
cache_port=cache_port,
logger=logger,
route_match_registry=route_match_registry,
capability_registry=capability_registry,
)
return _PluginLoadingCore(
plugin_registry=plugin_registry,
stage_slot_registry=stage_slot_registry,
event_subscription_registry=event_subscription_registry,
config_source_registry=config_source_registry,
route_match_registry=route_match_registry,
whitelist_registry=whitelist_registry,
capability_registry=capability_registry,
event_bus=event_bus,
cache_port=cache_port,
degradation_manager=degradation_manager,
plugin_lifecycle_manager=plugin_lifecycle_manager,
plugin_dependency_resolver=plugin_dependency_resolver,
plugin_loader=plugin_loader,
outbox_config=outbox_config,
redis_client=redis_client,
arq_pool=arq_pool,
execution_port_impl=execution_port_impl,
)
def _register_builtin_event_subscribers(
event_bus: EventBus,
persistence_port: PersistencePort,
queue_port: QueuePort,
whitelist_registry: WhitelistRegistry,
cache_port: CachePort,
plugin_registry: PluginRegistry,
logger: LoggerPort,
) -> None:
"""注册内置事件订阅者FR-18/22/30/32/33/34/36
订阅映射:
- ``OutboxStateChanged`` → 审计日志FR-22 / FR-34
- 8 类插件生命周期事件 → 审计日志FR-32 / FR-34共用同一
``PluginLifecycleAuditHandler`` 实例handler 内部按事件类型
映射审计操作类型
- ``ConfigChanged`` / ``ConfigRollback`` → 白名单实时刷新FR-18
+ 路由匹配缓存失效FR-30复用同一对 handler保证配置回滚
后下游缓存立即刷新
- ``PairingApproved`` → 入站管道重试FR-33
- ``ChannelDegraded`` / ``ChannelRecovered`` → 审计日志FR-36
形成降级-恢复闭环,满足「降级期间审计日志必须继续记录」要求
"""
# OutboxStateChanged → 审计日志
event_bus.register(
"host",
EventSubscription(
event_type="OutboxStateChanged",
handler=OutboxStateAuditHandler(persistence_port),
),
)
# 8 类插件生命周期事件 → 审计日志
plugin_audit_handler = PluginLifecycleAuditHandler(
persistence_port=persistence_port,
plugin_registry=plugin_registry,
)
for plugin_event_type in (
"PluginDiscovered",
"PluginLoaded",
"PluginStarted",
"PluginPaused",
"PluginResumed",
"PluginStopped",
"PluginUnloaded",
"PluginFailed",
):
event_bus.register(
"host",
EventSubscription(
event_type=plugin_event_type,
handler=plugin_audit_handler,
),
)
# ConfigChanged / ConfigRollback → 白名单刷新 + 路由匹配缓存失效
# handler 复用同一对实例WhitelistConfigHandler 内部按 event_type
# 区分 new_value / rollback_value消除重复构造。
whitelist_handler = WhitelistConfigHandler(whitelist_registry, logger=logger)
route_match_handler = RouteMatchCacheHandler(cache_port)
for config_event in ("ConfigChanged", "ConfigRollback"):
event_bus.register(
"host",
EventSubscription(event_type=config_event, handler=whitelist_handler),
)
event_bus.register(
"host",
EventSubscription(event_type=config_event, handler=route_match_handler),
)
# PairingApproved → 入站管道重试
event_bus.register(
"host",
EventSubscription(
event_type="PairingApproved",
handler=PairingApprovedHandler(queue_port),
),
)
# ChannelDegraded / ChannelRecovered → 审计日志(降级-恢复闭环)
event_bus.register(
"host",
EventSubscription(
event_type="ChannelDegraded",
handler=ChannelDegradedHandler(persistence_port),
),
)
event_bus.register(
"host",
EventSubscription(
event_type="ChannelRecovered",
handler=ChannelRecoveredHandler(persistence_port),
),
)
def _construct_scanners(
persistence_port: PersistencePort,
queue_port: QueuePort,
cache_port: CachePort,
degradation_manager: DegradationManager,
event_publisher: EventBus,
outbox_config: OutboxConfig,
logger: LoggerPort,
) -> tuple[OutboxRecoveryScanner, PairingExpirationScanner, AuditLogRetentionScanner]:
"""构造 3 个后台扫描器FR-22/33/34
- ``OutboxRecoveryScanner``outbox 重试扫描FR-22
- ``PairingExpirationScanner``配对过期自动标记FR-33
- ``AuditLogRetentionScanner``审计日志保留期清理FR-34
保留期从 ``channel_audit_log_retention_days`` 读取(默认 90 天)
"""
outbox_recovery_scanner = OutboxRecoveryScanner(
persistence_port=persistence_port,
queue_port=queue_port,
cache_port=cache_port,
logger=logger,
degradation_manager=degradation_manager,
event_publisher=event_publisher,
outbox_config=outbox_config,
)
pairing_expiration_scanner = PairingExpirationScanner(
persistence_port=persistence_port,
cache_port=cache_port,
logger=logger,
)
audit_log_retention_scanner = AuditLogRetentionScanner(
persistence_port=persistence_port,
cache_port=cache_port,
logger=logger,
retention_days=app_config.channel_audit_log_retention_days,
)
return outbox_recovery_scanner, pairing_expiration_scanner, audit_log_retention_scanner
def _construct_health_dependencies(
plugin_registry: PluginRegistry,
persistence_port: PersistencePort,
cache_port: CachePort,
queue_port: QueuePort,
capability_registry: CapabilityRegistry,
event_publisher: EventBus,
redis_client: Redis,
logger: LoggerPort,
) -> tuple[ConfigPort, ChannelCircuitBreaker, ChannelProbe, OpenTelemetryTracerAdapter, DiagnosticsExporter]:
"""构造健康检查子依赖FR-356-P0-07 装配)。
拆分为独立函数以解耦装配顺序:``TransportManager`` 需要
``channel_circuit_breaker``,而 ``HealthAggregator`` 需要
``TransportManager``(作为 ``transport_health_port``)。本函数先构造
circuit_breaker 等共享依赖,供后续 ``TransportManager`` 与
``HealthAggregator`` 复用,避免循环依赖。
``ChannelProbe`` 持有 ``PluginRegistry`` 引用,运行时动态查找插件注册的
``ProbeableAdapter``,无需静态 adapter_registry。``config_port`` /
``capability_verifier`` / ``channel_circuit_breaker`` 供
``HealthAggregator`` 读取各渠道熔断器状态并填充
``ChannelHealth.circuit_breaker_state``。
``tracer_port`` 供 ``DiagnosticsExporter`` / ``HealthAggregator`` 解析
真实 trace_idINV-10。``OpenTelemetryTracerAdapter`` 无状态(仅持
logger 引用 + 读 ContextVar
Returns:
``(config_port, channel_circuit_breaker, channel_probe, tracer_port,
diagnostics_exporter)`` 五元组,供后续 TransportManager 与
HealthAggregator 构造使用。
"""
config_port: ConfigPort = RedisConfigAdapter(redis_client=redis_client, logger=logger)
capability_verifier = CapabilityVerifier(
cache_port=cache_port,
registry=capability_registry,
logger=logger,
)
channel_circuit_breaker = ChannelCircuitBreaker(
cache_port=cache_port,
capability_verifier=capability_verifier,
event_publisher=event_publisher,
config_port=config_port,
logger=logger,
)
channel_probe = ChannelProbe(
plugin_registry=plugin_registry,
persistence_port=persistence_port,
cache_port=cache_port,
queue_port=queue_port,
logger=logger,
)
tracer_port = OpenTelemetryTracerAdapter(logger)
masking_port = MaskingAdapter()
diagnostics_exporter = DiagnosticsExporter(
persistence_port=persistence_port,
logger=logger,
masking_port=masking_port,
tracer_port=tracer_port,
)
return config_port, channel_circuit_breaker, channel_probe, tracer_port, diagnostics_exporter
def _register_di_singletons(
di_container: DependencyInjectionContainer,
core: _PluginLoadingCore,
*,
persistence_port: PersistencePort,
persistence_db: AsyncSession,
queue_port: QueuePort,
config_port: ConfigPort,
stage_slot_injector: StageSlotInjector,
outbox_recovery_scanner: OutboxRecoveryScanner,
health_aggregator: HealthAggregator,
channel_circuit_breaker: ChannelCircuitBreaker,
transport_manager: TransportManager,
logger: LoggerPort,
) -> None:
"""注册全量 DI 单例到容器INF-006消除模块级可变状态
``HostBootstrap._registerCoreSingletons`` 也会注册部分单例,此处预注册
保证 ``bootstrap()`` 失败后 DI 容器状态完整,``create_channel_use_cases``
不会因漏注册抛 InternalError``registerSingleton`` 幂等,后续覆盖为同一实例)。
注册内容分 4 类:
1. 从 ``core`` 直接注册的注册中心与共享依赖Redis/ARQ/EventBus 等)
2. 命令注册表FR-156 个跨渠道通用命令)与身份解析器注册表
FR-05explicit_mapping priority=200 + phone_number priority=300
3. 应用级端口与编排组件persistence/queue/cache/config + 扫描器 +
HealthAggregator + CircuitBreaker + StageSlotInjector
4. 内容审核域单例CR-01~CR-03
- ``ContentGuard``:封装关键词 + 可选 LLM 二审能力(不 import
全局单例 ``content_guard``§9.5 插件隔离)
- ``DefaultContentModerationAdapter``:未注册插件渠道时的兜底
审核适配器,供 ``create_channel_use_cases`` resolve 并注入
DispatchStage
- ``ContentReviewRepositoryAdapter``:审核历史仓储适配器,复用
``persistence_db`` 共享 AsyncSession与 ChannelPersistenceAdapter
同一会话§10.1 共享会话约定),``tx`` 参数仅作为"是否自主提交"
标志位。同时注册为 ``ContentReviewRepositoryPort`` 端口绑定
INV-3 契约显式化)
注:``persistence_db`` 的生命周期由 ``HostShutdown`` 通过
``persistence_port.aclose()`` → ``self._db.close()`` 统一管理
CloseablePort 契约),不注册为 ``AsyncSession`` 单例(请求级资源,
注册为单例存在并发污染隐患)。
"""
# 1. 注册中心与共享依赖
di_container.registerSingleton(PluginRegistry, core.plugin_registry)
di_container.registerSingleton(CapabilityRegistry, core.capability_registry)
di_container.registerSingleton(RouteMatchRegistry, core.route_match_registry)
di_container.registerSingleton(WhitelistRegistry, core.whitelist_registry)
# 共享 Redis 客户端单例,供 create_channel_use_cases 解析并注入请求级
# RedisConfigAdapter构造注入INV-5
di_container.registerSingleton(Redis, core.redis_client)
# 共享 ARQ 连接池单例,供 create_channel_use_cases 解析并注入请求级
# ARQQueueAdapter构造注入INV-5 / ADP-005
di_container.registerSingleton(ArqRedis, core.arq_pool)
# AgentRunExecutionPort 单例,供 create_channel_use_cases 解析并注入
# 请求级 AgentRunAdapter构造注入ADP-001
di_container.registerSingleton(AgentRunExecutionPort, core.execution_port_impl)
di_container.registerSingleton(EventBus, core.event_bus)
# EventPublisherPort 绑定到 EventBus 实例EventBus 结构化满足
# EventPublisherPort Protocol使应用层通过端口契约依赖。
di_container.registerSingleton(EventPublisherPort, core.event_bus)
di_container.registerSingleton(StageSlotRegistry, core.stage_slot_registry)
di_container.registerSingleton(EventSubscriptionRegistry, core.event_subscription_registry)
di_container.registerSingleton(ConfigSourceRegistry, core.config_source_registry)
di_container.registerSingleton(PluginLifecycleManager, core.plugin_lifecycle_manager)
di_container.registerSingleton(PluginDependencyResolver, core.plugin_dependency_resolver)
# PluginLoader 单例P1 PLG-INSTALL/UNINSTALL 文件操作),供
# create_channel_use_cases resolve 并注入 DispatchStage。
di_container.registerSingleton(PluginLoader, core.plugin_loader)
# 2. 命令注册表FR-15与身份解析器注册表FR-05
# 由 bootstrap 一次性注册,避免每个请求重复构造解析器实例。
# 通用命令注册推迟到插件加载完成后由 HostBootstrap 触发
# (依赖已加载插件的 channel_type 列表)。
command_registry_table = CommandRegistryTable()
di_container.registerSingleton(CommandRegistryTable, command_registry_table)
identity_resolver_registry = IdentityResolverRegistry()
# explicit_mappingpriority=200管理员配置的显式映射置信度 1.0
identity_resolver_registry.registerResolver(
IdentityResolverEntry(
name="explicit_mapping",
priority=200,
resolver=ExplicitMappingResolver(config_port=config_port),
)
)
# phone_numberpriority=300手机号启发式匹配置信度 0.7
identity_resolver_registry.registerResolver(
IdentityResolverEntry(
name="phone_number",
priority=300,
resolver=PhoneNumberResolver(persistence_port=persistence_port),
)
)
di_container.registerSingleton(IdentityResolverRegistry, identity_resolver_registry)
# 3. 应用级端口与编排组件
di_container.registerSingleton(StageSlotInjector, stage_slot_injector)
di_container.registerSingleton(OutboxRecoveryScanner, outbox_recovery_scanner)
# HealthAggregator 预注册HostBootstrap._registerCoreSingletons 也会注册,
# 此处预注册保证 bootstrap 失败后 DI 容器状态完整(与 EventBus 等同理)。
di_container.registerSingleton(HealthAggregator, health_aggregator)
# ChannelCircuitBreaker 注册为单例,供 create_channel_use_cases 解析并注入
# 控制面 DispatchStageFR-35 熔断器状态查询AC-32 运行时状态聚合)。
di_container.registerSingleton(ChannelCircuitBreaker, channel_circuit_breaker)
di_container.registerSingleton(LoggerPort, logger)
di_container.registerSingleton(CachePort, core.cache_port)
di_container.registerSingleton(PersistencePort, persistence_port)
di_container.registerSingleton(QueuePort, queue_port)
di_container.registerSingleton(ConfigPort, config_port)
di_container.registerSingleton(TransportManager, transport_manager)
# 4. 内容审核域单例CR-01~CR-03
content_guard = ContentGuard()
default_moderation_adapter = DefaultContentModerationAdapter(
content_guard=content_guard,
logger=logger,
)
review_repository = ContentReviewRepositoryAdapter(
db=persistence_db,
logger=logger,
)
di_container.registerSingleton(DefaultContentModerationAdapter, default_moderation_adapter)
di_container.registerSingleton(ContentReviewRepositoryAdapter, review_repository)
di_container.registerSingleton(ContentReviewRepositoryPort, review_repository)
async def create_host_bootstrap(
ensure_schema: _EnsureSchemaPort,
logger: LoggerPort,
plugin_dir: str,
di_container: DependencyInjectionContainer,
driving_adapter_registrar: Callable[[], Awaitable[None]] | None = None,
driving_adapter_unregistrar: Callable[[], Awaitable[None]] | None = None,
) -> HostBootstrap:
"""构造完整的 HostBootstrap 实例。
按依赖拓扑顺序装配渠道网关编排层的全量组件:注册中心 → EventBus →
端口适配器 → 插件生命周期管理器 → 管道注入器 → Outbox 扫描器 →
健康聚合器 → DI 容器 → HostBootstrap。构造完成后将共享依赖注册到
``DependencyInjectionContainer``,供 ``create_host_shutdown`` 复用
INF-006消除模块级可变状态
Args:
ensure_schema: 渠道 schema 初始化端口(``PostgresManager`` 满足此
Protocol提供 ``ensure_channel_schema`` 方法)。同时作为数据库
会话工厂来源,通过 ``ensure_schema.AsyncSession()`` 创建应用级
与请求级 ``AsyncSession``。
logger: 日志被驱动端口,供所有 framework 层组件注入。
plugin_dir: 插件目录路径,供 ``PluginLifecycleManager.discover`` 扫描。
di_container: 依赖注入容器由调用方创建并传入。HostBootstrap 的
``_registerCoreSingletons`` 会将 framework 层应用级单例
EventBus、StageSlotRegistry、PluginLifecycleManager、
HealthAggregator 等)注册到该容器,供路由层通过
``app.state.channel_di_container`` 解析。调用方需将同一容器
赋值给 ``app.state.channel_di_container`` 以保证路由层可访问。
driving_adapter_registrar: 驱动适配器注册回调(可选),由调用方
提供,在 HostBootstrap 步骤 7 调用以开始接受流量。
driving_adapter_unregistrar: 驱动适配器注销回调可选INF-013
与 ``driving_adapter_registrar`` 对称,由调用方提供。当
``bootstrap()`` 启动失败回滚且驱动适配器已注册时调用,以注销
已注册的驱动适配器并停止接收新流量。为 ``None`` 时回滚跳过
驱动适配器注销步骤。
Returns:
装配完成的 HostBootstrap 实例,调用方需进一步调用 ``bootstrap()``
执行启动编排。
"""
# 1. 装配插件加载核心组件(与 load_channel_plugins 共享)
core = await _assemble_plugin_loading_core(logger, ensure_schema, plugin_dir=plugin_dir)
# 2. 构造应用级 persistence_port 与 queue_port
# persistence_port 需要 AsyncSession创建应用级会话供 OutboxRecoveryScanner
# 与 DiagnosticsExporter 共用queue_port 无需会话。
persistence_db = ensure_schema.AsyncSession()
persistence_port: PersistencePort = ChannelPersistenceAdapter(
persistence_db, outbox_config=core.outbox_config, logger=logger
)
queue_port: QueuePort = ARQQueueAdapter(arq_pool=core.arq_pool, logger=logger)
# 3. 注册内置事件订阅者FR-18/22/30/32/33/34/36
_register_builtin_event_subscribers(
event_bus=core.event_bus,
persistence_port=persistence_port,
queue_port=queue_port,
whitelist_registry=core.whitelist_registry,
cache_port=core.cache_port,
plugin_registry=core.plugin_registry,
logger=logger,
)
# 4. 构造 StageSlotInjector
stage_slot_injector = StageSlotInjector(
stage_slot_registry=core.stage_slot_registry,
logger=logger,
)
# 5. 构造 3 个后台扫描器FR-22/33/34
outbox_recovery_scanner, pairing_expiration_scanner, audit_log_retention_scanner = _construct_scanners(
persistence_port=persistence_port,
queue_port=queue_port,
cache_port=core.cache_port,
degradation_manager=core.degradation_manager,
event_publisher=core.event_bus,
outbox_config=core.outbox_config,
logger=logger,
)
# 6. 构造健康检查子依赖circuit_breaker 等FR-356-P0-07 装配)
# 拆分为独立步骤以解耦装配顺序TransportManager 需要 circuit_breaker
# HealthAggregator 需要 TransportManager作为 transport_health_port
(
config_port,
channel_circuit_breaker,
channel_probe,
tracer_port,
diagnostics_exporter,
) = _construct_health_dependencies(
plugin_registry=core.plugin_registry,
persistence_port=persistence_port,
cache_port=core.cache_port,
queue_port=queue_port,
capability_registry=core.capability_registry,
event_publisher=core.event_bus,
redis_client=core.redis_client,
logger=logger,
)
# 7. 构造 TransportManager需要 circuit_breaker供 HealthAggregator 作为
# transport_health_port 注入FR-18 传输健康聚合)
async def _deliver_inbound_message(cmd: InboundMessageCmd) -> Any:
db = ensure_schema.AsyncSession()
try:
use_cases = create_channel_use_cases(db, di_container)
return await use_cases.inbound_message.receiveInbound(cmd)
finally:
await db.close()
transport_manager = TransportManager(
plugin_registry=core.plugin_registry,
persistence_port=persistence_port,
config_port=config_port,
event_bus=core.event_bus,
circuit_breaker=channel_circuit_breaker,
logger=logger,
message_deliverer=_deliver_inbound_message,
)
# 8. 构造 HealthAggregator注入 transport_manager 作为 transport_health_port
health_aggregator = HealthAggregator(
plugin_registry=core.plugin_registry,
persistence_port=persistence_port,
cache_port=core.cache_port,
queue_port=queue_port,
circuit_breaker=channel_circuit_breaker,
channel_probe=channel_probe,
diagnostics_exporter=diagnostics_exporter,
logger=logger,
tracer_port=tracer_port,
transport_health_port=transport_manager,
)
# 9. 注册全量 DI 单例INF-006消除模块级可变状态
_register_di_singletons(
di_container=di_container,
core=core,
persistence_port=persistence_port,
persistence_db=persistence_db,
queue_port=queue_port,
config_port=config_port,
stage_slot_injector=stage_slot_injector,
outbox_recovery_scanner=outbox_recovery_scanner,
health_aggregator=health_aggregator,
channel_circuit_breaker=channel_circuit_breaker,
transport_manager=transport_manager,
logger=logger,
)
# 10. 构造 HostBootstrapINF-009/010传入 config_port 与 app_driven_adapters
# 供 ``_loadConfig`` 加载渠道配置、``_initDrivenAdapters`` 对应用级
# 被驱动适配器执行连通性检查driving_adapter_unregistrar 用于启动
# 失败回滚时注销驱动适配器)
app_driven_adapters = (
core.cache_port,
persistence_port,
queue_port,
config_port,
)
return HostBootstrap(
ensure_schema=ensure_schema,
di_container=di_container,
plugin_dir=plugin_dir,
event_bus=core.event_bus,
stage_slot_registry=core.stage_slot_registry,
event_subscription_registry=core.event_subscription_registry,
config_source_registry=core.config_source_registry,
plugin_lifecycle_manager=core.plugin_lifecycle_manager,
plugin_dependency_resolver=core.plugin_dependency_resolver,
stage_slot_injector=stage_slot_injector,
outbox_recovery_scanner=outbox_recovery_scanner,
pairing_expiration_scanner=pairing_expiration_scanner,
audit_log_retention_scanner=audit_log_retention_scanner,
health_aggregator=health_aggregator,
transport_manager=transport_manager,
logger=logger,
config_port=config_port,
driving_adapter_registrar=driving_adapter_registrar,
driving_adapter_unregistrar=driving_adapter_unregistrar,
app_driven_adapters=app_driven_adapters,
)
def create_host_shutdown(
host_bootstrap: HostBootstrap,
di_container: DependencyInjectionContainer,
inflight_drain_waiter: Callable[[float], Awaitable[None]] | None = None,
) -> HostShutdown:
"""构造 HostShutdown 实例,复用 ``create_host_bootstrap`` 注册到 DI 容器的共享依赖。
从 ``DependencyInjectionContainer`` 解析 12 个必填依赖,并传入
``host_bootstrap`` 实例本身(而非其后台任务引用),由 ``HostShutdown``
在关停时惰性读取 ``outboxScanTask`` 等属性。这样允许 ``HostShutdown``
在 ``bootstrap()`` 执行之前创建,避免任务引用在构造时为 ``None`` 导致
关停时取消逻辑被跳过。
INF-017同时从 DI 容器解析 4 个应用级被驱动适配器
``cache_port`` / ``persistence_port`` / ``queue_port`` / ``config_port``
传入 ``app_driven_adapters`` 参数,供 ``HostShutdown`` 在关停步骤 6
显式调用 ``close()`` 释放资源。
INF-006依赖来源由模块级全局状态改为 DI 容器,
消除模块级可变状态INV-5
注:``HostShutdown`` 不再接受 ``driving_adapter_unregistrar`` 参数。
当前系统未接入驱动适配器机制,关停步骤已省略"注销驱动适配器"
当驱动适配器实现就绪后,需在 ``HostShutdown`` 与本工厂同步恢复该参数。
Args:
host_bootstrap: HostBootstrap 实例。无需已执行 ``bootstrap()``
``HostShutdown`` 会在关停时惰性读取其后台任务属性。
di_container: 依赖注入容器,由 ``create_host_bootstrap`` 注册共享
依赖。需与传入 ``host_bootstrap`` 的容器为同一实例。
inflight_drain_waiter: 在途请求排空回调(可选),接受超时参数,
在关停步骤 2 调用以等待在途请求完成。由 ``InflightRequestTracker.wait_drained``
提供。为 ``None`` 时步骤 2 直接等待固定超时。
Returns:
装配完成的 HostShutdown 实例,调用方需进一步调用 ``shutdown()``
执行关停编排。
"""
# 解析应用级被驱动适配器HostBootstrap 构造时注册到 DI 容器)
app_driven_adapters = (
di_container.resolve(CachePort),
di_container.resolve(PersistencePort),
di_container.resolve(QueuePort),
di_container.resolve(ConfigPort),
)
return HostShutdown(
plugin_lifecycle_manager=di_container.resolve(PluginLifecycleManager),
plugin_registry=di_container.resolve(PluginRegistry),
plugin_dependency_resolver=di_container.resolve(PluginDependencyResolver),
stage_slot_injector=di_container.resolve(StageSlotInjector),
stage_slot_registry=di_container.resolve(StageSlotRegistry),
event_subscription_registry=di_container.resolve(EventSubscriptionRegistry),
config_source_registry=di_container.resolve(ConfigSourceRegistry),
event_bus=di_container.resolve(EventBus),
outbox_recovery_scanner=di_container.resolve(OutboxRecoveryScanner),
transport_manager=di_container.resolve(TransportManager),
logger=di_container.resolve(LoggerPort),
host_bootstrap=host_bootstrap,
inflight_drain_waiter=inflight_drain_waiter,
app_driven_adapters=app_driven_adapters,
)
async def load_channel_plugins(
plugin_dir: str,
logger: LoggerPort,
ensure_schema: _EnsureSchemaPort,
) -> tuple[PluginRegistry, dict[ChannelType, OutboundAdapter], EventBus]:
"""加载渠道插件,返回 PluginRegistry、outbound_adapter_registry 与 EventBus。
通过 ``_assemble_plugin_loading_core`` 装配核心组件(与
``create_host_bootstrap`` 共享同一装配拓扑),执行 discover + resolve +
load 全流程,从加载的插件适配器构建 outbound_adapter_registry。供 ARQ
Worker 等非 FastAPI 进程共享调用。
与 ``create_host_bootstrap`` 的行为差异(调用方需知悉):
- **不执行** schema 初始化(``ensure_channel_schema``
- **不执行** 配置加载校验(``_loadConfig`` / ``config_port.getSchema``
- **不注册** 内置事件订阅者OutboxStateAudit / PluginLifecycleAudit /
WhitelistConfig / RouteMatchCache / PairingApproved / ChannelDegraded /
ChannelRecovered 等均缺失)
- **不注册** 通用命令(``registerCommonCommands``
- **不注册** 内置身份解析器explicit_mapping / phone_number
- **不构造** OutboxRecoveryScanner / PairingExpirationScanner /
AuditLogRetentionScanner / HealthAggregator 等编排组件
- **不注册** 任何 DI 容器单例(调用方需自行管理返回的三个实例)
失败处理对齐 ``HostBootstrap._loadPlugins``
- discover 失败记录警告并跳过插件加载warn-and-continue对齐
§11.9 优雅降级)。注意:仅 ``TimeoutError`` 在 ``HostBootstrap``
中会终止启动本函数不实现超时保护discover 异统一律 warn-and-continue。
- resolve 失败:记录警告并使用未排序顺序继续。
- 单个插件 load 失败:记录警告并继续(触发优雅降级),不阻塞其他插件。
Args:
plugin_dir: 插件目录路径,供 ``PluginLifecycleManager.discover`` 扫描。
logger: 日志端口,供所有 framework 层组件注入。
ensure_schema: 数据库会话工厂来源(``PostgresManager`` 满足此
Protocol提供 ``AsyncSession`` 方法),供 ``driven_adapters_factory``
创建请求级 ``AsyncSession``。
Returns:
``(plugin_registry, outbound_adapter_registry, event_bus)`` 三元组。
``plugin_registry`` 已填充已加载插件的清单与适配器。
``outbound_adapter_registry`` 按渠道类型索引 ``OutboundAdapter``。
``event_bus`` 为插件加载过程中使用的 ``EventBus``,供调用方复用。
"""
# 1. 装配插件加载核心组件(与 create_host_bootstrap 共享)
core = await _assemble_plugin_loading_core(logger, ensure_schema, plugin_dir=plugin_dir)
# 2. discover + resolve + load与 HostBootstrap._loadPlugins 一致)
# discover 失败记录警告并跳过插件加载(对齐 §11.9 优雅降级),
# 不静默吞掉错误,保证调用方知晓插件未加载。
try:
manifests = await core.plugin_lifecycle_manager.discover(plugin_dir)
except Exception as e:
await logger.warn(f"插件发现失败,跳过插件加载: {e}")
manifests = []
if not manifests:
await logger.info("无插件可加载")
else:
await logger.info(f"发现 {len(manifests)} 个插件")
# resolve 拓扑排序(失败则使用未排序顺序)
try:
sorted_manifests = core.plugin_dependency_resolver.resolve(manifests)
except Exception as e:
await logger.warn(f"插件依赖解析失败,使用未排序顺序加载: {e}")
sorted_manifests = manifests
# 按拓扑序加载(单个插件失败不阻塞其他插件,触发优雅降级)
for manifest in sorted_manifests:
result = await core.plugin_lifecycle_manager.load(manifest.id)
if result.state == "failed":
await logger.warn(
f"插件加载失败,触发优雅降级: plugin_id={manifest.id}, error={result.error}",
plugin_id=manifest.id,
error=result.error or "",
)
else:
await logger.info(
f"插件加载成功: plugin_id={manifest.id}",
plugin_id=manifest.id,
)
# 3. 构建 outbound_adapter_registry与 create_channel_use_cases 一致)
outbound_adapter_registry: dict[ChannelType, OutboundAdapter] = {}
for channel_type, plugin_da in core.plugin_registry.listPluginAdapters():
for adapter in plugin_da.outbound_adapters:
outbound_adapter_registry[channel_type] = adapter
return core.plugin_registry, outbound_adapter_registry, core.event_bus