mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
refactor: complete host module and event contracts
This commit is contained in:
@@ -9,7 +9,9 @@ from typing import Any
|
|||||||
from pydantic import BaseModel, ValidationError
|
from pydantic import BaseModel, ValidationError
|
||||||
|
|
||||||
import app.schemas.event as event_schemas
|
import app.schemas.event as event_schemas
|
||||||
|
from app.schemas.mediaserver import WebhookEventInfo
|
||||||
from app.schemas.types import ChainEventType, EventType
|
from app.schemas.types import ChainEventType, EventType
|
||||||
|
from app.schemas.workflow import ActionContext
|
||||||
|
|
||||||
|
|
||||||
class EventDelivery(StrEnum):
|
class EventDelivery(StrEnum):
|
||||||
@@ -53,6 +55,13 @@ class EventContract:
|
|||||||
|
|
||||||
_PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
|
_PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
|
||||||
EventType.ConfigChanged: event_schemas.ConfigChangeEventData,
|
EventType.ConfigChanged: event_schemas.ConfigChangeEventData,
|
||||||
|
EventType.PluginReload: event_schemas.PluginReloadEventData,
|
||||||
|
EventType.PluginAction: event_schemas.PluginActionEventData,
|
||||||
|
EventType.PluginTriggered: event_schemas.PluginTriggeredEventData,
|
||||||
|
EventType.CommandExcute: event_schemas.CommandExecuteEventData,
|
||||||
|
EventType.SiteDeleted: event_schemas.SiteEventData,
|
||||||
|
EventType.SiteUpdated: event_schemas.SiteEventData,
|
||||||
|
EventType.SiteRefreshed: event_schemas.SiteEventData,
|
||||||
EventType.AgentTokensUsage: event_schemas.AgentTokensUsageEventData,
|
EventType.AgentTokensUsage: event_schemas.AgentTokensUsageEventData,
|
||||||
EventType.SubscribeAdded: event_schemas.SubscribeAddedEventData,
|
EventType.SubscribeAdded: event_schemas.SubscribeAddedEventData,
|
||||||
EventType.SubscribeDeleted: event_schemas.SubscribeDeletedEventData,
|
EventType.SubscribeDeleted: event_schemas.SubscribeDeletedEventData,
|
||||||
@@ -60,6 +69,22 @@ _PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
|
|||||||
EventType.DownloadAdded: event_schemas.DownloadAddedEventData,
|
EventType.DownloadAdded: event_schemas.DownloadAddedEventData,
|
||||||
EventType.TransferComplete: event_schemas.TransferResultEventData,
|
EventType.TransferComplete: event_schemas.TransferResultEventData,
|
||||||
EventType.TransferFailed: event_schemas.TransferResultEventData,
|
EventType.TransferFailed: event_schemas.TransferResultEventData,
|
||||||
|
EventType.SubtitleTransferComplete: event_schemas.TransferResultEventData,
|
||||||
|
EventType.SubtitleTransferFailed: event_schemas.TransferResultEventData,
|
||||||
|
EventType.AudioTransferComplete: event_schemas.TransferResultEventData,
|
||||||
|
EventType.AudioTransferFailed: event_schemas.TransferResultEventData,
|
||||||
|
EventType.HistoryDeleted: event_schemas.HistoryDeletedEventData,
|
||||||
|
EventType.DownloadFileDeleted: event_schemas.DownloadFileDeletedEventData,
|
||||||
|
EventType.DownloadDeleted: event_schemas.DownloadDeletedEventData,
|
||||||
|
EventType.UserMessage: event_schemas.UserMessageEventData,
|
||||||
|
EventType.WebhookMessage: WebhookEventInfo,
|
||||||
|
EventType.NoticeMessage: event_schemas.NoticeMessageEventData,
|
||||||
|
EventType.SubscribeComplete: event_schemas.SubscribeCompleteEventData,
|
||||||
|
EventType.SystemError: event_schemas.SystemErrorEventData,
|
||||||
|
EventType.MetadataScrape: event_schemas.MetadataScrapeEventData,
|
||||||
|
EventType.ModuleReload: event_schemas.EmptyEventData,
|
||||||
|
EventType.MessageAction: event_schemas.MessageActionEventData,
|
||||||
|
EventType.WorkflowExecute: event_schemas.WorkflowExecuteEventData,
|
||||||
ChainEventType.PluginDataReset: event_schemas.PluginDataResetEventData,
|
ChainEventType.PluginDataReset: event_schemas.PluginDataResetEventData,
|
||||||
ChainEventType.AuthVerification: event_schemas.AuthCredentials,
|
ChainEventType.AuthVerification: event_schemas.AuthCredentials,
|
||||||
ChainEventType.AuthIntercept: event_schemas.AuthInterceptCredentials,
|
ChainEventType.AuthIntercept: event_schemas.AuthInterceptCredentials,
|
||||||
@@ -77,6 +102,11 @@ _PAYLOAD_MODELS: dict[EventType | ChainEventType, type[BaseModel]] = {
|
|||||||
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.SubscribeCompletionCheckEventData,
|
||||||
|
ChainEventType.NameRecognize: event_schemas.NameRecognizeEventData,
|
||||||
|
ChainEventType.MusicNameRecognize: event_schemas.MusicNameRecognizeEventData,
|
||||||
|
ChainEventType.MediaRecognize: event_schemas.MediaRecognizeEventData,
|
||||||
|
ChainEventType.MusicMediaRecognize: event_schemas.MusicMediaRecognizeEventData,
|
||||||
|
ChainEventType.WorkflowExecution: ActionContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
_DURABLE_REQUIRED = {
|
_DURABLE_REQUIRED = {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ _METHOD_CONTRACTS = {
|
|||||||
"get_folder": ModuleMethodContract(family="storage", input_contract="StorageFolderRequest", result_contract="FileItem | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "path")),
|
"get_folder": ModuleMethodContract(family="storage", input_contract="StorageFolderRequest", result_contract="FileItem | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "path")),
|
||||||
"get_parent_item": ModuleMethodContract(family="storage", input_contract="StorageParentRequest", result_contract="FileItem | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("fileitem",)),
|
"get_parent_item": ModuleMethodContract(family="storage", input_contract="StorageParentRequest", result_contract="FileItem | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("fileitem",)),
|
||||||
"rename_file": ModuleMethodContract(family="storage", input_contract="StorageRenameRequest", result_contract="bool | FileItem", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("fileitem", "name")),
|
"rename_file": ModuleMethodContract(family="storage", input_contract="StorageRenameRequest", result_contract="bool | FileItem", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("fileitem", "name")),
|
||||||
"storage_manage": ModuleMethodContract(family="storage", input_contract="StorageManageRequest", result_contract="Any", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "action")),
|
"storage_manage": ModuleMethodContract(family="storage", input_contract="StorageManageRequest", result_contract="StorageProviderResult", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "action")),
|
||||||
"snapshot_storage": ModuleMethodContract(family="storage", input_contract="StorageSnapshotRequest", result_contract="dict[str, dict] | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "path", "last_snapshot_time", "max_depth", "previous_snapshot")),
|
"snapshot_storage": ModuleMethodContract(family="storage", input_contract="StorageSnapshotRequest", result_contract="dict[str, dict] | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("storage", "path", "last_snapshot_time", "max_depth", "previous_snapshot")),
|
||||||
"send_message": ModuleMethodContract(family="messaging", input_contract="MessageSendRequest", result_contract="Message | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY),
|
"send_message": ModuleMethodContract(family="messaging", input_contract="MessageSendRequest", result_contract="Message | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY),
|
||||||
"finalize_message": ModuleMethodContract(family="messaging", input_contract="MessageFinalizeRequest", result_contract="Message | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("response",)),
|
"finalize_message": ModuleMethodContract(family="messaging", input_contract="MessageFinalizeRequest", result_contract="Message | None", aggregation=ModuleResultAggregation.FIRST_NON_EMPTY, required_parameters=("response",)),
|
||||||
@@ -107,6 +107,318 @@ _PREFIX_CONTRACTS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 宿主静态扫描到的全部字符串能力名。第三方插件仍可声明未在这里出现的自定义方法,
|
||||||
|
# 自定义方法继续走开放的 legacy contract;宿主新增调用则必须先进入本清单。
|
||||||
|
_OBSERVED_HOST_METHODS = (
|
||||||
|
'anilist_credits',
|
||||||
|
'anilist_discover',
|
||||||
|
'anilist_info',
|
||||||
|
'anilist_person_credits',
|
||||||
|
'anilist_person_detail',
|
||||||
|
'anilist_popular_this_season',
|
||||||
|
'anilist_recommendations',
|
||||||
|
'anilist_trending',
|
||||||
|
'any_files',
|
||||||
|
'async_anilist_credits',
|
||||||
|
'async_anilist_discover',
|
||||||
|
'async_anilist_info',
|
||||||
|
'async_anilist_person_credits',
|
||||||
|
'async_anilist_person_detail',
|
||||||
|
'async_anilist_popular_this_season',
|
||||||
|
'async_anilist_recommendations',
|
||||||
|
'async_anilist_trending',
|
||||||
|
'async_bangumi_calendar',
|
||||||
|
'async_bangumi_credits',
|
||||||
|
'async_bangumi_discover',
|
||||||
|
'async_bangumi_info',
|
||||||
|
'async_bangumi_person_credits',
|
||||||
|
'async_bangumi_person_detail',
|
||||||
|
'async_bangumi_recommend',
|
||||||
|
'async_douban_discover',
|
||||||
|
'async_douban_info',
|
||||||
|
'async_douban_movie_credits',
|
||||||
|
'async_douban_movie_recommend',
|
||||||
|
'async_douban_person_credits',
|
||||||
|
'async_douban_person_detail',
|
||||||
|
'async_douban_tv_credits',
|
||||||
|
'async_douban_tv_recommend',
|
||||||
|
'async_identify_music_by_fingerprint',
|
||||||
|
'async_match_doubaninfo',
|
||||||
|
'async_match_music_album',
|
||||||
|
'async_match_tmdbinfo',
|
||||||
|
'async_movie_hot',
|
||||||
|
'async_movie_showing',
|
||||||
|
'async_movie_top250',
|
||||||
|
'async_obtain_images',
|
||||||
|
'async_recognize_media',
|
||||||
|
'async_refresh_torrents',
|
||||||
|
'async_search_collections',
|
||||||
|
'async_search_medias',
|
||||||
|
'async_search_persons',
|
||||||
|
'async_search_subtitles',
|
||||||
|
'async_search_torrents',
|
||||||
|
'async_tmdb_collection',
|
||||||
|
'async_tmdb_discover',
|
||||||
|
'async_tmdb_episodes',
|
||||||
|
'async_tmdb_group_seasons',
|
||||||
|
'async_tmdb_info',
|
||||||
|
'async_tmdb_movie_credits',
|
||||||
|
'async_tmdb_movie_recommend',
|
||||||
|
'async_tmdb_movie_similar',
|
||||||
|
'async_tmdb_person_credits',
|
||||||
|
'async_tmdb_person_detail',
|
||||||
|
'async_tmdb_seasons',
|
||||||
|
'async_tmdb_trending',
|
||||||
|
'async_tmdb_tv_credits',
|
||||||
|
'async_tmdb_tv_recommend',
|
||||||
|
'async_tmdb_tv_similar',
|
||||||
|
'async_tv_animation',
|
||||||
|
'async_tv_hot',
|
||||||
|
'async_tv_weekly_chinese',
|
||||||
|
'async_tv_weekly_global',
|
||||||
|
'async_update_recognize_cache',
|
||||||
|
'bangumi_calendar',
|
||||||
|
'bangumi_credits',
|
||||||
|
'bangumi_discover',
|
||||||
|
'bangumi_info',
|
||||||
|
'bangumi_person_credits',
|
||||||
|
'bangumi_person_detail',
|
||||||
|
'bangumi_recommend',
|
||||||
|
'channel_manage',
|
||||||
|
'clear_cache',
|
||||||
|
'create_folder',
|
||||||
|
'delete_file',
|
||||||
|
'delete_message',
|
||||||
|
'douban_discover',
|
||||||
|
'douban_info',
|
||||||
|
'douban_movie_credits',
|
||||||
|
'douban_movie_recommend',
|
||||||
|
'douban_person_credits',
|
||||||
|
'douban_person_detail',
|
||||||
|
'douban_tv_credits',
|
||||||
|
'douban_tv_recommend',
|
||||||
|
'download',
|
||||||
|
'download_added',
|
||||||
|
'download_discord_file_bytes',
|
||||||
|
'download_feishu_file_bytes',
|
||||||
|
'download_feishu_image_to_data_url',
|
||||||
|
'download_file',
|
||||||
|
'download_qq_file_bytes',
|
||||||
|
'download_slack_file_bytes',
|
||||||
|
'download_slack_file_to_data_url',
|
||||||
|
'download_synologychat_file_bytes',
|
||||||
|
'download_telegram_file_bytes',
|
||||||
|
'download_telegram_file_to_base64',
|
||||||
|
'download_vocechat_file_bytes',
|
||||||
|
'download_vocechat_image_to_data_url',
|
||||||
|
'download_wechat_image_to_data_url',
|
||||||
|
'download_wechat_media_bytes',
|
||||||
|
'downloader_info',
|
||||||
|
'edit_message',
|
||||||
|
'filter_torrents',
|
||||||
|
'finalize_message',
|
||||||
|
'get_file_item',
|
||||||
|
'get_folder',
|
||||||
|
'get_parent_item',
|
||||||
|
'get_search_page_size',
|
||||||
|
'get_torrent_trackers',
|
||||||
|
'identify_music_by_fingerprint',
|
||||||
|
'list_files',
|
||||||
|
'list_torrents',
|
||||||
|
'load_category_config',
|
||||||
|
'mark_message_processing_finished',
|
||||||
|
'mark_message_processing_started',
|
||||||
|
'match_doubaninfo',
|
||||||
|
'match_music_album',
|
||||||
|
'match_tmdbinfo',
|
||||||
|
'media_category',
|
||||||
|
'media_exists',
|
||||||
|
'media_files',
|
||||||
|
'media_statistic',
|
||||||
|
'mediaserver_image_cookies',
|
||||||
|
'mediaserver_iteminfo',
|
||||||
|
'mediaserver_items',
|
||||||
|
'mediaserver_items_count',
|
||||||
|
'mediaserver_latest',
|
||||||
|
'mediaserver_latest_images',
|
||||||
|
'mediaserver_librarys',
|
||||||
|
'mediaserver_play_url',
|
||||||
|
'mediaserver_playing',
|
||||||
|
'mediaserver_season_episode_ids',
|
||||||
|
'mediaserver_tv_episodes',
|
||||||
|
'message_parser',
|
||||||
|
'metadata_img',
|
||||||
|
'metadata_nfo',
|
||||||
|
'movie_hot',
|
||||||
|
'movie_showing',
|
||||||
|
'movie_top250',
|
||||||
|
'music_album',
|
||||||
|
'music_album_related',
|
||||||
|
'music_artist',
|
||||||
|
'music_artist_albums',
|
||||||
|
'music_artist_related',
|
||||||
|
'music_cache_clear',
|
||||||
|
'music_cache_delete',
|
||||||
|
'music_cache_items',
|
||||||
|
'music_chart',
|
||||||
|
'music_discover',
|
||||||
|
'music_fresh_releases',
|
||||||
|
'music_lyrics',
|
||||||
|
'obtain_images',
|
||||||
|
'obtain_specific_image',
|
||||||
|
'recognize_media',
|
||||||
|
'recommend_name',
|
||||||
|
'refresh_torrents',
|
||||||
|
'refresh_userdata',
|
||||||
|
'register_commands',
|
||||||
|
'remove_torrents',
|
||||||
|
'rename_file',
|
||||||
|
'save_category_config',
|
||||||
|
'scheduler_job',
|
||||||
|
'search_collections',
|
||||||
|
'search_medias',
|
||||||
|
'search_music',
|
||||||
|
'search_persons',
|
||||||
|
'search_subtitles',
|
||||||
|
'search_torrents',
|
||||||
|
'search_tvdb',
|
||||||
|
'send_direct_message',
|
||||||
|
'set_torrents_tag',
|
||||||
|
'site_subtitle_links',
|
||||||
|
'snapshot_storage',
|
||||||
|
'start_torrents',
|
||||||
|
'stop_torrents',
|
||||||
|
'storage_manage',
|
||||||
|
'tmdb_cache_clear',
|
||||||
|
'tmdb_cache_delete',
|
||||||
|
'tmdb_cache_items',
|
||||||
|
'tmdb_collection',
|
||||||
|
'tmdb_discover',
|
||||||
|
'tmdb_episodes',
|
||||||
|
'tmdb_group_seasons',
|
||||||
|
'tmdb_info',
|
||||||
|
'tmdb_movie_credits',
|
||||||
|
'tmdb_movie_recommend',
|
||||||
|
'tmdb_movie_similar',
|
||||||
|
'tmdb_person_credits',
|
||||||
|
'tmdb_person_detail',
|
||||||
|
'tmdb_seasons',
|
||||||
|
'tmdb_trending',
|
||||||
|
'tmdb_tv_credits',
|
||||||
|
'tmdb_tv_recommend',
|
||||||
|
'tmdb_tv_similar',
|
||||||
|
'torrent_files',
|
||||||
|
'transfer',
|
||||||
|
'transfer_completed',
|
||||||
|
'tv_animation',
|
||||||
|
'tv_hot',
|
||||||
|
'tv_weekly_chinese',
|
||||||
|
'tv_weekly_global',
|
||||||
|
'tvdb_info',
|
||||||
|
'tvdb_slug',
|
||||||
|
'update_recognize_cache',
|
||||||
|
'update_torrent',
|
||||||
|
'upload_file',
|
||||||
|
'user_authenticate',
|
||||||
|
'webhook_parser',
|
||||||
|
)
|
||||||
|
|
||||||
|
_FAMILY_IO_CONTRACTS = {
|
||||||
|
"anilist": ("AniListKeywordArguments", "AniListProviderResult"),
|
||||||
|
"authentication": ("AuthenticationKeywordArguments", "AuthenticationResult"),
|
||||||
|
"bangumi": ("BangumiKeywordArguments", "BangumiProviderResult"),
|
||||||
|
"category": ("CategoryKeywordArguments", "CategoryProviderResult"),
|
||||||
|
"douban": ("DoubanKeywordArguments", "DoubanProviderResult"),
|
||||||
|
"downloader": ("DownloaderKeywordArguments", "DownloaderProviderResult"),
|
||||||
|
"integration": ("IntegrationKeywordArguments", "IntegrationProviderResult"),
|
||||||
|
"media-discovery": ("MediaDiscoveryKeywordArguments", "MediaDiscoveryProviderResult"),
|
||||||
|
"media-recognition": ("MediaRecognitionKeywordArguments", "MediaRecognitionProviderResult"),
|
||||||
|
"media-server": ("MediaServerKeywordArguments", "MediaServerProviderResult"),
|
||||||
|
"messaging": ("MessagingKeywordArguments", "MessagingProviderResult"),
|
||||||
|
"metadata": ("MetadataKeywordArguments", "MetadataProviderResult"),
|
||||||
|
"music": ("MusicKeywordArguments", "MusicProviderResult"),
|
||||||
|
"site": ("SiteKeywordArguments", "SiteProviderResult"),
|
||||||
|
"storage": ("StorageKeywordArguments", "StorageProviderResult"),
|
||||||
|
"tmdb": ("TmdbKeywordArguments", "TmdbProviderResult"),
|
||||||
|
"tvdb": ("TvdbKeywordArguments", "TvdbProviderResult"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _infer_observed_family(method: str) -> str:
|
||||||
|
"""按稳定能力前缀把已观察宿主方法归入可审计的输入/结果族。"""
|
||||||
|
for prefix, contract in _PREFIX_CONTRACTS:
|
||||||
|
if method.startswith(prefix):
|
||||||
|
return contract.family
|
||||||
|
if method.startswith(("mediaserver_", "media_exists", "media_statistic")):
|
||||||
|
return "media-server"
|
||||||
|
if method.startswith((
|
||||||
|
"download", "torrent_", "list_torrents", "refresh_torrents",
|
||||||
|
"remove_torrents", "start_torrents", "stop_torrents",
|
||||||
|
"set_torrents_tag", "update_torrent", "get_torrent_trackers",
|
||||||
|
"downloader_info", "filter_torrents", "transfer_completed",
|
||||||
|
)):
|
||||||
|
return "downloader"
|
||||||
|
if method.startswith((
|
||||||
|
"channel_", "delete_message", "edit_message", "finalize_message",
|
||||||
|
"mark_message_", "message_parser", "register_commands",
|
||||||
|
"send_direct_message", "send_message",
|
||||||
|
)):
|
||||||
|
return "messaging"
|
||||||
|
if method.startswith((
|
||||||
|
"any_files", "create_folder", "delete_file", "get_file_item",
|
||||||
|
"get_folder", "get_parent_item", "list_files", "media_files",
|
||||||
|
"rename_file", "snapshot_storage", "storage_manage", "transfer",
|
||||||
|
"upload_file",
|
||||||
|
)):
|
||||||
|
return "storage"
|
||||||
|
if method.startswith((
|
||||||
|
"metadata_", "obtain_specific_image", "recommend_name",
|
||||||
|
)):
|
||||||
|
return "metadata"
|
||||||
|
if method.startswith((
|
||||||
|
"async_identify_music", "async_match_music", "identify_music",
|
||||||
|
"match_music", "search_music",
|
||||||
|
)):
|
||||||
|
return "music"
|
||||||
|
if method.startswith((
|
||||||
|
"async_match_", "async_obtain_images", "async_recognize_media",
|
||||||
|
"async_update_recognize_cache", "match_", "obtain_images",
|
||||||
|
"recognize_media", "update_recognize_cache",
|
||||||
|
)):
|
||||||
|
return "media-recognition"
|
||||||
|
if method.startswith((
|
||||||
|
"async_movie_", "async_search_", "async_tv_", "movie_",
|
||||||
|
"search_collections", "search_medias", "search_persons",
|
||||||
|
"search_subtitles", "search_torrents", "tv_",
|
||||||
|
)):
|
||||||
|
return "media-discovery"
|
||||||
|
if method in {"clear_cache", "load_category_config", "save_category_config"}:
|
||||||
|
return "category"
|
||||||
|
if method in {"get_search_page_size", "refresh_userdata", "site_subtitle_links"}:
|
||||||
|
return "site"
|
||||||
|
if method == "user_authenticate":
|
||||||
|
return "authentication"
|
||||||
|
return "integration"
|
||||||
|
|
||||||
|
|
||||||
|
def _register_observed_host_contracts() -> None:
|
||||||
|
"""为全部宿主字符串调用登记完整 V2 字段,保留未知插件方法的 legacy fallback。"""
|
||||||
|
for method in _OBSERVED_HOST_METHODS:
|
||||||
|
if method in _METHOD_CONTRACTS:
|
||||||
|
continue
|
||||||
|
family = _infer_observed_family(method)
|
||||||
|
input_contract, result_contract = _FAMILY_IO_CONTRACTS[family]
|
||||||
|
_METHOD_CONTRACTS[method] = ModuleMethodContract(
|
||||||
|
family=family,
|
||||||
|
input_contract=input_contract,
|
||||||
|
result_contract=result_contract,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_register_observed_host_contracts()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_module_method_contract(method: str) -> ModuleMethodContract:
|
def get_module_method_contract(method: str) -> ModuleMethodContract:
|
||||||
"""返回方法的显式能力族契约,未知方法保持既有 legacy 协议。"""
|
"""返回方法的显式能力族契约,未知方法保持既有 legacy 协议。"""
|
||||||
if contract := _METHOD_CONTRACTS.get(method):
|
if contract := _METHOD_CONTRACTS.get(method):
|
||||||
|
|||||||
+185
-1
@@ -1,7 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterable, Optional, Dict, Any, List, Set, Callable
|
from typing import Iterable, Optional, Dict, Any, List, Set, Callable
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||||
|
|
||||||
from app.schemas.common import JsonData
|
from app.schemas.common import JsonData
|
||||||
from app.schemas.types import MediaType, NotificationChannel
|
from app.schemas.types import MediaType, NotificationChannel
|
||||||
@@ -29,6 +29,190 @@ class BaseEventData(BaseModel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensibleEventData(BaseEventData):
|
||||||
|
"""允许第三方插件附加字段的类型化事件载荷基类。"""
|
||||||
|
|
||||||
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyEventData(ExtensibleEventData):
|
||||||
|
"""当前没有固定字段、但仍需进入 typed registry 的事件载荷。"""
|
||||||
|
|
||||||
|
|
||||||
|
class PluginReloadEventData(ExtensibleEventData):
|
||||||
|
"""插件重载广播事件载荷。"""
|
||||||
|
|
||||||
|
plugin_id: str = Field(description="重载的插件 ID")
|
||||||
|
|
||||||
|
|
||||||
|
class PluginActionEventData(ExtensibleEventData):
|
||||||
|
"""插件命令动作载荷;具体动作参数由目标插件扩展。"""
|
||||||
|
|
||||||
|
plugin_id: Optional[str] = Field(default=None, description="目标插件 ID")
|
||||||
|
action: Optional[str] = Field(default=None, description="插件动作名称")
|
||||||
|
channel: Optional[str] = Field(default=None, description="消息渠道")
|
||||||
|
source: Optional[str] = Field(default=None, description="消息来源")
|
||||||
|
user: Optional[Any] = Field(default=None, description="发起用户")
|
||||||
|
|
||||||
|
|
||||||
|
class PluginTriggeredEventData(ExtensibleEventData):
|
||||||
|
"""插件主动发布的跨插件事件载荷。"""
|
||||||
|
|
||||||
|
plugin_id: str = Field(description="发布事件的插件 ID")
|
||||||
|
event_name: str = Field(description="插件定义的稳定事件名")
|
||||||
|
data: Any = Field(default=None, description="插件定义的事件数据")
|
||||||
|
|
||||||
|
|
||||||
|
class CommandExecuteEventData(ExtensibleEventData):
|
||||||
|
"""斜杠命令执行事件载荷。"""
|
||||||
|
|
||||||
|
cmd: str = Field(description="包含参数的完整命令文本")
|
||||||
|
user: Optional[Any] = Field(default=None, description="发起用户")
|
||||||
|
channel: Optional[str] = Field(default=None, description="消息渠道")
|
||||||
|
source: Optional[str] = Field(default=None, description="消息来源")
|
||||||
|
processing_status: Optional[Any] = Field(default=None, description="交互处理状态")
|
||||||
|
|
||||||
|
|
||||||
|
class SiteEventData(ExtensibleEventData):
|
||||||
|
"""站点新增、更新、删除或数据刷新事件载荷。"""
|
||||||
|
|
||||||
|
site_id: Optional[int | str] = Field(default=None, description="站点 ID 或通配符")
|
||||||
|
domain: Optional[str] = Field(default=None, description="站点域名")
|
||||||
|
name: Optional[str] = Field(default=None, description="站点名称")
|
||||||
|
site_url: Optional[str] = Field(default=None, description="站点地址")
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryDeletedEventData(ExtensibleEventData):
|
||||||
|
"""历史记录删除事件的兼容载荷。"""
|
||||||
|
|
||||||
|
history_id: Optional[int] = Field(default=None, description="历史记录 ID")
|
||||||
|
src: Optional[str] = Field(default=None, description="关联源路径")
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadFileDeletedEventData(ExtensibleEventData):
|
||||||
|
"""下载源文件删除事件载荷。"""
|
||||||
|
|
||||||
|
src: Optional[str] = Field(default=None, description="已删除的下载源路径")
|
||||||
|
hash: Optional[str] = Field(default=None, description="下载任务 hash")
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadDeletedEventData(ExtensibleEventData):
|
||||||
|
"""下载任务删除事件载荷。"""
|
||||||
|
|
||||||
|
hash: str = Field(description="下载任务 hash")
|
||||||
|
torrents: List[Dict[str, Any]] = Field(default_factory=list, description="删除前任务快照")
|
||||||
|
|
||||||
|
|
||||||
|
class UserMessageEventData(ExtensibleEventData):
|
||||||
|
"""未被宿主命令或交互消费的用户文本消息载荷。"""
|
||||||
|
|
||||||
|
text: str = Field(description="用户文本")
|
||||||
|
userid: Optional[Any] = Field(default=None, description="用户 ID")
|
||||||
|
channel: Optional[str] = Field(default=None, description="消息渠道")
|
||||||
|
source: Optional[str] = Field(default=None, description="消息来源")
|
||||||
|
chat_id: Optional[Any] = Field(default=None, description="会话 ID")
|
||||||
|
reply_to_message_id: Optional[Any] = Field(default=None, description="回复消息 ID")
|
||||||
|
|
||||||
|
|
||||||
|
class NoticeMessageEventData(ExtensibleEventData):
|
||||||
|
"""宿主向消息模块发送的通知事件载荷。"""
|
||||||
|
|
||||||
|
type: Optional[Any] = Field(default=None, description="兼容消息类型")
|
||||||
|
title: Optional[str] = Field(default=None, description="消息标题")
|
||||||
|
text: Optional[str] = Field(default=None, description="消息正文")
|
||||||
|
userid: Optional[Any] = Field(default=None, description="目标用户 ID")
|
||||||
|
channel: Optional[Any] = Field(default=None, description="目标消息渠道")
|
||||||
|
source: Optional[str] = Field(default=None, description="消息来源")
|
||||||
|
|
||||||
|
|
||||||
|
class SubscribeCompleteEventData(ExtensibleEventData):
|
||||||
|
"""订阅完成广播事件载荷。"""
|
||||||
|
|
||||||
|
subscribe_id: int = Field(description="已完成订阅 ID")
|
||||||
|
subscribe_info: Dict[str, Any] = Field(default_factory=dict, description="订阅快照")
|
||||||
|
mediainfo: Dict[str, Any] = Field(default_factory=dict, description="媒体信息快照")
|
||||||
|
|
||||||
|
|
||||||
|
class SystemErrorEventData(ExtensibleEventData):
|
||||||
|
"""事件、模块、插件或调度器错误的宿主诊断载荷。"""
|
||||||
|
|
||||||
|
type: str = Field(description="错误来源类别")
|
||||||
|
error: str = Field(description="错误摘要")
|
||||||
|
traceback: Optional[str] = Field(default=None, description="错误堆栈")
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataScrapeEventData(ExtensibleEventData):
|
||||||
|
"""媒体文件元数据刮削事件载荷。"""
|
||||||
|
|
||||||
|
fileitem: FileItem = Field(description="待刮削目录或文件项")
|
||||||
|
file_list: List[str] = Field(default_factory=list, description="待刮削文件清单")
|
||||||
|
meta: Any = Field(default=None, description="文件名解析对象")
|
||||||
|
mediainfo: Any = Field(default=None, description="媒体信息对象")
|
||||||
|
overwrite: bool = Field(default=False, description="是否覆盖已有元数据")
|
||||||
|
file_contexts: List[Any] = Field(default_factory=list, description="逐文件上下文")
|
||||||
|
|
||||||
|
|
||||||
|
class MessageActionEventData(ExtensibleEventData):
|
||||||
|
"""定向插件消息交互动作载荷。"""
|
||||||
|
|
||||||
|
plugin_id: Optional[str] = Field(default=None, description="目标插件 ID")
|
||||||
|
text: Optional[str] = Field(default=None, description="兼容动作文本")
|
||||||
|
input_text: Optional[str] = Field(default=None, description="用户输入文本")
|
||||||
|
userid: Optional[Any] = Field(default=None, description="用户 ID")
|
||||||
|
channel: Optional[str] = Field(default=None, description="消息渠道")
|
||||||
|
source: Optional[str] = Field(default=None, description="消息来源")
|
||||||
|
input_session_id: Optional[str] = Field(default=None, description="输入会话 ID")
|
||||||
|
payload: Any = Field(default=None, description="插件自定义交互数据")
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowExecuteEventData(ExtensibleEventData):
|
||||||
|
"""请求执行指定工作流的事件载荷。"""
|
||||||
|
|
||||||
|
workflow_id: int = Field(description="工作流 ID")
|
||||||
|
|
||||||
|
|
||||||
|
class NameRecognizeEventData(ExtensibleEventData):
|
||||||
|
"""影视名称辅助识别的输入和插件回写字段。"""
|
||||||
|
|
||||||
|
title: str = Field(description="待识别标题")
|
||||||
|
name: Optional[str] = Field(default=None, description="插件识别后的名称")
|
||||||
|
year: Optional[Any] = Field(default=None, description="年份")
|
||||||
|
season: Optional[Any] = Field(default=None, description="季号")
|
||||||
|
episode: Optional[Any] = Field(default=None, description="集号")
|
||||||
|
|
||||||
|
|
||||||
|
class MusicNameRecognizeEventData(ExtensibleEventData):
|
||||||
|
"""音乐名称辅助识别的输入和插件回写字段。"""
|
||||||
|
|
||||||
|
title: str = Field(description="待识别曲名")
|
||||||
|
artist: Optional[str] = Field(default=None, description="艺术家")
|
||||||
|
album: Optional[str] = Field(default=None, description="专辑")
|
||||||
|
year: Optional[Any] = Field(default=None, description="年份")
|
||||||
|
duration: Optional[Any] = Field(default=None, description="时长")
|
||||||
|
name: Optional[str] = Field(default=None, description="插件识别后的曲名")
|
||||||
|
|
||||||
|
|
||||||
|
class MediaRecognizeEventData(ExtensibleEventData):
|
||||||
|
"""影视媒体身份补充识别的输入和插件回写字段。"""
|
||||||
|
|
||||||
|
title: Optional[str] = Field(default=None, description="待识别标题")
|
||||||
|
year: Optional[Any] = Field(default=None, description="年份")
|
||||||
|
season: Optional[Any] = Field(default=None, description="季号")
|
||||||
|
type: Optional[Any] = Field(default=None, description="媒体类型")
|
||||||
|
media_source: Optional[Any] = Field(default=None, description="媒体数据源")
|
||||||
|
media_id: Optional[Any] = Field(default=None, description="数据源原生 ID")
|
||||||
|
mediainfo: Optional[Dict[str, Any]] = Field(default=None, description="插件回写媒体信息")
|
||||||
|
|
||||||
|
|
||||||
|
class MusicMediaRecognizeEventData(MediaRecognizeEventData):
|
||||||
|
"""音乐媒体身份补充识别的输入和插件回写字段。"""
|
||||||
|
|
||||||
|
artists: List[str] = Field(default_factory=list, description="艺术家列表")
|
||||||
|
album: Optional[str] = Field(default=None, description="专辑")
|
||||||
|
isrc: Optional[str] = Field(default=None, description="ISRC")
|
||||||
|
music_type: Optional[str] = Field(default=None, description="音乐实体类型")
|
||||||
|
|
||||||
|
|
||||||
class ConfigChangeEventData(BaseEventData):
|
class ConfigChangeEventData(BaseEventData):
|
||||||
"""
|
"""
|
||||||
ConfigChange 事件的数据模型
|
ConfigChange 事件的数据模型
|
||||||
|
|||||||
+22
-1
@@ -65,6 +65,7 @@ SCHEMA_EXPORTS = {
|
|||||||
'ChannelCapability': ('app.schemas.notification', 'ChannelCapability'),
|
'ChannelCapability': ('app.schemas.notification', 'ChannelCapability'),
|
||||||
'ChannelCapabilityManager': ('app.schemas.notification', 'ChannelCapabilityManager'),
|
'ChannelCapabilityManager': ('app.schemas.notification', 'ChannelCapabilityManager'),
|
||||||
'ClassVar': ('app.schemas.subscribe', 'ClassVar'),
|
'ClassVar': ('app.schemas.subscribe', 'ClassVar'),
|
||||||
|
'CommandExecuteEventData': ('app.schemas.event', 'CommandExecuteEventData'),
|
||||||
'CommandRegisterEventData': ('app.schemas.event', 'CommandRegisterEventData'),
|
'CommandRegisterEventData': ('app.schemas.event', 'CommandRegisterEventData'),
|
||||||
'ConfigChangeEventData': ('app.schemas.event', 'ConfigChangeEventData'),
|
'ConfigChangeEventData': ('app.schemas.event', 'ConfigChangeEventData'),
|
||||||
'ConfigDict': ('app.schemas.workflow', 'ConfigDict'),
|
'ConfigDict': ('app.schemas.workflow', 'ConfigDict'),
|
||||||
@@ -86,7 +87,9 @@ SCHEMA_EXPORTS = {
|
|||||||
'Discriminator': ('app.schemas.context', 'Discriminator'),
|
'Discriminator': ('app.schemas.context', 'Discriminator'),
|
||||||
'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'),
|
||||||
'DownloadDirectory': ('app.schemas.download', 'DownloadDirectory'),
|
'DownloadDirectory': ('app.schemas.download', 'DownloadDirectory'),
|
||||||
|
'DownloadFileDeletedEventData': ('app.schemas.event', 'DownloadFileDeletedEventData'),
|
||||||
'DownloadHistory': ('app.schemas.history', 'DownloadHistory'),
|
'DownloadHistory': ('app.schemas.history', 'DownloadHistory'),
|
||||||
'DownloadTask': ('app.schemas.workflow', 'DownloadTask'),
|
'DownloadTask': ('app.schemas.workflow', 'DownloadTask'),
|
||||||
'DownloadTaskMedia': ('app.schemas.transfer', 'DownloadTaskMedia'),
|
'DownloadTaskMedia': ('app.schemas.transfer', 'DownloadTaskMedia'),
|
||||||
@@ -94,6 +97,7 @@ SCHEMA_EXPORTS = {
|
|||||||
'DownloaderInfo': ('app.schemas.dashboard', 'DownloaderInfo'),
|
'DownloaderInfo': ('app.schemas.dashboard', 'DownloaderInfo'),
|
||||||
'DownloaderTorrent': ('app.schemas.transfer', 'DownloaderTorrent'),
|
'DownloaderTorrent': ('app.schemas.transfer', 'DownloaderTorrent'),
|
||||||
'DownloadingTorrent': ('app.schemas.transfer', 'DownloadingTorrent'),
|
'DownloadingTorrent': ('app.schemas.transfer', 'DownloadingTorrent'),
|
||||||
|
'EmptyEventData': ('app.schemas.event', 'EmptyEventData'),
|
||||||
'EndpointStats': ('app.schemas.monitoring', 'EndpointStats'),
|
'EndpointStats': ('app.schemas.monitoring', 'EndpointStats'),
|
||||||
'Enum': ('app.schemas.notification', 'Enum'),
|
'Enum': ('app.schemas.notification', 'Enum'),
|
||||||
'EpisodeFormat': ('app.schemas.transfer', 'EpisodeFormat'),
|
'EpisodeFormat': ('app.schemas.transfer', 'EpisodeFormat'),
|
||||||
@@ -103,12 +107,14 @@ SCHEMA_EXPORTS = {
|
|||||||
'ErrorRequest': ('app.schemas.monitoring', 'ErrorRequest'),
|
'ErrorRequest': ('app.schemas.monitoring', 'ErrorRequest'),
|
||||||
'Event': ('app.schemas.event', 'Event'),
|
'Event': ('app.schemas.event', 'Event'),
|
||||||
'ExistMediaInfo': ('app.schemas.mediaserver', 'ExistMediaInfo'),
|
'ExistMediaInfo': ('app.schemas.mediaserver', 'ExistMediaInfo'),
|
||||||
|
'ExtensibleEventData': ('app.schemas.event', 'ExtensibleEventData'),
|
||||||
'Field': ('app.schemas.mcp', 'Field'),
|
'Field': ('app.schemas.mcp', 'Field'),
|
||||||
'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'),
|
||||||
'FilterRuleGroup': ('app.schemas.system', 'FilterRuleGroup'),
|
'FilterRuleGroup': ('app.schemas.system', 'FilterRuleGroup'),
|
||||||
'Generic': ('app.schemas.response', 'Generic'),
|
'Generic': ('app.schemas.response', 'Generic'),
|
||||||
|
'HistoryDeletedEventData': ('app.schemas.event', 'HistoryDeletedEventData'),
|
||||||
'IdData': ('app.schemas.common', 'IdData'),
|
'IdData': ('app.schemas.common', 'IdData'),
|
||||||
'ImmediateException': ('app.schemas.exception', 'ImmediateException'),
|
'ImmediateException': ('app.schemas.exception', 'ImmediateException'),
|
||||||
'IncomingMessage': ('app.schemas.message', 'IncomingMessage'),
|
'IncomingMessage': ('app.schemas.message', 'IncomingMessage'),
|
||||||
@@ -173,6 +179,7 @@ SCHEMA_EXPORTS = {
|
|||||||
'MediaLanguage': ('app.schemas.context', 'MediaLanguage'),
|
'MediaLanguage': ('app.schemas.context', 'MediaLanguage'),
|
||||||
'MediaPerson': ('app.schemas.context', 'MediaPerson'),
|
'MediaPerson': ('app.schemas.context', 'MediaPerson'),
|
||||||
'MediaRecognizeConvertEventData': ('app.schemas.event', 'MediaRecognizeConvertEventData'),
|
'MediaRecognizeConvertEventData': ('app.schemas.event', 'MediaRecognizeConvertEventData'),
|
||||||
|
'MediaRecognizeEventData': ('app.schemas.event', 'MediaRecognizeEventData'),
|
||||||
'MediaReleaseDate': ('app.schemas.context', 'MediaReleaseDate'),
|
'MediaReleaseDate': ('app.schemas.context', 'MediaReleaseDate'),
|
||||||
'MediaSearchResult': ('app.schemas.context', 'MediaSearchResult'),
|
'MediaSearchResult': ('app.schemas.context', 'MediaSearchResult'),
|
||||||
'MediaSearchResults': ('app.schemas.context', 'MediaSearchResults'),
|
'MediaSearchResults': ('app.schemas.context', 'MediaSearchResults'),
|
||||||
@@ -190,6 +197,7 @@ SCHEMA_EXPORTS = {
|
|||||||
'MediaSourceInfo': ('app.schemas.event', 'MediaSourceInfo'),
|
'MediaSourceInfo': ('app.schemas.event', 'MediaSourceInfo'),
|
||||||
'MediaType': ('app.schemas.subscribe', 'MediaType'),
|
'MediaType': ('app.schemas.subscribe', 'MediaType'),
|
||||||
'Message': ('app.schemas.message', 'Message'),
|
'Message': ('app.schemas.message', 'Message'),
|
||||||
|
'MessageActionEventData': ('app.schemas.event', 'MessageActionEventData'),
|
||||||
'MessageClearBefore': ('app.schemas.message', 'MessageClearBefore'),
|
'MessageClearBefore': ('app.schemas.message', 'MessageClearBefore'),
|
||||||
'MessageClearData': ('app.schemas.message', 'MessageClearData'),
|
'MessageClearData': ('app.schemas.message', 'MessageClearData'),
|
||||||
'MessageClearScope': ('app.schemas.message', 'MessageClearScope'),
|
'MessageClearScope': ('app.schemas.message', 'MessageClearScope'),
|
||||||
@@ -197,6 +205,7 @@ 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'),
|
||||||
|
'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'),
|
||||||
'MonitoringConfig': ('app.schemas.monitoring', 'MonitoringConfig'),
|
'MonitoringConfig': ('app.schemas.monitoring', 'MonitoringConfig'),
|
||||||
@@ -205,16 +214,20 @@ SCHEMA_EXPORTS = {
|
|||||||
'MusicArtistInfo': ('app.schemas.music', 'MusicArtistInfo'),
|
'MusicArtistInfo': ('app.schemas.music', 'MusicArtistInfo'),
|
||||||
'MusicEntityType': ('app.schemas.music', 'MusicEntityType'),
|
'MusicEntityType': ('app.schemas.music', 'MusicEntityType'),
|
||||||
'MusicInfo': ('app.schemas.transfer', 'MusicInfo'),
|
'MusicInfo': ('app.schemas.transfer', 'MusicInfo'),
|
||||||
|
'MusicMediaRecognizeEventData': ('app.schemas.event', 'MusicMediaRecognizeEventData'),
|
||||||
'MusicMeta': ('app.schemas.transfer', 'MusicMeta'),
|
'MusicMeta': ('app.schemas.transfer', 'MusicMeta'),
|
||||||
|
'MusicNameRecognizeEventData': ('app.schemas.event', 'MusicNameRecognizeEventData'),
|
||||||
'MusicRecognitionCacheData': ('app.schemas.music', 'MusicRecognitionCacheData'),
|
'MusicRecognitionCacheData': ('app.schemas.music', 'MusicRecognitionCacheData'),
|
||||||
'MusicRecognitionCacheItem': ('app.schemas.music', 'MusicRecognitionCacheItem'),
|
'MusicRecognitionCacheItem': ('app.schemas.music', 'MusicRecognitionCacheItem'),
|
||||||
'MusicRecognizeRequest': ('app.schemas.music', 'MusicRecognizeRequest'),
|
'MusicRecognizeRequest': ('app.schemas.music', 'MusicRecognizeRequest'),
|
||||||
'MusicRelease': ('app.schemas.music', 'MusicRelease'),
|
'MusicRelease': ('app.schemas.music', 'MusicRelease'),
|
||||||
'MusicTargetEntityType': ('app.schemas.transfer', 'MusicTargetEntityType'),
|
'MusicTargetEntityType': ('app.schemas.transfer', 'MusicTargetEntityType'),
|
||||||
'NameData': ('app.schemas.common', 'NameData'),
|
'NameData': ('app.schemas.common', 'NameData'),
|
||||||
|
'NameRecognizeEventData': ('app.schemas.event', 'NameRecognizeEventData'),
|
||||||
'NameValueOption': ('app.schemas.workflow', 'NameValueOption'),
|
'NameValueOption': ('app.schemas.workflow', 'NameValueOption'),
|
||||||
'NetTestTarget': ('app.schemas.system', 'NetTestTarget'),
|
'NetTestTarget': ('app.schemas.system', 'NetTestTarget'),
|
||||||
'NotExistMediaInfo': ('app.schemas.mediaserver', 'NotExistMediaInfo'),
|
'NotExistMediaInfo': ('app.schemas.mediaserver', 'NotExistMediaInfo'),
|
||||||
|
'NoticeMessageEventData': ('app.schemas.event', 'NoticeMessageEventData'),
|
||||||
'NotificationChannel': ('app.schemas.notification', 'NotificationChannel'),
|
'NotificationChannel': ('app.schemas.notification', 'NotificationChannel'),
|
||||||
'NotificationConf': ('app.schemas.system', 'NotificationConf'),
|
'NotificationConf': ('app.schemas.system', 'NotificationConf'),
|
||||||
'NotificationSwitch': ('app.schemas.message', 'NotificationSwitch'),
|
'NotificationSwitch': ('app.schemas.message', 'NotificationSwitch'),
|
||||||
@@ -246,6 +259,7 @@ SCHEMA_EXPORTS = {
|
|||||||
'Path': ('app.schemas.transfer', 'Path'),
|
'Path': ('app.schemas.transfer', 'Path'),
|
||||||
'PerformanceSnapshot': ('app.schemas.monitoring', 'PerformanceSnapshot'),
|
'PerformanceSnapshot': ('app.schemas.monitoring', 'PerformanceSnapshot'),
|
||||||
'Plugin': ('app.schemas.plugin', 'Plugin'),
|
'Plugin': ('app.schemas.plugin', 'Plugin'),
|
||||||
|
'PluginActionEventData': ('app.schemas.event', 'PluginActionEventData'),
|
||||||
'PluginCloneRequest': ('app.schemas.plugin', 'PluginCloneRequest'),
|
'PluginCloneRequest': ('app.schemas.plugin', 'PluginCloneRequest'),
|
||||||
'PluginDashboard': ('app.schemas.plugin', 'PluginDashboard'),
|
'PluginDashboard': ('app.schemas.plugin', 'PluginDashboard'),
|
||||||
'PluginDashboardMetaItem': ('app.schemas.plugin', 'PluginDashboardMetaItem'),
|
'PluginDashboardMetaItem': ('app.schemas.plugin', 'PluginDashboardMetaItem'),
|
||||||
@@ -261,10 +275,12 @@ SCHEMA_EXPORTS = {
|
|||||||
'PluginRatingRequest': ('app.schemas.plugin', 'PluginRatingRequest'),
|
'PluginRatingRequest': ('app.schemas.plugin', 'PluginRatingRequest'),
|
||||||
'PluginReleaseData': ('app.schemas.plugin', 'PluginReleaseData'),
|
'PluginReleaseData': ('app.schemas.plugin', 'PluginReleaseData'),
|
||||||
'PluginReleaseItem': ('app.schemas.plugin', 'PluginReleaseItem'),
|
'PluginReleaseItem': ('app.schemas.plugin', 'PluginReleaseItem'),
|
||||||
|
'PluginReloadEventData': ('app.schemas.event', 'PluginReloadEventData'),
|
||||||
'PluginRemoteInfo': ('app.schemas.plugin', 'PluginRemoteInfo'),
|
'PluginRemoteInfo': ('app.schemas.plugin', 'PluginRemoteInfo'),
|
||||||
'PluginRuntimeStatus': ('app.schemas.plugin', 'PluginRuntimeStatus'),
|
'PluginRuntimeStatus': ('app.schemas.plugin', 'PluginRuntimeStatus'),
|
||||||
'PluginRuntimeSummary': ('app.schemas.plugin', 'PluginRuntimeSummary'),
|
'PluginRuntimeSummary': ('app.schemas.plugin', 'PluginRuntimeSummary'),
|
||||||
'PluginSidebarNavItem': ('app.schemas.plugin', 'PluginSidebarNavItem'),
|
'PluginSidebarNavItem': ('app.schemas.plugin', 'PluginSidebarNavItem'),
|
||||||
|
'PluginTriggeredEventData': ('app.schemas.event', 'PluginTriggeredEventData'),
|
||||||
'PluginWorkflowActionGroup': ('app.schemas.workflow', 'PluginWorkflowActionGroup'),
|
'PluginWorkflowActionGroup': ('app.schemas.workflow', 'PluginWorkflowActionGroup'),
|
||||||
'ProcessInfo': ('app.schemas.dashboard', 'ProcessInfo'),
|
'ProcessInfo': ('app.schemas.dashboard', 'ProcessInfo'),
|
||||||
'ProgressKeyData': ('app.schemas.common', 'ProgressKeyData'),
|
'ProgressKeyData': ('app.schemas.common', 'ProgressKeyData'),
|
||||||
@@ -304,6 +320,7 @@ SCHEMA_EXPORTS = {
|
|||||||
'SiteAuth': ('app.schemas.site', 'SiteAuth'),
|
'SiteAuth': ('app.schemas.site', 'SiteAuth'),
|
||||||
'SiteCategory': ('app.schemas.site', 'SiteCategory'),
|
'SiteCategory': ('app.schemas.site', 'SiteCategory'),
|
||||||
'SiteCookieUpdate': ('app.schemas.site', 'SiteCookieUpdate'),
|
'SiteCookieUpdate': ('app.schemas.site', 'SiteCookieUpdate'),
|
||||||
|
'SiteEventData': ('app.schemas.event', 'SiteEventData'),
|
||||||
'SiteIconData': ('app.schemas.site', 'SiteIconData'),
|
'SiteIconData': ('app.schemas.site', 'SiteIconData'),
|
||||||
'SiteMappingData': ('app.schemas.site', 'SiteMappingData'),
|
'SiteMappingData': ('app.schemas.site', 'SiteMappingData'),
|
||||||
'SiteStatistic': ('app.schemas.site', 'SiteStatistic'),
|
'SiteStatistic': ('app.schemas.site', 'SiteStatistic'),
|
||||||
@@ -327,6 +344,7 @@ SCHEMA_EXPORTS = {
|
|||||||
'SubscrbieInfo': ('app.schemas.subscribe', 'SubscrbieInfo'),
|
'SubscrbieInfo': ('app.schemas.subscribe', 'SubscrbieInfo'),
|
||||||
'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'),
|
||||||
'SubscribeCompletionCheckEventData': ('app.schemas.event', 'SubscribeCompletionCheckEventData'),
|
'SubscribeCompletionCheckEventData': ('app.schemas.event', 'SubscribeCompletionCheckEventData'),
|
||||||
'SubscribeDeletedEventData': ('app.schemas.event', 'SubscribeDeletedEventData'),
|
'SubscribeDeletedEventData': ('app.schemas.event', 'SubscribeDeletedEventData'),
|
||||||
'SubscribeDownloadFileInfo': ('app.schemas.subscribe', 'SubscribeDownloadFileInfo'),
|
'SubscribeDownloadFileInfo': ('app.schemas.subscribe', 'SubscribeDownloadFileInfo'),
|
||||||
@@ -341,6 +359,7 @@ SCHEMA_EXPORTS = {
|
|||||||
'SubtitleDownloadData': ('app.schemas.download', 'SubtitleDownloadData'),
|
'SubtitleDownloadData': ('app.schemas.download', 'SubtitleDownloadData'),
|
||||||
'SubtitleInfo': ('app.schemas.search', 'SubtitleInfo'),
|
'SubtitleInfo': ('app.schemas.search', 'SubtitleInfo'),
|
||||||
'SystemEnvironmentUpdateData': ('app.schemas.system', 'SystemEnvironmentUpdateData'),
|
'SystemEnvironmentUpdateData': ('app.schemas.system', 'SystemEnvironmentUpdateData'),
|
||||||
|
'SystemErrorEventData': ('app.schemas.event', 'SystemErrorEventData'),
|
||||||
'SystemModuleInfo': ('app.schemas.system', 'SystemModuleInfo'),
|
'SystemModuleInfo': ('app.schemas.system', 'SystemModuleInfo'),
|
||||||
'SystemModuleListData': ('app.schemas.system', 'SystemModuleListData'),
|
'SystemModuleListData': ('app.schemas.system', 'SystemModuleListData'),
|
||||||
'TMDbException': ('app.schemas.exception', 'TMDbException'),
|
'TMDbException': ('app.schemas.exception', 'TMDbException'),
|
||||||
@@ -386,6 +405,7 @@ SCHEMA_EXPORTS = {
|
|||||||
'UserInDB': ('app.schemas.user', 'UserInDB'),
|
'UserInDB': ('app.schemas.user', 'UserInDB'),
|
||||||
'UserInDBBase': ('app.schemas.user', 'UserInDBBase'),
|
'UserInDBBase': ('app.schemas.user', 'UserInDBBase'),
|
||||||
'UserMessage': ('app.schemas.agent', 'UserMessage'),
|
'UserMessage': ('app.schemas.agent', 'UserMessage'),
|
||||||
|
'UserMessageEventData': ('app.schemas.event', 'UserMessageEventData'),
|
||||||
'UserPermissions': ('app.schemas.user', 'UserPermissions'),
|
'UserPermissions': ('app.schemas.user', 'UserPermissions'),
|
||||||
'UserUpdate': ('app.schemas.user', 'UserUpdate'),
|
'UserUpdate': ('app.schemas.user', 'UserUpdate'),
|
||||||
'ValidationIssue': ('app.schemas.response', 'ValidationIssue'),
|
'ValidationIssue': ('app.schemas.response', 'ValidationIssue'),
|
||||||
@@ -397,6 +417,7 @@ SCHEMA_EXPORTS = {
|
|||||||
'WechatClawBotKnownTarget': ('app.schemas.notification', 'WechatClawBotKnownTarget'),
|
'WechatClawBotKnownTarget': ('app.schemas.notification', 'WechatClawBotKnownTarget'),
|
||||||
'Workflow': ('app.schemas.workflow', 'Workflow'),
|
'Workflow': ('app.schemas.workflow', 'Workflow'),
|
||||||
'WorkflowActionDefinition': ('app.schemas.workflow', 'WorkflowActionDefinition'),
|
'WorkflowActionDefinition': ('app.schemas.workflow', 'WorkflowActionDefinition'),
|
||||||
|
'WorkflowExecuteEventData': ('app.schemas.event', 'WorkflowExecuteEventData'),
|
||||||
'WorkflowExecutionConfig': ('app.schemas.workflow', 'WorkflowExecutionConfig'),
|
'WorkflowExecutionConfig': ('app.schemas.workflow', 'WorkflowExecutionConfig'),
|
||||||
'WorkflowExecutionState': ('app.schemas.workflow', 'WorkflowExecutionState'),
|
'WorkflowExecutionState': ('app.schemas.workflow', 'WorkflowExecutionState'),
|
||||||
'WorkflowNodeState': ('app.schemas.workflow', 'WorkflowNodeState'),
|
'WorkflowNodeState': ('app.schemas.workflow', 'WorkflowNodeState'),
|
||||||
@@ -415,7 +436,7 @@ SCHEMA_EXPORTS = {
|
|||||||
SCHEMA_CONFLICTS = {
|
SCHEMA_CONFLICTS = {
|
||||||
'Any': ['app.schemas.common', 'app.schemas.context', 'app.schemas.event', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.response', 'app.schemas.subscribe', 'app.schemas.system', 'app.schemas.workflow'],
|
'Any': ['app.schemas.common', 'app.schemas.context', 'app.schemas.event', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.response', 'app.schemas.subscribe', 'app.schemas.system', 'app.schemas.workflow'],
|
||||||
'BaseModel': ['app.schemas.agent', 'app.schemas.cache', 'app.schemas.category', 'app.schemas.common', 'app.schemas.context', 'app.schemas.dashboard', 'app.schemas.download', 'app.schemas.event', 'app.schemas.file', 'app.schemas.history', 'app.schemas.llm', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.mfa', 'app.schemas.music', 'app.schemas.monitoring', 'app.schemas.notification', 'app.schemas.plugin', 'app.schemas.response', 'app.schemas.rule', 'app.schemas.search', 'app.schemas.storage', 'app.schemas.openai', 'app.schemas.servarr', 'app.schemas.servcookie', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.system', 'app.schemas.tmdb', 'app.schemas.token', 'app.schemas.transfer', 'app.schemas.user', 'app.schemas.workflow', 'app.schemas.mcp'],
|
'BaseModel': ['app.schemas.agent', 'app.schemas.cache', 'app.schemas.category', 'app.schemas.common', 'app.schemas.context', 'app.schemas.dashboard', 'app.schemas.download', 'app.schemas.event', 'app.schemas.file', 'app.schemas.history', 'app.schemas.llm', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.mfa', 'app.schemas.music', 'app.schemas.monitoring', 'app.schemas.notification', 'app.schemas.plugin', 'app.schemas.response', 'app.schemas.rule', 'app.schemas.search', 'app.schemas.storage', 'app.schemas.openai', 'app.schemas.servarr', 'app.schemas.servcookie', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.system', 'app.schemas.tmdb', 'app.schemas.token', 'app.schemas.transfer', 'app.schemas.user', 'app.schemas.workflow', 'app.schemas.mcp'],
|
||||||
'ConfigDict': ['app.schemas.agent', 'app.schemas.category', 'app.schemas.history', 'app.schemas.mediaserver', 'app.schemas.response', 'app.schemas.openai', 'app.schemas.servcookie', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.user', 'app.schemas.workflow'],
|
'ConfigDict': ['app.schemas.agent', 'app.schemas.category', 'app.schemas.event', 'app.schemas.history', 'app.schemas.mediaserver', 'app.schemas.response', 'app.schemas.openai', 'app.schemas.servcookie', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.user', 'app.schemas.workflow'],
|
||||||
'Context': ['app.schemas.context', 'app.schemas.workflow'],
|
'Context': ['app.schemas.context', 'app.schemas.workflow'],
|
||||||
'Dict': ['app.schemas.category', 'app.schemas.common', 'app.schemas.context', 'app.schemas.event', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.notification', 'app.schemas.plugin', 'app.schemas.openai', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.mcp'],
|
'Dict': ['app.schemas.category', 'app.schemas.common', 'app.schemas.context', 'app.schemas.event', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.notification', 'app.schemas.plugin', 'app.schemas.openai', 'app.schemas.site', 'app.schemas.subscribe', 'app.schemas.mcp'],
|
||||||
'DownloadTask': ['app.schemas.download', 'app.schemas.workflow'],
|
'DownloadTask': ['app.schemas.download', 'app.schemas.workflow'],
|
||||||
|
|||||||
@@ -621,6 +621,9 @@ ModuleMethodSpec(
|
|||||||
`ModuleCapability` Protocol 为宿主和新插件提供静态声明入口,但不替换字符串 dispatcher ABI。
|
`ModuleCapability` Protocol 为宿主和新插件提供静态声明入口,但不替换字符串 dispatcher ABI。
|
||||||
- 22 个显式方法进一步登记宿主真实传入的 required parameter 名称,覆盖识别、搜索、媒体服务器、存储、
|
- 22 个显式方法进一步登记宿主真实传入的 required parameter 名称,覆盖识别、搜索、媒体服务器、存储、
|
||||||
消息收尾、命令注册和 webhook;dispatcher 仍只输出诊断 warning,不阻断缺少参数的旧插件或未知自定义方法。
|
消息收尾、命令注册和 webhook;dispatcher 仍只输出诊断 warning,不阻断缺少参数的旧插件或未知自定义方法。
|
||||||
|
- 契约清单现覆盖静态扫描到的 211 个宿主字符串调用,并保留一个暂未被宿主调用的 `send_message` 公开能力,
|
||||||
|
共 212 个显式 V2 spec。原先仅按 prefix 分类或落入默认 legacy 的宿主方法均获得稳定 family、输入合同、
|
||||||
|
结果合同、执行、超时和错误语义;未知第三方自定义方法仍走开放 legacy fallback,不拒绝加载或执行。
|
||||||
|
|
||||||
#### ARCH-241:Event Contract Registry
|
#### ARCH-241:Event Contract Registry
|
||||||
|
|
||||||
@@ -658,6 +661,9 @@ ModuleMethodSpec(
|
|||||||
- 订阅变更、下载添加、整理成功/失败等用户副作用标记为 `durable_required`,只表达完成语义要求;
|
- 订阅变更、下载添加、整理成功/失败等用户副作用标记为 `durable_required`,只表达完成语义要求;
|
||||||
在 ARCH-251 pilot 完成前不虚构当前已具备持久投递。SystemError 仍沿用既有递归保护和异常通知路径。
|
在 ARCH-251 pilot 完成前不虚构当前已具备持久投递。SystemError 仍沿用既有递归保护和异常通知路径。
|
||||||
- runtime contract baseline 新增稳定 `event_specs`,后续 enum 新增必须同步登记,且不比较源码行号。
|
- runtime contract baseline 新增稳定 `event_specs`,后续 enum 新增必须同步登记,且不比较源码行号。
|
||||||
|
- 53 个事件现已全部绑定 typed payload,原有 28 个 `legacy_dict` 项归零。插件动作/触发等开放事件使用
|
||||||
|
“公共字段类型化 + `extra=allow`”模型,Webhook 与 Workflow execution 复用既有 DTO;验证仍只诊断并投递
|
||||||
|
同一个原始 dict/model,因此插件字段、对象引用和链式原地修改语义未改变。
|
||||||
|
|
||||||
#### ARCH-242:Module/Integration 质量清单
|
#### ARCH-242:Module/Integration 质量清单
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -13,8 +13,8 @@
|
|||||||
"runtime_to_db": [],
|
"runtime_to_db": [],
|
||||||
"workflow_to_db": []
|
"workflow_to_db": []
|
||||||
},
|
},
|
||||||
"edge_count": 6367,
|
"edge_count": 6369,
|
||||||
"edge_sha256": "62c413857cd12dbaf4d31e4fad4c6b6c80f7dc747d0d5a74ecb61c44b4996822",
|
"edge_sha256": "62638f6e334e7deb05902ca1e50cdcd03245a85c207dd2b4efaa03306cd28187",
|
||||||
"edges": [
|
"edges": [
|
||||||
"app -> app.runtime",
|
"app -> app.runtime",
|
||||||
"app -> app.runtime.compat",
|
"app -> app.runtime.compat",
|
||||||
@@ -5436,7 +5436,9 @@
|
|||||||
"app.runtime.event.binding -> app.runtime.log",
|
"app.runtime.event.binding -> app.runtime.log",
|
||||||
"app.runtime.event.contracts -> app.schemas",
|
"app.runtime.event.contracts -> app.schemas",
|
||||||
"app.runtime.event.contracts -> app.schemas.event",
|
"app.runtime.event.contracts -> app.schemas.event",
|
||||||
|
"app.runtime.event.contracts -> app.schemas.mediaserver",
|
||||||
"app.runtime.event.contracts -> app.schemas.types",
|
"app.runtime.event.contracts -> app.schemas.types",
|
||||||
|
"app.runtime.event.contracts -> app.schemas.workflow",
|
||||||
"app.runtime.event.dispatch -> app.runtime",
|
"app.runtime.event.dispatch -> app.runtime",
|
||||||
"app.runtime.event.dispatch -> app.runtime.correlation",
|
"app.runtime.event.dispatch -> app.runtime.correlation",
|
||||||
"app.runtime.event.dispatch -> app.runtime.event",
|
"app.runtime.event.dispatch -> app.runtime.event",
|
||||||
|
|||||||
+2907
-57
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@ from app.schemas.types import ChainEventType, EventType, MediaType
|
|||||||
|
|
||||||
|
|
||||||
def test_every_event_enum_has_complete_contract() -> None:
|
def test_every_event_enum_has_complete_contract() -> None:
|
||||||
"""53 个广播/链式事件必须全部登记且 legacy 项必须解释原因。"""
|
"""53 个广播/链式事件必须全部登记并绑定 typed payload。"""
|
||||||
expected = {*EventType, *ChainEventType}
|
expected = {*EventType, *ChainEventType}
|
||||||
|
|
||||||
assert set(EVENT_CONTRACTS) == expected
|
assert set(EVENT_CONTRACTS) == expected
|
||||||
@@ -26,8 +26,24 @@ def test_every_event_enum_has_complete_contract() -> None:
|
|||||||
for contract in EVENT_CONTRACTS.values():
|
for contract in EVENT_CONTRACTS.values():
|
||||||
assert contract.mode in {"broadcast", "chain"}
|
assert contract.mode in {"broadcast", "chain"}
|
||||||
assert contract.payload_contract
|
assert contract.payload_contract
|
||||||
if contract.payload_contract == "legacy_dict":
|
assert contract.payload_model is not None
|
||||||
assert contract.legacy_reason
|
assert contract.payload_contract != "legacy_dict"
|
||||||
|
assert contract.legacy_reason is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_extensible_plugin_payload_accepts_custom_fields_without_shape_change() -> None:
|
||||||
|
"""插件动作 contract 只校验公共字段,插件自定义字段和原始 dict 均保持不变。"""
|
||||||
|
payload = {
|
||||||
|
"plugin_id": "DemoPlugin",
|
||||||
|
"action": "refresh",
|
||||||
|
"plugin_owned_field": {"value": 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert validate_event_payload(EventType.PluginAction, payload) == ()
|
||||||
|
event = Event(EventType.PluginAction, payload)
|
||||||
|
|
||||||
|
assert event.event_data is payload
|
||||||
|
assert event.event_data["plugin_owned_field"] == {"value": 1}
|
||||||
|
|
||||||
|
|
||||||
def test_typed_payload_is_validated_without_changing_public_shape() -> None:
|
def test_typed_payload_is_validated_without_changing_public_shape() -> None:
|
||||||
|
|||||||
@@ -19,15 +19,19 @@ RUNTIME_BASELINE = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_all_scanned_module_methods_resolve_a_contract() -> None:
|
def test_all_scanned_host_module_methods_have_explicit_v2_contracts() -> None:
|
||||||
"""架构快照中的所有字符串方法都必须能解析到稳定聚合规则。"""
|
"""架构快照中的全部宿主字符串方法都必须显式登记 V2 契约。"""
|
||||||
payload = json.loads(RUNTIME_BASELINE.read_text(encoding="utf-8"))
|
payload = json.loads(RUNTIME_BASELINE.read_text(encoding="utf-8"))
|
||||||
methods = payload["run_module"]["methods"]
|
methods = set(payload["run_module"]["methods"])
|
||||||
|
contracts = list_explicit_module_contracts()
|
||||||
|
|
||||||
assert methods
|
assert methods
|
||||||
|
assert methods <= contracts.keys()
|
||||||
for method in methods:
|
for method in methods:
|
||||||
contract = get_module_method_contract(method)
|
contract = contracts[method]
|
||||||
assert isinstance(contract.aggregation, ModuleResultAggregation)
|
assert isinstance(contract.aggregation, ModuleResultAggregation)
|
||||||
|
assert contract.input_contract != "legacy_args"
|
||||||
|
assert contract.result_contract != "Any"
|
||||||
assert contract.plugin_short_circuit is True
|
assert contract.plugin_short_circuit is True
|
||||||
|
|
||||||
|
|
||||||
@@ -59,11 +63,11 @@ def test_unknown_plugin_method_keeps_legacy_compatibility() -> None:
|
|||||||
assert contract.supports_async is True
|
assert contract.supports_async is True
|
||||||
|
|
||||||
|
|
||||||
def test_contract_v2_freezes_at_least_twenty_high_value_methods() -> None:
|
def test_contract_v2_freezes_every_observed_host_method() -> None:
|
||||||
"""首批能力必须具备可生成文档和诊断的完整 V2 字段。"""
|
"""全部已观察宿主能力必须具备可生成文档和诊断的完整 V2 字段。"""
|
||||||
contracts = list_explicit_module_contracts()
|
contracts = list_explicit_module_contracts()
|
||||||
|
|
||||||
assert len(contracts) >= 20
|
assert len(contracts) >= 211
|
||||||
for contract in contracts.values():
|
for contract in contracts.values():
|
||||||
assert contract.version == 1
|
assert contract.version == 1
|
||||||
assert contract.input_contract != "legacy_args"
|
assert contract.input_contract != "legacy_args"
|
||||||
|
|||||||
Reference in New Issue
Block a user