feat(events): expose typed payload snapshots

This commit is contained in:
jxxghp
2026-08-25 22:32:33 +08:00
parent c8146ee395
commit 3ba4027d84
13 changed files with 1023 additions and 54 deletions
+81 -20
View File
@@ -37,6 +37,21 @@ class EventErrorBehavior(StrEnum):
NOTIFY = "notify" NOTIFY = "notify"
class EventPayloadMode(StrEnum):
"""描述事件 payload 是运行时对象还是稳定快照。"""
RUNTIME = "runtime"
SNAPSHOT = "snapshot"
EXTENSION = "extension"
class EventValidationMode(StrEnum):
"""描述契约校验失败后的兼容策略。"""
DIAGNOSTIC = "diagnostic"
STRICT = "strict"
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class EventContract: class EventContract:
"""冻结一个事件的 payload 与运行语义。""" """冻结一个事件的 payload 与运行语义。"""
@@ -49,6 +64,11 @@ class EventContract:
delivery: EventDelivery delivery: EventDelivery
error_behavior: EventErrorBehavior error_behavior: EventErrorBehavior
ordering: str ordering: str
input_model: type[BaseModel] | None = None
output_model: type[BaseModel] | None = None
schema_version: int = 1
payload_mode: EventPayloadMode = EventPayloadMode.RUNTIME
validation_mode: EventValidationMode = EventValidationMode.DIAGNOSTIC
sensitive_fields: tuple[str, ...] = () sensitive_fields: tuple[str, ...] = ()
legacy_reason: str | None = None legacy_reason: str | None = None
@@ -66,13 +86,13 @@ _PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
EventType.SubscribeAdded: event_schemas.SubscribeAddedEventData, EventType.SubscribeAdded: event_schemas.SubscribeAddedEventData,
EventType.SubscribeDeleted: event_schemas.SubscribeDeletedEventData, EventType.SubscribeDeleted: event_schemas.SubscribeDeletedEventData,
EventType.SubscribeModified: event_schemas.SubscribeModifiedEventData, EventType.SubscribeModified: event_schemas.SubscribeModifiedEventData,
EventType.DownloadAdded: event_schemas.DownloadAddedEventData, EventType.DownloadAdded: event_schemas.DownloadAddedContractData,
EventType.TransferComplete: event_schemas.TransferResultEventData, EventType.TransferComplete: event_schemas.TransferResultContractData,
EventType.TransferFailed: event_schemas.TransferResultEventData, EventType.TransferFailed: event_schemas.TransferResultContractData,
EventType.SubtitleTransferComplete: event_schemas.TransferResultEventData, EventType.SubtitleTransferComplete: event_schemas.TransferResultContractData,
EventType.SubtitleTransferFailed: event_schemas.TransferResultEventData, EventType.SubtitleTransferFailed: event_schemas.TransferResultContractData,
EventType.AudioTransferComplete: event_schemas.TransferResultEventData, EventType.AudioTransferComplete: event_schemas.TransferResultContractData,
EventType.AudioTransferFailed: event_schemas.TransferResultEventData, EventType.AudioTransferFailed: event_schemas.TransferResultContractData,
EventType.HistoryDeleted: event_schemas.HistoryDeletedEventData, EventType.HistoryDeleted: event_schemas.HistoryDeletedEventData,
EventType.DownloadFileDeleted: event_schemas.DownloadFileDeletedEventData, EventType.DownloadFileDeleted: event_schemas.DownloadFileDeletedEventData,
EventType.DownloadDeleted: event_schemas.DownloadDeletedEventData, EventType.DownloadDeleted: event_schemas.DownloadDeletedEventData,
@@ -81,7 +101,7 @@ _PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
EventType.NoticeMessage: event_schemas.NoticeMessageEventData, EventType.NoticeMessage: event_schemas.NoticeMessageEventData,
EventType.SubscribeComplete: event_schemas.SubscribeCompleteEventData, EventType.SubscribeComplete: event_schemas.SubscribeCompleteEventData,
EventType.SystemError: event_schemas.SystemErrorEventData, EventType.SystemError: event_schemas.SystemErrorEventData,
EventType.MetadataScrape: event_schemas.MetadataScrapeEventData, EventType.MetadataScrape: event_schemas.MetadataScrapeContractData,
EventType.ModuleReload: event_schemas.EmptyEventData, EventType.ModuleReload: event_schemas.EmptyEventData,
EventType.MessageAction: event_schemas.MessageActionEventData, EventType.MessageAction: event_schemas.MessageActionEventData,
EventType.WorkflowExecute: event_schemas.WorkflowExecuteEventData, EventType.WorkflowExecute: event_schemas.WorkflowExecuteEventData,
@@ -93,15 +113,15 @@ _PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
ChainEventType.TransferRenameBuild: event_schemas.TransferRenameBuildEventData, ChainEventType.TransferRenameBuild: event_schemas.TransferRenameBuildEventData,
ChainEventType.TransferIntercept: event_schemas.TransferInterceptEventData, ChainEventType.TransferIntercept: event_schemas.TransferInterceptEventData,
ChainEventType.TransferOverwriteCheck: event_schemas.TransferOverwriteCheckEventData, ChainEventType.TransferOverwriteCheck: event_schemas.TransferOverwriteCheckEventData,
ChainEventType.ResourceSelection: event_schemas.ResourceSelectionEventData, ChainEventType.ResourceSelection: event_schemas.ResourceSelectionContractData,
ChainEventType.ResourceDownload: event_schemas.ResourceDownloadEventData, ChainEventType.ResourceDownload: event_schemas.ResourceDownloadContractData,
ChainEventType.DiscoverSource: event_schemas.DiscoverSourceEventData, ChainEventType.DiscoverSource: event_schemas.DiscoverSourceEventData,
ChainEventType.MediaRecognizeConvert: event_schemas.MediaRecognizeConvertEventData, ChainEventType.MediaRecognizeConvert: event_schemas.MediaRecognizeConvertEventData,
ChainEventType.RecommendSource: event_schemas.RecommendSourceEventData, ChainEventType.RecommendSource: event_schemas.RecommendSourceEventData,
ChainEventType.StorageOperSelection: event_schemas.StorageOperSelectionEventData, ChainEventType.StorageOperSelection: event_schemas.StorageOperSelectionEventData,
ChainEventType.AgentLLMProvider: event_schemas.AgentLLMProviderEventData, ChainEventType.AgentLLMProvider: event_schemas.AgentLLMProviderEventData,
ChainEventType.SubscribeEpisodesRefresh: event_schemas.SubscribeEpisodesRefreshEventData, ChainEventType.SubscribeEpisodesRefresh: event_schemas.SubscribeEpisodesRefreshEventData,
ChainEventType.SubscribeCompletionCheck: event_schemas.SubscribeCompletionCheckEventData, ChainEventType.SubscribeCompletionCheck: event_schemas.SubscribeCompletionCheckContractData,
ChainEventType.NameRecognize: event_schemas.NameRecognizeEventData, ChainEventType.NameRecognize: event_schemas.NameRecognizeEventData,
ChainEventType.MusicNameRecognize: event_schemas.MusicNameRecognizeEventData, ChainEventType.MusicNameRecognize: event_schemas.MusicNameRecognizeEventData,
ChainEventType.MediaRecognize: event_schemas.MediaRecognizeEventData, ChainEventType.MediaRecognize: event_schemas.MediaRecognizeEventData,
@@ -109,6 +129,32 @@ _PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
ChainEventType.WorkflowExecution: ActionContext, ChainEventType.WorkflowExecution: ActionContext,
} }
_SNAPSHOT_EVENTS = {
EventType.DownloadAdded,
EventType.TransferComplete,
EventType.TransferFailed,
EventType.SubtitleTransferComplete,
EventType.SubtitleTransferFailed,
EventType.AudioTransferComplete,
EventType.AudioTransferFailed,
EventType.MetadataScrape,
ChainEventType.ResourceSelection,
ChainEventType.ResourceDownload,
ChainEventType.SubscribeCompletionCheck,
}
_INPUT_MODELS = {
ChainEventType.ResourceSelection: event_schemas.ResourceSelectionInputContractData,
ChainEventType.ResourceDownload: event_schemas.ResourceDownloadInputContractData,
ChainEventType.SubscribeCompletionCheck: event_schemas.SubscribeCompletionCheckInputContractData,
}
_OUTPUT_MODELS = {
ChainEventType.ResourceSelection: event_schemas.ResourceSelectionOutputContractData,
ChainEventType.ResourceDownload: event_schemas.ResourceDownloadOutputContractData,
ChainEventType.SubscribeCompletionCheck: event_schemas.SubscribeCompletionCheckOutputContractData,
}
_DURABLE_REQUIRED = { _DURABLE_REQUIRED = {
EventType.SubscribeAdded, EventType.SubscribeAdded,
EventType.SubscribeModified, EventType.SubscribeModified,
@@ -150,6 +196,14 @@ def _build_contract(event_type: EventType | ChainEventType) -> EventContract:
EventErrorBehavior.STOP_CHAIN if is_chain else EventErrorBehavior.NOTIFY EventErrorBehavior.STOP_CHAIN if is_chain else EventErrorBehavior.NOTIFY
), ),
ordering="priority_serial" if is_chain else "priority_queue", ordering="priority_serial" if is_chain else "priority_queue",
input_model=_INPUT_MODELS.get(event_type, payload_model),
output_model=_OUTPUT_MODELS.get(event_type),
payload_mode=(
EventPayloadMode.SNAPSHOT
if event_type in _SNAPSHOT_EVENTS
else EventPayloadMode.RUNTIME
),
validation_mode=EventValidationMode.DIAGNOSTIC,
sensitive_fields=_SENSITIVE_FIELDS.get(event_type, ()), sensitive_fields=_SENSITIVE_FIELDS.get(event_type, ()),
legacy_reason=( legacy_reason=(
None None
@@ -195,16 +249,23 @@ def validate_event_payload(
) -> tuple[str, ...]: ) -> tuple[str, ...]:
"""在发送边界诊断首批 typed payload,保持原对象和插件 dict 形状不变。""" """在发送边界诊断首批 typed payload,保持原对象和插件 dict 形状不变。"""
try: try:
model = get_event_contract(event_type).payload_model contract = get_event_contract(event_type)
models = tuple(
model
for model in (contract.input_model, contract.output_model)
if model is not None
)
except KeyError: except KeyError:
# 动态插件在旧 ABI 下可能使用宿主枚举之外的字符串事件。 # 动态插件在旧 ABI 下可能使用宿主枚举之外的字符串事件。
return () return ()
if model is None or payload is None: if not models or payload is None:
return () return ()
if isinstance(payload, model): problems: list[str] = []
return () for model in models:
try: if isinstance(payload, model):
model.model_validate(payload) continue
except (ValidationError, TypeError, ValueError) as error: try:
return (str(error),) model.model_validate(payload)
return () except (ValidationError, TypeError, ValueError) as error:
problems.append(f"{model.__name__}: {error}")
return tuple(problems)
+116
View File
@@ -0,0 +1,116 @@
"""事件 payload 的只读契约快照访问。"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from pydantic import BaseModel, ValidationError
from app.runtime.event.contracts import get_event_contract, normalize_event_type
from app.schemas.types import ChainEventType, EventType
@dataclass(frozen=True, slots=True)
class EventPayloadSnapshot:
"""保存原始 payload 及按登记契约解析出的独立快照。"""
event_type: EventType | ChainEventType | str
raw: Any
payload: BaseModel | None
input: BaseModel | None
output: BaseModel | None
known: bool
schema_version: int | None
payload_mode: str | None
validation_mode: str | None
errors: tuple[str, ...] = ()
@property
def valid(self) -> bool:
"""返回已知事件的全部登记模型是否都通过校验。"""
return self.known and not self.errors
def _model_snapshot(
model: type[BaseModel] | None,
payload: Any,
label: str,
cache: dict[type[BaseModel], BaseModel | None],
errors: list[str],
) -> BaseModel | None:
"""按单个模型构造快照,同一模型在一次访问中只解析一次。"""
if model is None:
return None
if model in cache:
return cache[model]
if isinstance(payload, model):
cache[model] = payload.model_copy(deep=True)
return cache[model]
try:
cache[model] = model.model_validate(payload)
except (ValidationError, TypeError, ValueError) as error:
cache[model] = None
errors.append(f"{label}:{model.__name__}: {error}")
return cache[model]
def snapshot_event_data(
event_type: EventType | ChainEventType | str,
event_data: Any,
) -> EventPayloadSnapshot:
"""按事件注册契约解析 payload,未知自定义事件保持原始数据并返回未登记状态。"""
normalized = normalize_event_type(event_type)
try:
contract = get_event_contract(normalized)
except KeyError:
return EventPayloadSnapshot(
event_type=normalized,
raw=event_data,
payload=None,
input=None,
output=None,
known=False,
schema_version=None,
payload_mode=None,
validation_mode=None,
)
errors: list[str] = []
cache: dict[type[BaseModel], BaseModel | None] = {}
payload_snapshot = _model_snapshot(
contract.payload_model,
event_data,
"payload",
cache,
errors,
)
input_snapshot = _model_snapshot(
contract.input_model,
event_data,
"input",
cache,
errors,
)
output_snapshot = _model_snapshot(
contract.output_model,
event_data,
"output",
cache,
errors,
)
return EventPayloadSnapshot(
event_type=normalized,
raw=event_data,
payload=payload_snapshot,
input=input_snapshot,
output=output_snapshot,
known=True,
schema_version=contract.schema_version,
payload_mode=contract.payload_mode.value,
validation_mode=contract.validation_mode.value,
errors=tuple(errors),
)
__all__ = ["EventPayloadSnapshot", "snapshot_event_data"]
+18 -9
View File
@@ -7,26 +7,29 @@ import uuid
from contextvars import ContextVar from contextvars import ContextVar
from dataclasses import dataclass from dataclasses import dataclass
from queue import Empty, PriorityQueue from queue import Empty, PriorityQueue
from typing import Callable, Dict, List, Optional, Tuple, Union, Any, Type from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Type, Union
from app.runtime.config import global_vars
from app.runtime.thread import ThreadHelper
from app.runtime.log import logger
from app.schemas.event import ChainEventData
from app.schemas.types import ChainEventType, EventType
from app.runtime.rate import ExponentialBackoffRateLimiter
from app.foundation.singleton import Singleton from app.foundation.singleton import Singleton
from app.runtime.config import global_vars
from app.runtime.correlation import get_correlation_id
from app.runtime.event.binding import ( from app.runtime.event.binding import (
EventBindingResolver, EventBindingResolver,
EventHandlerBinding, EventHandlerBinding,
HandlerInstanceResolver, HandlerInstanceResolver,
) )
from app.runtime.event.contracts import normalize_event_type, validate_event_payload
from app.runtime.event.dispatch import EventDispatcher from app.runtime.event.dispatch import EventDispatcher
from app.runtime.event.errors import EventErrorNotifier, EventErrorPolicy from app.runtime.event.errors import EventErrorNotifier, EventErrorPolicy
from app.runtime.event.registry import EventRegistry from app.runtime.event.registry import EventRegistry
from app.runtime.event.contracts import normalize_event_type, validate_event_payload from app.runtime.log import logger
from app.runtime.correlation import get_correlation_id
from app.runtime.observability import record_metric from app.runtime.observability import record_metric
from app.runtime.rate import ExponentialBackoffRateLimiter
from app.runtime.thread import ThreadHelper
from app.schemas.event import ChainEventData
from app.schemas.types import ChainEventType, EventType
if TYPE_CHECKING:
from app.runtime.event.snapshot import EventPayloadSnapshot
DEFAULT_EVENT_PRIORITY = 10 # 事件的默认优先级 DEFAULT_EVENT_PRIORITY = 10 # 事件的默认优先级
MIN_EVENT_CONSUMER_THREADS = 1 # 最小事件消费者线程数 MIN_EVENT_CONSUMER_THREADS = 1 # 最小事件消费者线程数
@@ -84,6 +87,12 @@ class Event:
event_name = getattr(self.event_type, "value", self.event_type) event_name = getattr(self.event_type, "value", self.event_type)
return f"<{event_kind}: {event_name}, ID: {self.event_id}, Priority: {self.priority}>" return f"<{event_kind}: {event_name}, ID: {self.event_id}, Priority: {self.priority}>"
def snapshot(self) -> "EventPayloadSnapshot":
"""返回插件可安全读取的类型化 payload 快照,原始 event_data 保持不变。"""
from app.runtime.event.snapshot import snapshot_event_data
return snapshot_event_data(self.event_type, self.event_data)
def __lt__(self, other): def __lt__(self, other):
""" """
定义事件对象的比较规则,基于优先级比较 定义事件对象的比较规则,基于优先级比较
+203 -5
View File
@@ -1,14 +1,89 @@
from collections.abc import Mapping as _Mapping
from enum import Enum as _Enum
from pathlib import Path from pathlib import Path
from typing import Iterable, Optional, Dict, Any, List, Set, Callable from typing import Annotated as _Annotated
from typing import Any, Callable, Dict, Iterable, List, Optional, Set
from typing import Union as _Union
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from pydantic import BeforeValidator as _BeforeValidator
from app.schemas.common import JsonData from app.schemas.common import JsonData
from app.schemas.types import MediaType, NotificationChannel from app.schemas.context import Context as _ContextSnapshotBase
from app.schemas.context import MediaInfo as _MediaInfoSnapshot
from app.schemas.context import MetaInfo as _MetaInfoSnapshot
from app.schemas.file import FileItem from app.schemas.file import FileItem
from app.schemas.media import OptionalMediaIdentityMixin, RequiredMediaIdentityMixin from app.schemas.media import OptionalMediaIdentityMixin, RequiredMediaIdentityMixin
from app.schemas.music import MusicInfo as _MusicInfoSnapshot
from app.schemas.music import MusicMeta as _MusicMetaSnapshot
from app.schemas.subscribe import Subscribe as _SubscribeSnapshot
from app.schemas.transfer import TransferInfo from app.schemas.transfer import TransferInfo
from app.schemas.types import MediaSource from app.schemas.types import MediaSource, MediaType, NotificationChannel
JsonValue = JsonData
"""事件扩展字段允许的 JSON 值类型。"""
def _coerce_event_snapshot(value: Any) -> Any:
"""把旧运行时对象转换为只用于契约校验的可读取快照。"""
if isinstance(value, BaseModel):
value = value.model_dump(mode="python")
elif hasattr(value, "to_dict") and callable(value.to_dict):
value = value.to_dict()
elif isinstance(value, _Enum):
return value.value
elif isinstance(value, Path):
return value.as_posix()
if isinstance(value, _Mapping):
return {str(key): _coerce_event_snapshot(item) for key, item in value.items()}
if isinstance(value, (list, tuple, set, frozenset)):
return [_coerce_event_snapshot(item) for item in value]
return value
EventJsonValue = _Annotated[JsonValue, _BeforeValidator(_coerce_event_snapshot)]
MediaSnapshot = _Annotated[
_Union[_MusicInfoSnapshot, _MediaInfoSnapshot],
_BeforeValidator(_coerce_event_snapshot),
]
MetaSnapshot = _Annotated[
_Union[_MusicMetaSnapshot, _MetaInfoSnapshot],
_BeforeValidator(_coerce_event_snapshot),
]
class ContextSnapshot(_ContextSnapshotBase):
"""资源上下文的稳定事件快照,不替换链路中的运行时 Context。"""
allowed_episodes: Optional[Set[int]] = None
model_config = ConfigDict(
extra="allow",
from_attributes=True,
arbitrary_types_allowed=True,
)
@model_validator(mode="before")
@classmethod
def coerce_runtime_context(cls, value: Any) -> Any:
"""允许旧 Context/dataclass 进入校验,同时保持原对象继续投递。"""
return _coerce_event_snapshot(value)
class FileContextSnapshot(BaseModel):
"""音乐批次中单个文件的元数据上下文快照。"""
path: str
meta: Optional[MetaSnapshot] = None
mediainfo: Optional[MediaSnapshot] = None
model_config = ConfigDict(extra="allow", from_attributes=True)
@model_validator(mode="before")
@classmethod
def coerce_runtime_context(cls, value: Any) -> Any:
"""把旧文件上下文对象转换成可验证的字典。"""
return _coerce_event_snapshot(value)
class Event(BaseModel): class Event(BaseModel):
@@ -26,7 +101,7 @@ class BaseEventData(BaseModel):
事件数据的基类,所有具体事件数据类应继承自此类 事件数据的基类,所有具体事件数据类应继承自此类
""" """
pass model_config = ConfigDict(from_attributes=True, arbitrary_types_allowed=True)
class ExtensibleEventData(BaseEventData): class ExtensibleEventData(BaseEventData):
@@ -249,7 +324,130 @@ class ChainEventData(BaseEventData):
链式事件数据的基类,所有具体事件数据类应继承自此类 链式事件数据的基类,所有具体事件数据类应继承自此类
""" """
pass model_config = ConfigDict(from_attributes=True, arbitrary_types_allowed=True)
class ResourceSelectionContractData(ChainEventData):
"""ResourceSelection 的稳定输入/输出契约。"""
contexts: List[ContextSnapshot] = Field(default_factory=list)
downloader: Optional[str] = None
origin: Optional[str] = None
updated: bool = False
updated_contexts: Optional[List[ContextSnapshot]] = None
source: Optional[str] = "未知拦截源"
class ResourceSelectionInputContractData(ChainEventData):
"""ResourceSelection 事件输入字段。"""
contexts: List[ContextSnapshot] = Field(default_factory=list)
downloader: Optional[str] = None
origin: Optional[str] = None
class ResourceSelectionOutputContractData(ChainEventData):
"""ResourceSelection 事件插件回写字段。"""
updated: bool = False
updated_contexts: Optional[List[ContextSnapshot]] = None
source: Optional[str] = "未知拦截源"
class ResourceDownloadContractData(ChainEventData):
"""ResourceDownload 的稳定输入/输出契约。"""
context: Optional[ContextSnapshot] = None
episodes: Optional[Set[int]] = None
channel: Optional[NotificationChannel] = None
origin: Optional[str] = None
downloader: Optional[str] = None
options: Optional[Dict[str, EventJsonValue]] = None
cancel: bool = False
source: str = "未知拦截源"
reason: str = ""
class ResourceDownloadInputContractData(ChainEventData):
"""ResourceDownload 事件输入字段。"""
context: Optional[ContextSnapshot] = None
episodes: Optional[Set[int]] = None
channel: Optional[NotificationChannel] = None
origin: Optional[str] = None
downloader: Optional[str] = None
options: Optional[Dict[str, EventJsonValue]] = None
class ResourceDownloadOutputContractData(ChainEventData):
"""ResourceDownload 事件插件回写字段。"""
cancel: bool = False
source: str = "未知拦截源"
reason: str = ""
class DownloadAddedContractData(BaseEventData):
"""DownloadAdded 的稳定下载上下文契约。"""
hash: str
context: ContextSnapshot
username: Optional[str] = None
downloader: Optional[str] = None
episodes: List[int] = Field(default_factory=list)
source: Optional[str] = None
idempotency_key: Optional[str] = None
class TransferResultContractData(BaseEventData):
"""整理结果事件的稳定媒体与元数据契约。"""
fileitem: Optional[FileItem] = None
meta: Optional[MetaSnapshot] = None
mediainfo: Optional[MediaSnapshot] = None
transferinfo: Optional[TransferInfo] = None
downloader: Optional[str] = None
download_hash: Optional[str] = None
transfer_history_id: Optional[int] = None
idempotency_key: Optional[str] = None
class MetadataScrapeContractData(ExtensibleEventData):
"""MetadataScrape 的稳定媒体刮削契约。"""
fileitem: FileItem
file_list: List[str] = Field(default_factory=list)
meta: Optional[MetaSnapshot] = None
mediainfo: Optional[MediaSnapshot] = None
overwrite: bool = False
file_contexts: List[FileContextSnapshot] = Field(default_factory=list)
class SubscribeCompletionCheckContractData(ChainEventData):
"""订阅完成判定的稳定订阅、媒体和元数据契约。"""
subscribe: Optional[_SubscribeSnapshot] = None
mediainfo: Optional[MediaSnapshot] = None
meta: Optional[MetaSnapshot] = None
cancel: bool = False
source: str = "未知来源"
reason: str = ""
class SubscribeCompletionCheckInputContractData(ChainEventData):
"""SubscribeCompletionCheck 事件输入字段。"""
subscribe: Optional[_SubscribeSnapshot] = None
mediainfo: Optional[MediaSnapshot] = None
meta: Optional[MetaSnapshot] = None
class SubscribeCompletionCheckOutputContractData(ChainEventData):
"""SubscribeCompletionCheck 事件插件回写字段。"""
cancel: bool = False
source: str = "未知来源"
reason: str = ""
class PluginDataResetEventData(ChainEventData): class PluginDataResetEventData(ChainEventData):
+18
View File
@@ -71,6 +71,7 @@ SCHEMA_EXPORTS = {
'ConfigDict': ('app.schemas.workflow', 'ConfigDict'), 'ConfigDict': ('app.schemas.workflow', 'ConfigDict'),
'ContentType': ('app.schemas.message', 'ContentType'), 'ContentType': ('app.schemas.message', 'ContentType'),
'Context': ('app.schemas.workflow', 'Context'), 'Context': ('app.schemas.workflow', 'Context'),
'ContextSnapshot': ('app.schemas.event', 'ContextSnapshot'),
'ConversationMemory': ('app.schemas.agent', 'ConversationMemory'), 'ConversationMemory': ('app.schemas.agent', 'ConversationMemory'),
'CookieActionResponse': ('app.schemas.servcookie', 'CookieActionResponse'), 'CookieActionResponse': ('app.schemas.servcookie', 'CookieActionResponse'),
'CookieData': ('app.schemas.servcookie', 'CookieData'), 'CookieData': ('app.schemas.servcookie', 'CookieData'),
@@ -87,6 +88,7 @@ SCHEMA_EXPORTS = {
'DiscoverMediaSource': ('app.schemas.event', 'DiscoverMediaSource'), 'DiscoverMediaSource': ('app.schemas.event', 'DiscoverMediaSource'),
'DiscoverSourceEventData': ('app.schemas.event', 'DiscoverSourceEventData'), 'DiscoverSourceEventData': ('app.schemas.event', 'DiscoverSourceEventData'),
'Discriminator': ('app.schemas.context', 'Discriminator'), 'Discriminator': ('app.schemas.context', 'Discriminator'),
'DownloadAddedContractData': ('app.schemas.event', 'DownloadAddedContractData'),
'DownloadAddedData': ('app.schemas.download', 'DownloadAddedData'), 'DownloadAddedData': ('app.schemas.download', 'DownloadAddedData'),
'DownloadAddedEventData': ('app.schemas.event', 'DownloadAddedEventData'), 'DownloadAddedEventData': ('app.schemas.event', 'DownloadAddedEventData'),
'DownloadDeletedEventData': ('app.schemas.event', 'DownloadDeletedEventData'), 'DownloadDeletedEventData': ('app.schemas.event', 'DownloadDeletedEventData'),
@@ -109,9 +111,11 @@ SCHEMA_EXPORTS = {
'EpisodeFormatRule': ('app.schemas.transfer', 'EpisodeFormatRule'), 'EpisodeFormatRule': ('app.schemas.transfer', 'EpisodeFormatRule'),
'ErrorRequest': ('app.schemas.monitoring', 'ErrorRequest'), 'ErrorRequest': ('app.schemas.monitoring', 'ErrorRequest'),
'Event': ('app.schemas.event', 'Event'), 'Event': ('app.schemas.event', 'Event'),
'EventJsonValue': ('app.schemas.event', 'EventJsonValue'),
'ExistMediaInfo': ('app.schemas.mediaserver', 'ExistMediaInfo'), 'ExistMediaInfo': ('app.schemas.mediaserver', 'ExistMediaInfo'),
'ExtensibleEventData': ('app.schemas.event', 'ExtensibleEventData'), 'ExtensibleEventData': ('app.schemas.event', 'ExtensibleEventData'),
'Field': ('app.schemas.mcp', 'Field'), 'Field': ('app.schemas.mcp', 'Field'),
'FileContextSnapshot': ('app.schemas.event', 'FileContextSnapshot'),
'FileItem': ('app.schemas.workflow', 'FileItem'), 'FileItem': ('app.schemas.workflow', 'FileItem'),
'FileNameData': ('app.schemas.common', 'FileNameData'), 'FileNameData': ('app.schemas.common', 'FileNameData'),
'FileURI': ('app.schemas.file', 'FileURI'), 'FileURI': ('app.schemas.file', 'FileURI'),
@@ -125,6 +129,7 @@ SCHEMA_EXPORTS = {
'JsonData': ('app.schemas.mcp', 'JsonData'), 'JsonData': ('app.schemas.mcp', 'JsonData'),
'JsonObject': ('app.schemas.common', 'JsonObject'), 'JsonObject': ('app.schemas.common', 'JsonObject'),
'JsonObjectList': ('app.schemas.common', 'JsonObjectList'), 'JsonObjectList': ('app.schemas.common', 'JsonObjectList'),
'JsonValue': ('app.schemas.event', 'JsonValue'),
'LLMAuthStatus': ('app.schemas.llm', 'LLMAuthStatus'), 'LLMAuthStatus': ('app.schemas.llm', 'LLMAuthStatus'),
'LLMModelCatalogData': ('app.schemas.llm', 'LLMModelCatalogData'), 'LLMModelCatalogData': ('app.schemas.llm', 'LLMModelCatalogData'),
'LLMModelInfo': ('app.schemas.llm', 'LLMModelInfo'), 'LLMModelInfo': ('app.schemas.llm', 'LLMModelInfo'),
@@ -196,6 +201,7 @@ SCHEMA_EXPORTS = {
'MediaServerPlayData': ('app.schemas.mediaserver', 'MediaServerPlayData'), 'MediaServerPlayData': ('app.schemas.mediaserver', 'MediaServerPlayData'),
'MediaServerPlayItem': ('app.schemas.mediaserver', 'MediaServerPlayItem'), 'MediaServerPlayItem': ('app.schemas.mediaserver', 'MediaServerPlayItem'),
'MediaServerSeasonInfo': ('app.schemas.mediaserver', 'MediaServerSeasonInfo'), 'MediaServerSeasonInfo': ('app.schemas.mediaserver', 'MediaServerSeasonInfo'),
'MediaSnapshot': ('app.schemas.event', 'MediaSnapshot'),
'MediaSource': ('app.schemas.transfer', 'MediaSource'), 'MediaSource': ('app.schemas.transfer', 'MediaSource'),
'MediaSourceInfo': ('app.schemas.event', 'MediaSourceInfo'), 'MediaSourceInfo': ('app.schemas.event', 'MediaSourceInfo'),
'MediaType': ('app.schemas.subscribe', 'MediaType'), 'MediaType': ('app.schemas.subscribe', 'MediaType'),
@@ -208,6 +214,8 @@ SCHEMA_EXPORTS = {
'MessageResponse': ('app.schemas.message', 'MessageResponse'), 'MessageResponse': ('app.schemas.message', 'MessageResponse'),
'MessageType': ('app.schemas.message', 'MessageType'), 'MessageType': ('app.schemas.message', 'MessageType'),
'MetaInfo': ('app.schemas.transfer', 'MetaInfo'), 'MetaInfo': ('app.schemas.transfer', 'MetaInfo'),
'MetaSnapshot': ('app.schemas.event', 'MetaSnapshot'),
'MetadataScrapeContractData': ('app.schemas.event', 'MetadataScrapeContractData'),
'MetadataScrapeEventData': ('app.schemas.event', 'MetadataScrapeEventData'), 'MetadataScrapeEventData': ('app.schemas.event', 'MetadataScrapeEventData'),
'MfaChallenge': ('app.schemas.token', 'MfaChallenge'), 'MfaChallenge': ('app.schemas.token', 'MfaChallenge'),
'MfaStatusData': ('app.schemas.mfa', 'MfaStatusData'), 'MfaStatusData': ('app.schemas.mfa', 'MfaStatusData'),
@@ -295,8 +303,14 @@ SCHEMA_EXPORTS = {
'RefreshMediaItem': ('app.schemas.mediaserver', 'RefreshMediaItem'), 'RefreshMediaItem': ('app.schemas.mediaserver', 'RefreshMediaItem'),
'RequestMetrics': ('app.schemas.monitoring', 'RequestMetrics'), 'RequestMetrics': ('app.schemas.monitoring', 'RequestMetrics'),
'RequiredMediaIdentityMixin': ('app.schemas.music', 'RequiredMediaIdentityMixin'), 'RequiredMediaIdentityMixin': ('app.schemas.music', 'RequiredMediaIdentityMixin'),
'ResourceDownloadContractData': ('app.schemas.event', 'ResourceDownloadContractData'),
'ResourceDownloadEventData': ('app.schemas.event', 'ResourceDownloadEventData'), 'ResourceDownloadEventData': ('app.schemas.event', 'ResourceDownloadEventData'),
'ResourceDownloadInputContractData': ('app.schemas.event', 'ResourceDownloadInputContractData'),
'ResourceDownloadOutputContractData': ('app.schemas.event', 'ResourceDownloadOutputContractData'),
'ResourceSelectionContractData': ('app.schemas.event', 'ResourceSelectionContractData'),
'ResourceSelectionEventData': ('app.schemas.event', 'ResourceSelectionEventData'), 'ResourceSelectionEventData': ('app.schemas.event', 'ResourceSelectionEventData'),
'ResourceSelectionInputContractData': ('app.schemas.event', 'ResourceSelectionInputContractData'),
'ResourceSelectionOutputContractData': ('app.schemas.event', 'ResourceSelectionOutputContractData'),
'Response': ('app.schemas.response', 'Response'), 'Response': ('app.schemas.response', 'Response'),
'RootModel': ('app.schemas.mcp', 'RootModel'), 'RootModel': ('app.schemas.mcp', 'RootModel'),
'RuleTestData': ('app.schemas.system', 'RuleTestData'), 'RuleTestData': ('app.schemas.system', 'RuleTestData'),
@@ -349,7 +363,10 @@ SCHEMA_EXPORTS = {
'Subscribe': ('app.schemas.workflow', 'Subscribe'), 'Subscribe': ('app.schemas.workflow', 'Subscribe'),
'SubscribeAddedEventData': ('app.schemas.event', 'SubscribeAddedEventData'), 'SubscribeAddedEventData': ('app.schemas.event', 'SubscribeAddedEventData'),
'SubscribeCompleteEventData': ('app.schemas.event', 'SubscribeCompleteEventData'), 'SubscribeCompleteEventData': ('app.schemas.event', 'SubscribeCompleteEventData'),
'SubscribeCompletionCheckContractData': ('app.schemas.event', 'SubscribeCompletionCheckContractData'),
'SubscribeCompletionCheckEventData': ('app.schemas.event', 'SubscribeCompletionCheckEventData'), 'SubscribeCompletionCheckEventData': ('app.schemas.event', 'SubscribeCompletionCheckEventData'),
'SubscribeCompletionCheckInputContractData': ('app.schemas.event', 'SubscribeCompletionCheckInputContractData'),
'SubscribeCompletionCheckOutputContractData': ('app.schemas.event', 'SubscribeCompletionCheckOutputContractData'),
'SubscribeDeletedEventData': ('app.schemas.event', 'SubscribeDeletedEventData'), 'SubscribeDeletedEventData': ('app.schemas.event', 'SubscribeDeletedEventData'),
'SubscribeDownloadFileInfo': ('app.schemas.subscribe', 'SubscribeDownloadFileInfo'), 'SubscribeDownloadFileInfo': ('app.schemas.subscribe', 'SubscribeDownloadFileInfo'),
'SubscribeEpisodeInfo': ('app.schemas.subscribe', 'SubscribeEpisodeInfo'), 'SubscribeEpisodeInfo': ('app.schemas.subscribe', 'SubscribeEpisodeInfo'),
@@ -396,6 +413,7 @@ SCHEMA_EXPORTS = {
'TransferOverwriteCheckEventData': ('app.schemas.event', 'TransferOverwriteCheckEventData'), 'TransferOverwriteCheckEventData': ('app.schemas.event', 'TransferOverwriteCheckEventData'),
'TransferRenameBuildEventData': ('app.schemas.event', 'TransferRenameBuildEventData'), 'TransferRenameBuildEventData': ('app.schemas.event', 'TransferRenameBuildEventData'),
'TransferRenameEventData': ('app.schemas.event', 'TransferRenameEventData'), 'TransferRenameEventData': ('app.schemas.event', 'TransferRenameEventData'),
'TransferResultContractData': ('app.schemas.event', 'TransferResultContractData'),
'TransferResultEventData': ('app.schemas.event', 'TransferResultEventData'), 'TransferResultEventData': ('app.schemas.event', 'TransferResultEventData'),
'TransferTorrent': ('app.schemas.transfer', 'TransferTorrent'), 'TransferTorrent': ('app.schemas.transfer', 'TransferTorrent'),
'TypeAdapter': ('app.schemas.mcp', 'TypeAdapter'), 'TypeAdapter': ('app.schemas.mcp', 'TypeAdapter'),
+42 -2
View File
@@ -1,6 +1,46 @@
"""插件事件订阅与发布接口。""" """插件事件订阅与发布接口。"""
from app.runtime.event.snapshot import EventPayloadSnapshot, snapshot_event_data
from app.runtime.events import Event, EventManager, eventmanager from app.runtime.events import Event, EventManager, eventmanager
from app.schemas.event import (
ContextSnapshot,
DownloadAddedContractData,
FileContextSnapshot,
MediaSnapshot,
MetadataScrapeContractData,
MetaSnapshot,
ResourceDownloadContractData,
ResourceDownloadInputContractData,
ResourceDownloadOutputContractData,
ResourceSelectionContractData,
ResourceSelectionInputContractData,
ResourceSelectionOutputContractData,
SubscribeCompletionCheckContractData,
SubscribeCompletionCheckInputContractData,
SubscribeCompletionCheckOutputContractData,
TransferResultContractData,
)
__all__ = [
__all__ = ["Event", "EventManager", "eventmanager"] "ContextSnapshot",
"DownloadAddedContractData",
"Event",
"EventManager",
"EventPayloadSnapshot",
"FileContextSnapshot",
"MediaSnapshot",
"MetadataScrapeContractData",
"MetaSnapshot",
"ResourceDownloadContractData",
"ResourceDownloadInputContractData",
"ResourceDownloadOutputContractData",
"ResourceSelectionContractData",
"ResourceSelectionInputContractData",
"ResourceSelectionOutputContractData",
"SubscribeCompletionCheckContractData",
"SubscribeCompletionCheckInputContractData",
"SubscribeCompletionCheckOutputContractData",
"TransferResultContractData",
"eventmanager",
"snapshot_event_data",
]
+13
View File
@@ -351,6 +351,19 @@ flowchart TB
宿主内建 `EventType` / `ChainEventType` 均在事件注册表中绑定 typed payload。开放插件事件允许 宿主内建 `EventType` / `ChainEventType` 均在事件注册表中绑定 typed payload。开放插件事件允许
额外字段,校验只生成诊断;分发给既有插件的仍是原始 dict/model 对象,不改变事件 ABI。 额外字段,校验只生成诊断;分发给既有插件的仍是原始 dict/model 对象,不改变事件 ABI。
插件可通过 `app.sdk.events` 调用 `event.snapshot()`
`snapshot_event_data(event_type, event_data)` 获取独立的类型化快照。返回结果中的 `raw` 保留
原始对象,`payload` 是组合契约,链式事件还提供 `input` / `output` 快照;`known``valid`
`errors` 分别用于处理未知自定义事件和兼容期校验失败。快照修改不会回写原事件,如需拦截、取消
或替换链式结果,插件仍应修改原始 `event.event_data`
```python
from app.sdk.events import snapshot_event_data
snapshot = snapshot_event_data(event.event_type, event.event_data)
if snapshot.valid and snapshot.payload.context.media_info.type == "音乐":
music_type = snapshot.payload.context.media_info.music_type
```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
+1
View File
@@ -536,6 +536,7 @@ Durable post-commit side effects have a separate boundary:
| `app/runtime/event/binding.py` | Explicit module/plugin/host handler resolvers; unresolved classes are diagnosed and skipped, never implicitly constructed by the bus | | `app/runtime/event/binding.py` | Explicit module/plugin/host handler resolvers; unresolved classes are diagnosed and skipped, never implicitly constructed by the bus |
| `app/runtime/event/dispatch.py` | Chain/broadcast ordering, concurrency, target-plugin filtering and isolated delivery | | `app/runtime/event/dispatch.py` | Chain/broadcast ordering, concurrency, target-plugin filtering and isolated delivery |
| `app/runtime/event/errors.py` | Handler failure notification and non-recursive `SystemError` downgrade policy | | `app/runtime/event/errors.py` | Handler failure notification and non-recursive `SystemError` downgrade policy |
| `app/runtime/event/snapshot.py` | Read-only typed payload snapshots for the plugin SDK; never mutates or replaces the event ABI |
| `app/runtime/extensions/module/dispatcher.py` | Plugin-first invocation, short-circuit, list merge, signature relay and sync/async execution | | `app/runtime/extensions/module/dispatcher.py` | Plugin-first invocation, short-circuit, list merge, signature relay and sync/async execution |
| `app/runtime/extensions/module/contracts.py` | High-frequency method families and frozen legacy fallback contract | | `app/runtime/extensions/module/contracts.py` | High-frequency method families and frozen legacy fallback contract |
| `app/application/chain/context.py` | Injectable Chain dependencies and no-argument compatibility provider | | `app/application/chain/context.py` | Injectable Chain dependencies and no-argument compatibility provider |
+9 -1
View File
@@ -13,7 +13,6 @@ from collections import defaultdict
from pathlib import Path from pathlib import Path
from typing import Any, Optional from typing import Any, Optional
PROJECT_ROOT = Path(__file__).resolve().parents[2] PROJECT_ROOT = Path(__file__).resolve().parents[2]
APP_ROOT = PROJECT_ROOT / "app" APP_ROOT = PROJECT_ROOT / "app"
BASELINE_ROOT = PROJECT_ROOT / "tests" / "fixtures" / "architecture" BASELINE_ROOT = PROJECT_ROOT / "tests" / "fixtures" / "architecture"
@@ -988,6 +987,15 @@ def collect_event_specs() -> dict[str, Any]:
return { return {
contract.event_name: { contract.event_name: {
"payload_contract": contract.payload_contract, "payload_contract": contract.payload_contract,
"input_contract": (
contract.input_model.__name__ if contract.input_model else None
),
"output_contract": (
contract.output_model.__name__ if contract.output_model else None
),
"schema_version": contract.schema_version,
"payload_mode": contract.payload_mode.value,
"validation_mode": contract.validation_mode.value,
"mode": contract.mode, "mode": contract.mode,
"visibility": contract.visibility.value, "visibility": contract.visibility.value,
"delivery": contract.delivery.value, "delivery": contract.delivery.value,
+17 -3
View File
@@ -13,8 +13,8 @@
"runtime_to_db": [], "runtime_to_db": [],
"workflow_to_db": [] "workflow_to_db": []
}, },
"edge_count": 6689, "edge_count": 6702,
"edge_sha256": "764be9020b4657c344453dc801ba270c925a2b26c94c0fb43c5a9f89953a4e07", "edge_sha256": "a6d5e5b0ccca6f84fe5d8aa5acb0402e7253958913da8d9624a23bd80ceab748",
"edges": [ "edges": [
"app -> app.runtime", "app -> app.runtime",
"app -> app.runtime.compat", "app -> app.runtime.compat",
@@ -5734,6 +5734,11 @@
"app.runtime.event.registry -> app.runtime.log", "app.runtime.event.registry -> app.runtime.log",
"app.runtime.event.registry -> app.schemas", "app.runtime.event.registry -> app.schemas",
"app.runtime.event.registry -> app.schemas.types", "app.runtime.event.registry -> app.schemas.types",
"app.runtime.event.snapshot -> app.runtime",
"app.runtime.event.snapshot -> app.runtime.event",
"app.runtime.event.snapshot -> app.runtime.event.contracts",
"app.runtime.event.snapshot -> app.schemas",
"app.runtime.event.snapshot -> app.schemas.types",
"app.runtime.events -> app.foundation", "app.runtime.events -> app.foundation",
"app.runtime.events -> app.foundation.singleton", "app.runtime.events -> app.foundation.singleton",
"app.runtime.events -> app.runtime", "app.runtime.events -> app.runtime",
@@ -5745,6 +5750,7 @@
"app.runtime.events -> app.runtime.event.dispatch", "app.runtime.events -> app.runtime.event.dispatch",
"app.runtime.events -> app.runtime.event.errors", "app.runtime.events -> app.runtime.event.errors",
"app.runtime.events -> app.runtime.event.registry", "app.runtime.events -> app.runtime.event.registry",
"app.runtime.events -> app.runtime.event.snapshot",
"app.runtime.events -> app.runtime.log", "app.runtime.events -> app.runtime.log",
"app.runtime.events -> app.runtime.observability", "app.runtime.events -> app.runtime.observability",
"app.runtime.events -> app.runtime.rate", "app.runtime.events -> app.runtime.rate",
@@ -5987,8 +5993,11 @@
"app.schemas.dashboard -> app.schemas.common", "app.schemas.dashboard -> app.schemas.common",
"app.schemas.event -> app.schemas", "app.schemas.event -> app.schemas",
"app.schemas.event -> app.schemas.common", "app.schemas.event -> app.schemas.common",
"app.schemas.event -> app.schemas.context",
"app.schemas.event -> app.schemas.file", "app.schemas.event -> app.schemas.file",
"app.schemas.event -> app.schemas.media", "app.schemas.event -> app.schemas.media",
"app.schemas.event -> app.schemas.music",
"app.schemas.event -> app.schemas.subscribe",
"app.schemas.event -> app.schemas.transfer", "app.schemas.event -> app.schemas.transfer",
"app.schemas.event -> app.schemas.types", "app.schemas.event -> app.schemas.types",
"app.schemas.file -> app.schemas", "app.schemas.file -> app.schemas",
@@ -6093,7 +6102,11 @@
"app.sdk.database -> app.application.backup", "app.sdk.database -> app.application.backup",
"app.sdk.database -> app.application.database", "app.sdk.database -> app.application.database",
"app.sdk.events -> app.runtime", "app.sdk.events -> app.runtime",
"app.sdk.events -> app.runtime.event",
"app.sdk.events -> app.runtime.event.snapshot",
"app.sdk.events -> app.runtime.events", "app.sdk.events -> app.runtime.events",
"app.sdk.events -> app.schemas",
"app.sdk.events -> app.schemas.event",
"app.sdk.logging -> app.runtime", "app.sdk.logging -> app.runtime",
"app.sdk.logging -> app.runtime.log", "app.sdk.logging -> app.runtime.log",
"app.sdk.media -> app.domain", "app.sdk.media -> app.domain",
@@ -6706,7 +6719,7 @@
"app.workflow.actions.transfer_file -> app.workflow", "app.workflow.actions.transfer_file -> app.workflow",
"app.workflow.actions.transfer_file -> app.workflow.actions" "app.workflow.actions.transfer_file -> app.workflow.actions"
], ],
"module_count": 823, "module_count": 824,
"modules": [ "modules": [
"app", "app",
"app.adapters", "app.adapters",
@@ -7382,6 +7395,7 @@
"app.runtime.event.dispatch", "app.runtime.event.dispatch",
"app.runtime.event.errors", "app.runtime.event.errors",
"app.runtime.event.registry", "app.runtime.event.registry",
"app.runtime.event.snapshot",
"app.runtime.events", "app.runtime.events",
"app.runtime.execution", "app.runtime.execution",
"app.runtime.extensions", "app.runtime.extensions",
+366 -11
View File
@@ -1163,539 +1163,804 @@
"ChainEventType.AgentLLMProvider": { "ChainEventType.AgentLLMProvider": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "AgentLLMProviderEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "AgentLLMProviderEventData", "payload_contract": "AgentLLMProviderEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [ "sensitive_fields": [
"api_key" "api_key"
], ],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.AuthIntercept": { "ChainEventType.AuthIntercept": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "AuthInterceptCredentials",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "AuthInterceptCredentials", "payload_contract": "AuthInterceptCredentials",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [ "sensitive_fields": [
"token" "token"
], ],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.AuthVerification": { "ChainEventType.AuthVerification": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "AuthCredentials",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "AuthCredentials", "payload_contract": "AuthCredentials",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [ "sensitive_fields": [
"password", "password",
"token", "token",
"mfa_code" "mfa_code"
], ],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.CommandRegister": { "ChainEventType.CommandRegister": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "CommandRegisterEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "CommandRegisterEventData", "payload_contract": "CommandRegisterEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.DiscoverSource": { "ChainEventType.DiscoverSource": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "DiscoverSourceEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "DiscoverSourceEventData", "payload_contract": "DiscoverSourceEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.MediaRecognize": { "ChainEventType.MediaRecognize": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "MediaRecognizeEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "MediaRecognizeEventData", "payload_contract": "MediaRecognizeEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.MediaRecognizeConvert": { "ChainEventType.MediaRecognizeConvert": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "MediaRecognizeConvertEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "MediaRecognizeConvertEventData", "payload_contract": "MediaRecognizeConvertEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.MusicMediaRecognize": { "ChainEventType.MusicMediaRecognize": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "MusicMediaRecognizeEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "MusicMediaRecognizeEventData", "payload_contract": "MusicMediaRecognizeEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.MusicNameRecognize": { "ChainEventType.MusicNameRecognize": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "MusicNameRecognizeEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "MusicNameRecognizeEventData", "payload_contract": "MusicNameRecognizeEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.NameRecognize": { "ChainEventType.NameRecognize": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "NameRecognizeEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "NameRecognizeEventData", "payload_contract": "NameRecognizeEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.PluginDataReset": { "ChainEventType.PluginDataReset": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "PluginDataResetEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "PluginDataResetEventData", "payload_contract": "PluginDataResetEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.RecommendSource": { "ChainEventType.RecommendSource": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "RecommendSourceEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "RecommendSourceEventData", "payload_contract": "RecommendSourceEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.ResourceDownload": { "ChainEventType.ResourceDownload": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "ResourceDownloadInputContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"payload_contract": "ResourceDownloadEventData", "output_contract": "ResourceDownloadOutputContractData",
"payload_contract": "ResourceDownloadContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.ResourceSelection": { "ChainEventType.ResourceSelection": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "ResourceSelectionInputContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"payload_contract": "ResourceSelectionEventData", "output_contract": "ResourceSelectionOutputContractData",
"payload_contract": "ResourceSelectionContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.StorageOperSelection": { "ChainEventType.StorageOperSelection": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "StorageOperSelectionEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "StorageOperSelectionEventData", "payload_contract": "StorageOperSelectionEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.SubscribeCompletionCheck": { "ChainEventType.SubscribeCompletionCheck": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "SubscribeCompletionCheckInputContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"payload_contract": "SubscribeCompletionCheckEventData", "output_contract": "SubscribeCompletionCheckOutputContractData",
"payload_contract": "SubscribeCompletionCheckContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.SubscribeEpisodesRefresh": { "ChainEventType.SubscribeEpisodesRefresh": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "SubscribeEpisodesRefreshEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "SubscribeEpisodesRefreshEventData", "payload_contract": "SubscribeEpisodesRefreshEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.TransferIntercept": { "ChainEventType.TransferIntercept": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "TransferInterceptEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "TransferInterceptEventData", "payload_contract": "TransferInterceptEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.TransferOverwriteCheck": { "ChainEventType.TransferOverwriteCheck": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "TransferOverwriteCheckEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "TransferOverwriteCheckEventData", "payload_contract": "TransferOverwriteCheckEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.TransferRename": { "ChainEventType.TransferRename": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "TransferRenameEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "TransferRenameEventData", "payload_contract": "TransferRenameEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.TransferRenameBuild": { "ChainEventType.TransferRenameBuild": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "TransferRenameBuildEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "TransferRenameBuildEventData", "payload_contract": "TransferRenameBuildEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"ChainEventType.WorkflowExecution": { "ChainEventType.WorkflowExecution": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "stop_chain", "error_behavior": "stop_chain",
"input_contract": "ActionContext",
"legacy_reason": null, "legacy_reason": null,
"mode": "chain", "mode": "chain",
"ordering": "priority_serial", "ordering": "priority_serial",
"output_contract": null,
"payload_contract": "ActionContext", "payload_contract": "ActionContext",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.AgentTokensUsage": { "EventType.AgentTokensUsage": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "AgentTokensUsageEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "AgentTokensUsageEventData", "payload_contract": "AgentTokensUsageEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.AudioTransferComplete": { "EventType.AudioTransferComplete": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "TransferResultContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"payload_contract": "TransferResultEventData", "output_contract": null,
"payload_contract": "TransferResultContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.AudioTransferFailed": { "EventType.AudioTransferFailed": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "TransferResultContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"payload_contract": "TransferResultEventData", "output_contract": null,
"payload_contract": "TransferResultContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.CommandExcute": { "EventType.CommandExcute": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "CommandExecuteEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "CommandExecuteEventData", "payload_contract": "CommandExecuteEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.ConfigChanged": { "EventType.ConfigChanged": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "ConfigChangeEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "ConfigChangeEventData", "payload_contract": "ConfigChangeEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "host_only" "visibility": "host_only"
}, },
"EventType.DownloadAdded": { "EventType.DownloadAdded": {
"delivery": "durable_required", "delivery": "durable_required",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "DownloadAddedContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"payload_contract": "DownloadAddedEventData", "output_contract": null,
"payload_contract": "DownloadAddedContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.DownloadDeleted": { "EventType.DownloadDeleted": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "DownloadDeletedEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "DownloadDeletedEventData", "payload_contract": "DownloadDeletedEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.DownloadFileDeleted": { "EventType.DownloadFileDeleted": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "DownloadFileDeletedEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "DownloadFileDeletedEventData", "payload_contract": "DownloadFileDeletedEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.HistoryDeleted": { "EventType.HistoryDeleted": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "HistoryDeletedEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "HistoryDeletedEventData", "payload_contract": "HistoryDeletedEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.MessageAction": { "EventType.MessageAction": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "MessageActionEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "MessageActionEventData", "payload_contract": "MessageActionEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.MetadataScrape": { "EventType.MetadataScrape": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "MetadataScrapeContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"payload_contract": "MetadataScrapeEventData", "output_contract": null,
"payload_contract": "MetadataScrapeContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.ModuleReload": { "EventType.ModuleReload": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "EmptyEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "EmptyEventData", "payload_contract": "EmptyEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "host_only" "visibility": "host_only"
}, },
"EventType.NoticeMessage": { "EventType.NoticeMessage": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "NoticeMessageEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "NoticeMessageEventData", "payload_contract": "NoticeMessageEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.PluginAction": { "EventType.PluginAction": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "PluginActionEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "PluginActionEventData", "payload_contract": "PluginActionEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "target_plugin" "visibility": "target_plugin"
}, },
"EventType.PluginReload": { "EventType.PluginReload": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "PluginReloadEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "PluginReloadEventData", "payload_contract": "PluginReloadEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.PluginTriggered": { "EventType.PluginTriggered": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "PluginTriggeredEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "PluginTriggeredEventData", "payload_contract": "PluginTriggeredEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "target_plugin" "visibility": "target_plugin"
}, },
"EventType.SiteDeleted": { "EventType.SiteDeleted": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "SiteEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "SiteEventData", "payload_contract": "SiteEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.SiteRefreshed": { "EventType.SiteRefreshed": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "SiteEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "SiteEventData", "payload_contract": "SiteEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.SiteUpdated": { "EventType.SiteUpdated": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "SiteEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "SiteEventData", "payload_contract": "SiteEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.SubscribeAdded": { "EventType.SubscribeAdded": {
"delivery": "durable_required", "delivery": "durable_required",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "SubscribeAddedEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "SubscribeAddedEventData", "payload_contract": "SubscribeAddedEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.SubscribeComplete": { "EventType.SubscribeComplete": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "SubscribeCompleteEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "SubscribeCompleteEventData", "payload_contract": "SubscribeCompleteEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.SubscribeDeleted": { "EventType.SubscribeDeleted": {
"delivery": "durable_required", "delivery": "durable_required",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "SubscribeDeletedEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "SubscribeDeletedEventData", "payload_contract": "SubscribeDeletedEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.SubscribeModified": { "EventType.SubscribeModified": {
"delivery": "durable_required", "delivery": "durable_required",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "SubscribeModifiedEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "SubscribeModifiedEventData", "payload_contract": "SubscribeModifiedEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.SubtitleTransferComplete": { "EventType.SubtitleTransferComplete": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "TransferResultContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"payload_contract": "TransferResultEventData", "output_contract": null,
"payload_contract": "TransferResultContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.SubtitleTransferFailed": { "EventType.SubtitleTransferFailed": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "TransferResultContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"payload_contract": "TransferResultEventData", "output_contract": null,
"payload_contract": "TransferResultContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.SystemError": { "EventType.SystemError": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "SystemErrorEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "SystemErrorEventData", "payload_contract": "SystemErrorEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "host_only" "visibility": "host_only"
}, },
"EventType.TransferComplete": { "EventType.TransferComplete": {
"delivery": "durable_required", "delivery": "durable_required",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "TransferResultContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"payload_contract": "TransferResultEventData", "output_contract": null,
"payload_contract": "TransferResultContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.TransferFailed": { "EventType.TransferFailed": {
"delivery": "durable_required", "delivery": "durable_required",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "TransferResultContractData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"payload_contract": "TransferResultEventData", "output_contract": null,
"payload_contract": "TransferResultContractData",
"payload_mode": "snapshot",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.UserMessage": { "EventType.UserMessage": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "UserMessageEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "UserMessageEventData", "payload_contract": "UserMessageEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.WebhookMessage": { "EventType.WebhookMessage": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "WebhookEventInfo",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "WebhookEventInfo", "payload_contract": "WebhookEventInfo",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
}, },
"EventType.WorkflowExecute": { "EventType.WorkflowExecute": {
"delivery": "ephemeral", "delivery": "ephemeral",
"error_behavior": "notify", "error_behavior": "notify",
"input_contract": "WorkflowExecuteEventData",
"legacy_reason": null, "legacy_reason": null,
"mode": "broadcast", "mode": "broadcast",
"ordering": "priority_queue", "ordering": "priority_queue",
"output_contract": null,
"payload_contract": "WorkflowExecuteEventData", "payload_contract": "WorkflowExecuteEventData",
"payload_mode": "runtime",
"schema_version": 1,
"sensitive_fields": [], "sensitive_fields": [],
"validation_mode": "diagnostic",
"visibility": "plugin_public" "visibility": "plugin_public"
} }
}, },
@@ -8239,6 +8504,16 @@
} }
], ],
"app.sdk.events": [ "app.sdk.events": [
{
"kind": "import",
"name": "ContextSnapshot",
"target": "app.schemas.event.ContextSnapshot"
},
{
"kind": "import",
"name": "DownloadAddedContractData",
"target": "app.schemas.event.DownloadAddedContractData"
},
{ {
"kind": "import", "kind": "import",
"name": "Event", "name": "Event",
@@ -8249,10 +8524,90 @@
"name": "EventManager", "name": "EventManager",
"target": "app.runtime.events.EventManager" "target": "app.runtime.events.EventManager"
}, },
{
"kind": "import",
"name": "EventPayloadSnapshot",
"target": "app.runtime.event.snapshot.EventPayloadSnapshot"
},
{
"kind": "import",
"name": "FileContextSnapshot",
"target": "app.schemas.event.FileContextSnapshot"
},
{
"kind": "import",
"name": "MediaSnapshot",
"target": "app.schemas.event.MediaSnapshot"
},
{
"kind": "import",
"name": "MetaSnapshot",
"target": "app.schemas.event.MetaSnapshot"
},
{
"kind": "import",
"name": "MetadataScrapeContractData",
"target": "app.schemas.event.MetadataScrapeContractData"
},
{
"kind": "import",
"name": "ResourceDownloadContractData",
"target": "app.schemas.event.ResourceDownloadContractData"
},
{
"kind": "import",
"name": "ResourceDownloadInputContractData",
"target": "app.schemas.event.ResourceDownloadInputContractData"
},
{
"kind": "import",
"name": "ResourceDownloadOutputContractData",
"target": "app.schemas.event.ResourceDownloadOutputContractData"
},
{
"kind": "import",
"name": "ResourceSelectionContractData",
"target": "app.schemas.event.ResourceSelectionContractData"
},
{
"kind": "import",
"name": "ResourceSelectionInputContractData",
"target": "app.schemas.event.ResourceSelectionInputContractData"
},
{
"kind": "import",
"name": "ResourceSelectionOutputContractData",
"target": "app.schemas.event.ResourceSelectionOutputContractData"
},
{
"kind": "import",
"name": "SubscribeCompletionCheckContractData",
"target": "app.schemas.event.SubscribeCompletionCheckContractData"
},
{
"kind": "import",
"name": "SubscribeCompletionCheckInputContractData",
"target": "app.schemas.event.SubscribeCompletionCheckInputContractData"
},
{
"kind": "import",
"name": "SubscribeCompletionCheckOutputContractData",
"target": "app.schemas.event.SubscribeCompletionCheckOutputContractData"
},
{
"kind": "import",
"name": "TransferResultContractData",
"target": "app.schemas.event.TransferResultContractData"
},
{ {
"kind": "import", "kind": "import",
"name": "eventmanager", "name": "eventmanager",
"target": "app.runtime.events.eventmanager" "target": "app.runtime.events.eventmanager"
},
{
"kind": "import",
"name": "snapshot_event_data",
"target": "app.runtime.event.snapshot.snapshot_event_data"
} }
], ],
"app.sdk.logging": [ "app.sdk.logging": [
+40 -3
View File
@@ -8,6 +8,8 @@ from app.application.outbox import (
DURABLE_EVENT_TOPICS, DURABLE_EVENT_TOPICS,
validate_durable_event_handlers, validate_durable_event_handlers,
) )
from app.domain.context import Context, MediaInfo
from app.domain.metainfo import MetaInfo
from app.runtime.event.contracts import ( from app.runtime.event.contracts import (
EVENT_CONTRACTS, EVENT_CONTRACTS,
EventDelivery, EventDelivery,
@@ -15,9 +17,13 @@ from app.runtime.event.contracts import (
validate_event_payload, validate_event_payload,
) )
from app.runtime.events import Event from app.runtime.events import Event
from app.domain.context import Context, MediaInfo from app.schemas.event import (
from app.domain.metainfo import MetaInfo ConfigChangeEventData,
from app.schemas.event import ConfigChangeEventData ResourceDownloadInputContractData,
ResourceDownloadOutputContractData,
ResourceSelectionInputContractData,
ResourceSelectionOutputContractData,
)
from app.schemas.file import FileItem from app.schemas.file import FileItem
from app.schemas.transfer import TransferInfo from app.schemas.transfer import TransferInfo
from app.schemas.types import ChainEventType, EventType, MediaType from app.schemas.types import ChainEventType, EventType, MediaType
@@ -129,6 +135,37 @@ def test_download_and_transfer_typed_contracts_accept_legacy_runtime_objects() -
) == () ) == ()
def test_music_runtime_objects_are_checked_as_typed_snapshots() -> None:
"""音乐运行时对象保留给插件,同时通过快照契约明确 type/music_type。"""
from app.domain.context import MusicInfo
media = MusicInfo(title="Song", artists=["Artist"], album="Album")
context = Context(media_info=media)
assert validate_event_payload(
EventType.DownloadAdded,
{"hash": "music-hash", "context": context},
) == ()
assert validate_event_payload(
EventType.TransferComplete,
{"mediainfo": media},
) == ()
def test_snapshot_contract_metadata_declares_mutable_chain_boundaries() -> None:
"""核心链式事件公开快照输入输出模型,但仍处于兼容诊断模式。"""
selection = get_event_contract(ChainEventType.ResourceSelection)
download = get_event_contract(ChainEventType.ResourceDownload)
assert selection.schema_version == 1
assert selection.payload_mode.value == "snapshot"
assert selection.input_model is ResourceSelectionInputContractData
assert selection.output_model is ResourceSelectionOutputContractData
assert download.input_model is ResourceDownloadInputContractData
assert download.output_model is ResourceDownloadOutputContractData
assert selection.validation_mode.value == "diagnostic"
def test_model_instance_remains_mutable_chain_payload() -> None: def test_model_instance_remains_mutable_chain_payload() -> None:
"""链式处理器继续接收原 model 实例,确保输出字段可原地接力。""" """链式处理器继续接收原 model 实例,确保输出字段可原地接力。"""
payload = ConfigChangeEventData(key={"PROXY_HOST"}) payload = ConfigChangeEventData(key={"PROXY_HOST"})
+99
View File
@@ -0,0 +1,99 @@
"""插件 SDK 事件快照访问测试。"""
from app.domain.context import Context, MediaInfo, MusicInfo
from app.runtime.event.snapshot import (
EventPayloadSnapshot as RuntimeEventPayloadSnapshot,
)
from app.runtime.event.snapshot import snapshot_event_data as runtime_snapshot_event_data
from app.schemas.event import ResourceSelectionEventData
from app.schemas.types import ChainEventType, EventType, MediaType
from app.sdk.events import (
DownloadAddedContractData,
Event,
EventPayloadSnapshot,
ResourceSelectionInputContractData,
ResourceSelectionOutputContractData,
snapshot_event_data,
)
def test_sdk_exports_canonical_snapshot_interfaces() -> None:
"""SDK 只公开 canonical 快照实现,不复制事件解析逻辑。"""
assert EventPayloadSnapshot is RuntimeEventPayloadSnapshot
assert snapshot_event_data is runtime_snapshot_event_data
def test_event_snapshot_exposes_music_type_without_changing_raw_payload() -> None:
"""插件可从快照稳定识别音乐,原 Context 和 MusicInfo 继续按旧 ABI 投递。"""
media = MusicInfo(title="Song", artists=["Artist"], album="Album")
context = Context(media_info=media)
raw = {"hash": "music-hash", "context": context}
event = Event(EventType.DownloadAdded, raw)
snapshot = event.snapshot()
assert snapshot.known is True
assert snapshot.valid is True
assert snapshot.raw is raw
assert isinstance(snapshot.payload, DownloadAddedContractData)
assert snapshot.input is snapshot.payload
assert snapshot.output is None
assert snapshot.payload.context.media_info.type == MediaType.MUSIC.value
assert snapshot.payload.context.media_info.music_type == "recording"
assert event.event_data["context"] is context
snapshot.payload.context.media_info.title = "Snapshot Song"
assert media.title == "Song"
def test_chain_snapshot_separates_plugin_input_and_output_models() -> None:
"""链式事件同时提供输入和回写快照,且不替换可变运行时事件模型。"""
context = Context(
media_info=MediaInfo(type=MediaType.MOVIE, title="Movie", year="2026")
)
raw = ResourceSelectionEventData(
contexts=[context],
updated=True,
updated_contexts=[context],
source="plugin",
)
event = Event(ChainEventType.ResourceSelection, raw)
snapshot = event.snapshot()
assert snapshot.raw is raw
assert isinstance(snapshot.input, ResourceSelectionInputContractData)
assert isinstance(snapshot.output, ResourceSelectionOutputContractData)
assert snapshot.input.contexts[0].media_info.type == MediaType.MOVIE.value
assert snapshot.output.updated is True
assert snapshot.output.updated_contexts[0].media_info.title == "Movie"
assert event.event_data is raw
def test_unknown_plugin_event_returns_unregistered_snapshot_without_error() -> None:
"""插件自定义字符串事件没有宿主契约时保留原 payload,不制造假模型。"""
raw = {"plugin_owned": {"value": 1}}
snapshot = snapshot_event_data("plugin.custom.event", raw)
assert snapshot.known is False
assert snapshot.valid is False
assert snapshot.raw is raw
assert snapshot.payload is None
assert snapshot.input is None
assert snapshot.output is None
assert snapshot.errors == ()
def test_invalid_known_event_returns_validation_errors_instead_of_raising() -> None:
"""坏 payload 由 SDK 结果携带错误,插件可自行决定降级策略。"""
raw = {"context": {"media_info": {"type": "音乐"}}}
snapshot = snapshot_event_data(EventType.DownloadAdded, raw)
assert snapshot.known is True
assert snapshot.valid is False
assert snapshot.raw is raw
assert snapshot.payload is None
assert snapshot.errors
assert "DownloadAddedContractData" in snapshot.errors[0]