ForcePilot/backend/package/yuxi/channels/infrastructure/factory.py
Kris eb80382da0 feat(wechat-woc): 新增SSE流式推送支持,优化日志与幂等性
1.  新增WeChatWocStreamConnectorAdapter,实现SSE长连接接收消息,支持热更新配置
2.  升级wechat_woc插件依赖bridge版本至1.5.0,开启both传输模式
3.  新增SSE相关常量与配置项,完善woc_bridge_client SSE实现
4.  重构RouteStage,复用会话缓存避免重复查询
5.  新增入站/出站全链路日志打点,优化异常场景日志输出
6.  实现入站幂等记录状态回写机制,独立事务避免死锁
7.  新增TransportManager重启恢复逻辑,自动恢复在线账号传输任务
8.  修复inbound_pipeline移除冗余persistence_port参数,注入idempotency仓储
9.  新增InboundContext.idempotency_record_id字段,传递幂等记录主键
2026-07-08 00:17:39 +08:00

1041 lines
49 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, Mapping
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.channel_event_broadcaster import (
ChannelEventBroadcaster,
)
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.config_scope_registry import (
ConfigScopeRegistry,
)
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.lifecycle.sensitive_field_registry import (
SensitiveFieldRegistry,
)
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.config import ConfigScope
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.event.config import configureSensitiveFieldRegistry
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 个默认层级与 6 个内置 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 注册 6 个session_key/identity_id/peer_id/chat_type/account/default
其余 2 个层级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("chat_type", lambda ctx: registry.findByChatType(ctx.account_id, ctx.chat_type))
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
# F-01敏感字段注册表从 CONFIG_SCHEMA 预合并HostBootstrap._loadPlugins
# discover 后调用 register(manifests) 合并插件声明的敏感字段。
sensitive_registry: SensitiveFieldRegistry
# F-02配置作用域注册表从 CONFIG_SCHEMA 预合并HostBootstrap._loadPlugins
# discover 后调用 register(manifests) 合并插件声明的作用域。注入到
# RedisConfigAdapter 供 _build_key 校验 key→scope 一致性。
config_scope_registry: ConfigScopeRegistry
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()
# F-01敏感字段注册表构造时预合并 CONFIG_SCHEMA 中的敏感字段。
# HostBootstrap._loadPlugins discover 后调用 register(manifests) 合并
# 插件 manifest 声明的敏感字段,并通过 configureSensitiveFieldRegistry
# 注入到 core/event/config.py 模块级全局变量。
sensitive_registry = SensitiveFieldRegistry()
# F-02配置作用域注册表构造时预合并 CONFIG_SCHEMA 中的作用域声明。
# HostBootstrap._loadPlugins discover 后调用 register(manifests) 合并
# 插件 manifest 声明的作用域。registry.key_to_scope_map 注入到
# RedisConfigAdapter 供 _build_key 校验 key→scope 一致性。
config_scope_registry = ConfigScopeRegistry()
# driven_adapters_factory 为请求级工厂,每次调用创建独立 AsyncSession
# 与 DrivenAdapters 实例,供 PluginHostImpl 获取端口。
# F-02传入 config_scope_registry.key_to_scope_map 供 RedisConfigAdapter
# 校验作用域一致性。registry 通过 register() 原地更新内部 dictadapter
# 持有的只读视图在 discover 后可见插件声明的作用域。
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,
key_to_scope_map=config_scope_registry.key_to_scope_map,
)
# 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,
sensitive_registry=sensitive_registry,
config_scope_registry=config_scope_registry,
)
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,
di_container: DependencyInjectionContainer | None = None,
) -> 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
形成降级-恢复闭环,满足「降级期间审计日志必须继续记录」要求
- ``ChannelSessionUpdated`` / ``ChannelMessageReceived`` /
``ChannelMessageSent`` → SSE 广播器(渠道实时事件)
"""
# 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),
),
)
# 渠道会话/消息事件 → SSE 广播器BE-3
channel_event_broadcaster = ChannelEventBroadcaster(logger=logger)
if di_container is not None:
di_container.registerSingleton(ChannelEventBroadcaster, channel_event_broadcaster)
for channel_event_type in (
"ChannelSessionUpdated",
"ChannelMessageReceived",
"ChannelMessageSent",
):
event_bus.register(
"host",
EventSubscription(
event_type=channel_event_type,
handler=channel_event_broadcaster,
),
)
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,
key_to_scope_map: Mapping[str, ConfigScope] | None = None,
) -> 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
Args:
key_to_scope_map: F-02 配置作用域映射,注入到 ``RedisConfigAdapter``
供 ``_build_key`` 校验 key→scope 一致性。为 ``None`` 时不校验
(向后兼容)。
Returns:
``(config_port, channel_circuit_breaker, channel_probe, tracer_port,
diagnostics_exporter)`` 五元组,供后续 TransportManager 与
HealthAggregator 构造使用。
"""
config_port: ConfigPort = RedisConfigAdapter(
redis_client=redis_client,
logger=logger,
key_to_scope_map=key_to_scope_map,
)
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,
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)
# F-01敏感字段注册表单例供 create_channel_use_cases resolve 并注入
# ControlPlanePipeline.createConfigHandler + AuditContextBuilder
# 外部 AuditContextBuilder。HostBootstrap._loadPlugins discover 后调用
# register(manifests) 合并插件声明的敏感字段。
di_container.registerSingleton(SensitiveFieldRegistry, core.sensitive_registry)
# F-02配置作用域注册表单例供 create_channel_use_cases resolve 并提取
# key_to_scope_map 注入到请求级 RedisConfigAdapter。HostBootstrap._loadPlugins
# discover 后调用 register(manifests) 合并插件声明的作用域。
di_container.registerSingleton(ConfigScopeRegistry, core.config_scope_registry)
# 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)
# 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 →
端口适配器 → 插件生命周期管理器 → 管道注入器 → 健康聚合器 →
DI 容器 → HostBootstrap。构造完成后将共享依赖注册到
``DependencyInjectionContainer``,供 ``create_host_shutdown`` 复用
INF-006消除模块级可变状态
Outbox 恢复扫描FR-22、配对过期扫描FR-33与审计日志保留期
清理FR-34已迁移至 scheduler worker handler不再由 api 进程构造与
启动,避免共享 ``AsyncSession`` 并发使用与锁 TTL 不匹配问题。
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创建应用级会话供 TransportManager /
# HealthAggregator / 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,
di_container=di_container,
)
# 4. 构造 StageSlotInjector
stage_slot_injector = StageSlotInjector(
stage_slot_registry=core.stage_slot_registry,
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,
key_to_scope_map=core.config_scope_registry.key_to_scope_map,
)
# 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)
result = await use_cases.inbound_message.receiveInbound(cmd)
await logger.debug(
"inbound message delivered",
trace_id=cmd.trace_id,
channel_type=str(cmd.channel_type),
account_id=cmd.account_id,
ack_decision=result.ack_decision,
)
return result
finally:
# SQLAlchemy AsyncSession.close() 会自动 rollback 未提交的事务,
# 无需显式 rollbackL-12 修复:删除冗余 rollback。close 失败
# 时记录 error避免连接泄漏被静默吞掉。
try:
await db.close()
except Exception as exc:
await logger.error(
"inbound_message db close failed, connection may leak",
error=str(exc),
)
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,
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,
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`` 解析必填依赖,并传入
``host_bootstrap`` 实例本身(而非其后台任务引用),由 ``HostShutdown``
在关停时惰性读取后台任务属性。这样允许 ``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),
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
- **不构造** 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)} 个插件")
# F-01合并插件 manifest 声明的敏感字段到 SensitiveFieldRegistry
# 并通过 configureSensitiveFieldRegistry 注入到 core/event/config.py
# 模块级全局变量。worker 进程ARQ 等)发布 ConfigChanged/
# ConfigRollback 事件时_sanitize_config_value 能识别插件声明的
# 敏感字段(如 bridge_url/bot_token/app_secret 等),避免泄露到事件
# 订阅方。对齐 HostBootstrap._loadPlugins 的调用顺序register +
# configure 在 resolve + load 之前)。
core.sensitive_registry.register(manifests)
configureSensitiveFieldRegistry(core.sensitive_registry)
# 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