use serde::Serialize;
use crate::RpcRequestLifecycleRule;
#[derive(Debug, Clone, Copy)]
pub struct RpcMethodCatalogOptions {
pub runtime_available: bool,
pub mob_enabled: bool,
pub mcp_enabled: bool,
pub comms_enabled: bool,
pub blob_enabled: bool,
pub session_events_enabled: bool,
pub session_streams_enabled: bool,
pub schedule_enabled: bool,
pub workgraph_enabled: bool,
pub skills_enabled: bool,
}
impl RpcMethodCatalogOptions {
pub const fn documented_surface() -> Self {
Self {
runtime_available: true,
mob_enabled: true,
mcp_enabled: true,
comms_enabled: true,
blob_enabled: true,
session_events_enabled: true,
session_streams_enabled: true,
schedule_enabled: true,
workgraph_enabled: true,
skills_enabled: true,
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct RpcMethodDescriptor {
pub name: &'static str,
pub description: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub params_type: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_type: Option<&'static str>,
#[serde(skip_serializing)]
pub request_lifecycle: RpcRequestLifecycleRule,
}
impl RpcMethodDescriptor {
const fn basic(name: &'static str, description: &'static str) -> Self {
Self {
name,
description,
params_type: None,
result_type: None,
request_lifecycle: RpcRequestLifecycleRule::INLINE_OBSERVATION,
}
}
const fn typed(
name: &'static str,
description: &'static str,
params_type: &'static str,
result_type: &'static str,
) -> Self {
Self {
name,
description,
params_type: Some(params_type),
result_type: Some(result_type),
request_lifecycle: RpcRequestLifecycleRule::INLINE_OBSERVATION,
}
}
const fn with_request_lifecycle(mut self, request_lifecycle: RpcRequestLifecycleRule) -> Self {
self.request_lifecycle = request_lifecycle;
self
}
const fn result_only(
name: &'static str,
description: &'static str,
result_type: &'static str,
) -> Self {
Self {
name,
description,
params_type: None,
result_type: Some(result_type),
request_lifecycle: RpcRequestLifecycleRule::INLINE_OBSERVATION,
}
}
}
pub fn rpc_method_catalog(options: RpcMethodCatalogOptions) -> Vec<RpcMethodDescriptor> {
let mut methods = vec![
RpcMethodDescriptor::basic("initialize", "Handshake, returns server capabilities"),
RpcMethodDescriptor::typed(
"session/create",
"Create session + run first turn",
"CreateSessionParams",
"WireRunResult | DeferredCreateResult",
)
.with_request_lifecycle(RpcRequestLifecycleRule::SessionCreateInitialTurn),
RpcMethodDescriptor::typed(
"session/list",
"List active sessions",
"ListSessionsParams",
"ListSessionsResult",
),
RpcMethodDescriptor::typed(
"session/read",
"Get session state",
"ReadSessionParams",
"WireSessionInfo",
),
RpcMethodDescriptor::typed(
"session/history",
"Get full session history",
"ReadSessionHistoryParams",
"WireSessionHistory",
),
RpcMethodDescriptor::typed(
"session/fork_at",
"Fork an idle session at a transcript message index",
"ForkSessionAtParams",
"SessionForkResult",
),
RpcMethodDescriptor::typed(
"session/fork_replace",
"Fork an idle session and apply a typed transcript replacement",
"ForkSessionReplaceParams",
"SessionForkResult",
),
RpcMethodDescriptor::typed(
"session/archive",
"Remove session",
"ArchiveSessionParams",
"Value",
),
RpcMethodDescriptor::typed(
"turn/start",
"Start a new turn on existing session",
"StartTurnParams",
"WireRunResult",
)
.with_request_lifecycle(RpcRequestLifecycleRule::LONG_RUNNING_PUBLISH_ON_SUCCESS),
RpcMethodDescriptor::typed(
"turn/interrupt",
"Cancel in-flight turn",
"InterruptParams",
"Value",
),
RpcMethodDescriptor::basic("config/get", "Read config"),
RpcMethodDescriptor::basic("config/set", "Replace config"),
RpcMethodDescriptor::basic("config/patch", "Merge-patch config"),
RpcMethodDescriptor::basic("capabilities/get", "Get runtime capabilities"),
RpcMethodDescriptor::result_only(
"runtime/host_info",
"Get read-only runtime host information",
"RuntimeHostInfo",
),
RpcMethodDescriptor::result_only(
"runtime/capabilities",
"Get runtime host capability flags",
"RuntimeHostCapabilities",
),
RpcMethodDescriptor::result_only(
"runtime/health",
"Get runtime host health",
"RuntimeHostHealth",
),
RpcMethodDescriptor::typed(
"approval/request",
"Create a durable approval request",
"ApprovalRequestParams",
"ApprovalRecord",
),
RpcMethodDescriptor::typed(
"approval/list",
"List durable approval requests",
"ApprovalListParams",
"ApprovalListResult",
),
RpcMethodDescriptor::typed(
"approval/get",
"Get one durable approval request",
"ApprovalGetParams",
"ApprovalRecord",
),
RpcMethodDescriptor::typed(
"approval/decide",
"Record a durable approval decision",
"ApprovalDecideParams",
"ApprovalRecord",
),
RpcMethodDescriptor::result_only(
"models/catalog",
"Get the effective model catalog (built-in plus config-backed entries)",
"ModelsCatalogResponse",
),
RpcMethodDescriptor::typed(
"auth/profile/list",
"List configured auth profiles",
"RealmIdParams",
"WireAuthProfilesList",
),
RpcMethodDescriptor::typed(
"auth/profile/get",
"Get one auth profile by id",
"BindingIdParams",
"WireAuthProfileDetail",
),
RpcMethodDescriptor::typed(
"auth/profile/create",
"Create an auth profile",
"CreateProfileParams",
"WireAuthProfileCreated",
),
RpcMethodDescriptor::typed(
"auth/profile/delete",
"Delete an auth profile",
"BindingIdParams",
"WireAuthProfileCleared",
),
RpcMethodDescriptor::typed(
"auth/login/start",
"Begin an OAuth login; returns authorize URL, state, PKCE verifier",
"LoginStartParams",
"WireLoginStart",
),
RpcMethodDescriptor::typed(
"auth/login/complete",
"Finish an OAuth login by exchanging an authorization code",
"LoginCompleteParams",
"WireLoginReady",
),
RpcMethodDescriptor::typed(
"auth/login/device_start",
"Begin a device-code OAuth login; returns user-code + verification URL",
"DeviceStartParams",
"WireDeviceStart",
),
RpcMethodDescriptor::typed(
"auth/login/device_complete",
"Single-poll completion for a device-code OAuth login; returns pending / slow_down / access_denied / expired / ready",
"DeviceCompleteParams",
"WireDeviceCompleteResult",
),
RpcMethodDescriptor::typed(
"auth/login/provision_api_key",
"Console-OAuth → API key provisioning (Anthropic oauth_to_api_key): POST access_token to create_api_key endpoint, persist returned api_key",
"ProvisionApiKeyParams",
"WireProvisionApiKeyResult",
),
RpcMethodDescriptor::typed(
"auth/status/get",
"Get auth status for a profile",
"BindingIdParams",
"WireAuthStatusDetail",
),
RpcMethodDescriptor::typed(
"auth/logout",
"Revoke + remove persisted credentials",
"BindingIdParams",
"WireAuthProfileCleared",
),
RpcMethodDescriptor::result_only(
"realm/list",
"List realms with binding summaries",
"WireRealmList",
),
RpcMethodDescriptor::typed(
"realm/get",
"Get one realm's full connection set",
"RealmIdParams",
"WireRealmConnectionSet",
),
];
if options.runtime_available {
methods.push(
RpcMethodDescriptor::typed(
"help/ask",
"Ask Meerkat usage help with the embedded platform skill",
"HelpRequest",
"HelpResponse",
)
.with_request_lifecycle(RpcRequestLifecycleRule::LONG_RUNNING_PUBLISH_ON_SUCCESS),
);
}
if options.blob_enabled {
methods.extend([
RpcMethodDescriptor::typed(
"blob/get",
"Fetch raw blob payload metadata and bytes by blob id",
"BlobGetParams",
"BlobPayload",
),
RpcMethodDescriptor::typed(
"artifact/list",
"List stable artifact records",
"ArtifactListParams",
"ArtifactListResult",
),
RpcMethodDescriptor::typed(
"artifact/get",
"Get one stable artifact record",
"ArtifactIdParams",
"ArtifactRecord",
),
RpcMethodDescriptor::typed(
"artifact/download",
"Download blob-backed artifact payload bytes",
"ArtifactDownloadParams",
"ArtifactDownloadResult",
),
]);
}
if options.session_events_enabled {
methods.extend([
RpcMethodDescriptor::typed(
"session/external_event",
"Queue a runtime-backed external event",
"SessionExternalEventEnvelope",
"RuntimeAcceptResult",
),
RpcMethodDescriptor::typed(
"session/peer_response_terminal",
"Admit a correlated terminal peer response through the typed runtime ingress",
"SessionPeerResponseTerminalParams",
"RuntimeAcceptResult",
),
RpcMethodDescriptor::typed(
"session/inject_context",
"Stage runtime system context for application at the next LLM boundary",
"InjectSystemContextParams",
"InjectSystemContextResult",
),
RpcMethodDescriptor::typed(
"events/latest_cursor",
"Read the latest replay cursor for an event source",
"EventsLatestCursorParams",
"EventsLatestCursorResult",
),
RpcMethodDescriptor::typed(
"events/list_since",
"List replayable events after a cursor",
"EventsListSinceParams",
"EventsListSinceResult",
),
RpcMethodDescriptor::typed(
"events/snapshot",
"Read a point-in-time snapshot anchor for an event source",
"EventsSnapshotParams",
"EventsSnapshotResult",
),
]);
}
if options.session_streams_enabled {
methods.extend([
RpcMethodDescriptor::typed(
"session/stream_open",
"Open a session event stream",
"SessionStreamOpenParams",
"SessionStreamOpenResult",
),
RpcMethodDescriptor::typed(
"session/stream_close",
"Close a session event stream",
"SessionStreamCloseParams",
"SessionStreamCloseResult",
),
]);
}
if options.schedule_enabled {
methods.extend([
RpcMethodDescriptor::typed(
"schedule/create",
"Create a schedule",
"CreateScheduleRequest",
"Schedule",
),
RpcMethodDescriptor::typed(
"schedule/get",
"Get one schedule",
"ScheduleIdParams",
"Schedule",
),
RpcMethodDescriptor::typed(
"schedule/list",
"List schedules",
"ListSchedulesParams",
"ScheduleListResult",
),
RpcMethodDescriptor::typed(
"schedule/update",
"Update a schedule",
"UpdateScheduleParams",
"Schedule",
),
RpcMethodDescriptor::typed(
"schedule/pause",
"Pause a schedule",
"ScheduleIdParams",
"Schedule",
),
RpcMethodDescriptor::typed(
"schedule/resume",
"Resume a schedule",
"ScheduleIdParams",
"Schedule",
),
RpcMethodDescriptor::typed(
"schedule/delete",
"Delete a schedule",
"ScheduleIdParams",
"Schedule",
),
RpcMethodDescriptor::typed(
"schedule/occurrences",
"List schedule occurrences",
"ScheduleOccurrencesParams",
"ScheduleOccurrencesResult",
),
RpcMethodDescriptor::result_only(
"schedule/tools",
"List schedule transport tools",
"ScheduleToolsResult",
),
RpcMethodDescriptor::typed(
"schedule/call",
"Call a schedule transport tool",
"ScheduleToolCallParams",
"Value",
),
]);
}
if options.workgraph_enabled {
methods.extend([
RpcMethodDescriptor::typed(
"workgraph/get",
"Get one WorkGraph item",
"WorkGraphIdParams",
"WorkItem",
),
RpcMethodDescriptor::typed(
"workgraph/list",
"List WorkGraph items",
"WorkItemFilter",
"WorkItemListResult",
),
RpcMethodDescriptor::typed(
"workgraph/ready",
"List ready WorkGraph items",
"ReadyWorkFilter",
"WorkItemListResult",
),
RpcMethodDescriptor::typed(
"workgraph/snapshot",
"Read a WorkGraph snapshot",
"WorkGraphSnapshotFilter",
"WorkGraphSnapshot",
),
RpcMethodDescriptor::typed(
"workgraph/events",
"List WorkGraph events",
"WorkGraphEventFilter",
"WorkGraphEventsResult",
),
]);
}
if options.skills_enabled {
methods.extend([RpcMethodDescriptor::basic(
"skills/list",
"List available skills",
)]);
}
if options.runtime_available {
methods.extend([
RpcMethodDescriptor::typed(
"live/open",
"Open a live audio/text channel for a session",
"LiveOpenParams",
"LiveOpenResult",
),
RpcMethodDescriptor::typed(
"live/webrtc/answer",
"Answer a browser WebRTC offer for an already-open live channel",
"LiveWebrtcAnswerParams",
"LiveWebrtcAnswerResult",
),
RpcMethodDescriptor::typed(
"live/status",
"Get the status of a live channel",
"LiveChannelParams",
"LiveStatusResult",
),
RpcMethodDescriptor::typed(
"live/close",
"Close a live channel",
"LiveChannelParams",
"Value",
),
RpcMethodDescriptor::typed(
"live/send_input",
"Send an input chunk (audio/text) to a live channel",
"LiveSendInputParams",
"Value",
),
RpcMethodDescriptor::typed(
"live/commit_input",
"Commit any buffered input on a live channel",
"LiveCommitInputParams",
"Value",
),
RpcMethodDescriptor::typed(
"live/interrupt",
"Interrupt the in-progress assistant turn on a live channel",
"LiveChannelParams",
"Value",
),
RpcMethodDescriptor::typed(
"live/truncate",
"Truncate the assistant output on a live channel at the client-tracked playback cursor",
"LiveTruncateParams",
"Value",
),
RpcMethodDescriptor::typed(
"live/refresh",
"Apply mutable session config (instructions/tools/audio) to an open live channel via a single session.update; does NOT replay history. Identity swaps (model/provider) require close + reopen.",
"LiveChannelParams",
"LiveRefreshResult",
),
]);
}
if options.mob_enabled {
methods.extend([
RpcMethodDescriptor::typed(
"mob/create",
"Create a mob from a definition",
"MobCreateParams",
"MobCreateResult",
),
RpcMethodDescriptor::result_only("mob/list", "List active mobs", "MobListResult"),
RpcMethodDescriptor::typed(
"mob/status",
"Get mob lifecycle status",
"MobIdParams",
"MobStatusResult",
),
RpcMethodDescriptor::typed(
"mob/lifecycle",
"Apply a mob lifecycle action",
"MobLifecycleParams",
"MobLifecycleResult",
),
RpcMethodDescriptor::typed(
"mob/spawn",
"Spawn a new mob member",
"MobSpawnParams",
"MobSpawnResult",
),
RpcMethodDescriptor::typed(
"mob/spawn_many",
"Spawn multiple new mob members",
"MobSpawnManyParams",
"MobSpawnManyResult",
),
RpcMethodDescriptor::typed(
"mob/ensure_member",
"Declarative: spawn if absent, otherwise return the existing roster entry",
"MobEnsureMemberParams",
"MobEnsureMemberResult",
),
RpcMethodDescriptor::typed(
"mob/reconcile",
"Declarative: drive the roster toward a desired set of specs",
"MobReconcileParams",
"MobReconcileResult",
),
RpcMethodDescriptor::typed(
"mob/list_members_matching",
"List roster members that match a filter",
"MobListMembersMatchingParams",
"MobListMembersMatchingResult",
),
RpcMethodDescriptor::typed(
"mob/retire",
"Retire a mob member",
"MobMemberParams",
"MobRetireResult",
),
RpcMethodDescriptor::typed(
"mob/respawn",
"Respawn a mob member with topology restore",
"MobRespawnParams",
"MobRespawnResult",
),
RpcMethodDescriptor::typed(
"mob/wire",
"Wire a local mob member to a local or external peer",
"MobWireParams",
"MobWireResult",
),
RpcMethodDescriptor::typed(
"mob/wire_members_batch",
"Wire multiple local mob member edges",
"MobWireMembersBatchParams",
"MobWireMembersBatchResult",
),
RpcMethodDescriptor::typed(
"mob/unwire",
"Unwire a local mob member from a local or external peer",
"MobUnwireParams",
"MobUnwireResult",
),
RpcMethodDescriptor::typed(
"mob/members",
"List members in a mob roster",
"MobIdParams",
"MobMembersResult",
),
RpcMethodDescriptor::typed(
"mob/events",
"Read mob event history",
"MobEventsParams",
"MobEventsResult",
),
RpcMethodDescriptor::typed(
"mob/member_send",
"Deliver ordinary content to a specific mob member via the host control plane",
"MobMemberSendParams",
"MobMemberSendResult",
),
RpcMethodDescriptor::typed(
"mob/ingress_interaction",
"Ensure an ingress member, then deliver user input with a replay cursor receipt",
"MobIngressInteractionParams",
"MobIngressInteractionResult",
),
RpcMethodDescriptor::typed(
"mob/append_system_context",
"Append system context for a mob member",
"MobAppendSystemContextParams",
"MobAppendSystemContextResult",
),
RpcMethodDescriptor::typed(
"mob/flows",
"List flows defined for a mob",
"MobIdParams",
"MobFlowsResult",
),
RpcMethodDescriptor::typed(
"mob/flow_run",
"Start a mob flow run",
"MobFlowRunParams",
"MobFlowRunResult",
),
RpcMethodDescriptor::typed(
"mob/flow_status",
"Get status for a mob flow run",
"MobFlowStatusParams",
"MobFlowStatusResult",
),
RpcMethodDescriptor::typed(
"mob/flow_cancel",
"Cancel a mob flow run",
"MobFlowCancelParams",
"MobFlowCancelResult",
),
RpcMethodDescriptor::typed(
"mob/spawn_helper",
"Spawn a helper member and wait for completion",
"MobSpawnHelperParams",
"MobHelperResult",
),
RpcMethodDescriptor::typed(
"mob/fork_helper",
"Fork a helper member and wait for completion",
"MobForkHelperParams",
"MobHelperResult",
),
RpcMethodDescriptor::typed(
"mob/force_cancel",
"Force-cancel a mob member",
"MobMemberParams",
"MobForceCancelResult",
),
RpcMethodDescriptor::typed(
"mob/turn_start",
"Start a turn on a mob member by identity",
"MobTurnStartParams",
"WireRunResult",
)
.with_request_lifecycle(RpcRequestLifecycleRule::LONG_RUNNING_PUBLISH_ON_SUCCESS),
RpcMethodDescriptor::typed(
"mob/member_status",
"Get live status for a mob member",
"MobMemberParams",
"MobMemberStatusResult",
),
RpcMethodDescriptor::typed(
"mob/snapshot",
"Point-in-time aggregate of mob status plus member list",
"MobIdParams",
"MobSnapshotResult",
),
RpcMethodDescriptor::typed(
"mob/destroy",
"Destroy a mob and surface the structured MobDestroyReport",
"MobIdParams",
"MobDestroyResult",
),
RpcMethodDescriptor::typed(
"mob/rotate_supervisor",
"Rotate the supervisor bridge for all members of a mob",
"MobIdParams",
"MobRotateSupervisorResult",
),
RpcMethodDescriptor::typed(
"mob/submit_work",
"Submit a unit of work to a mob member through the work lane",
"MobSubmitWorkParams",
"MobSubmitWorkResult",
),
RpcMethodDescriptor::typed(
"mob/cancel_work",
"Cancel a previously submitted unit of work",
"MobCancelWorkParams",
"MobCancelWorkResult",
),
RpcMethodDescriptor::typed(
"mob/cancel_all_work",
"Cancel all in-flight work for a mob member",
"MobCancelAllWorkParams",
"MobCancelAllWorkResult",
),
RpcMethodDescriptor::typed(
"mob/wait_kickoff",
"Wait for kickoff completion for a member",
"MobWaitParams",
"MobWaitMembersResult",
),
RpcMethodDescriptor::typed(
"mob/wait_ready",
"Wait for mob startup readiness (members bound but kickoff not required)",
"MobWaitParams",
"MobWaitMembersResult",
),
RpcMethodDescriptor::typed(
"mob/profile/create",
"Create a realm-scoped mob profile",
"MobProfileCreateParams",
"MobProfileLookupResult",
),
RpcMethodDescriptor::typed(
"mob/profile/get",
"Read a realm-scoped mob profile",
"MobProfileNameParams",
"MobProfileLookupResult",
),
RpcMethodDescriptor::result_only(
"mob/profile/list",
"List realm-scoped mob profiles",
"MobProfileListResult",
),
RpcMethodDescriptor::typed(
"mob/profile/update",
"Update a realm-scoped mob profile",
"MobProfileUpdateParams",
"MobProfileLookupResult",
),
RpcMethodDescriptor::typed(
"mob/profile/delete",
"Delete a realm-scoped mob profile",
"MobProfileDeleteParams",
"MobProfileDeleteResult",
),
RpcMethodDescriptor::typed(
"mob/stream_open",
"Open a mob event stream",
"MobStreamOpenParams",
"MobStreamOpenResult",
),
RpcMethodDescriptor::typed(
"mob/stream_close",
"Close a mob event stream",
"MobStreamCloseParams",
"MobStreamCloseResult",
),
]);
}
if options.mcp_enabled {
methods.extend([
RpcMethodDescriptor::typed(
"mcp/add",
"Stage live MCP server add for a session",
"McpAddParams",
"McpLiveOpResponse",
),
RpcMethodDescriptor::typed(
"mcp/remove",
"Stage live MCP server remove for a session",
"McpRemoveParams",
"McpLiveOpResponse",
),
RpcMethodDescriptor::typed(
"mcp/reload",
"Optional skeleton for live MCP reload",
"McpReloadParams",
"McpLiveOpResponse",
),
]);
}
if options.comms_enabled {
methods.extend([
RpcMethodDescriptor::typed(
"comms/send",
"Send a comms command from a session",
"CommsSendParams",
"CommsSendResult",
),
RpcMethodDescriptor::typed(
"comms/peers",
"List peers visible to a session",
"CommsPeersParams",
"CommsPeersResult",
),
]);
}
methods
}
pub fn rpc_method_names(options: RpcMethodCatalogOptions) -> Vec<String> {
rpc_method_catalog(options)
.into_iter()
.map(|method| method.name.to_string())
.collect()
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct RpcNotificationDescriptor {
pub name: &'static str,
pub description: &'static str,
}
impl RpcNotificationDescriptor {
const fn basic(name: &'static str, description: &'static str) -> Self {
Self { name, description }
}
}
pub fn rpc_notification_catalog(
options: RpcMethodCatalogOptions,
) -> Vec<RpcNotificationDescriptor> {
let mut notifications = vec![
RpcNotificationDescriptor::basic(
"initialized",
"Client notification acknowledged silently (no response)",
),
RpcNotificationDescriptor::basic(
"cancel",
"Cancel an in-flight JSON-RPC request by request id",
),
RpcNotificationDescriptor::basic("session/event", "AgentEvent payload during turns"),
];
if options.session_streams_enabled {
notifications.extend([
RpcNotificationDescriptor::basic(
"session/stream_event",
"Standalone session stream event notification",
),
RpcNotificationDescriptor::basic(
"session/stream_end",
"Standalone session stream terminal notification",
),
]);
}
if options.mob_enabled {
notifications.extend([
RpcNotificationDescriptor::basic("mob/stream_event", "Mob stream event notification"),
RpcNotificationDescriptor::basic("mob/stream_end", "Mob stream terminal notification"),
]);
}
notifications
}
pub fn rpc_notification_names(options: RpcMethodCatalogOptions) -> Vec<String> {
rpc_notification_catalog(options)
.into_iter()
.map(|notification| notification.name.to_string())
.collect()
}
#[cfg(test)]
#[allow(clippy::panic)]
mod tests {
use super::*;
use crate::{RequestLifecycle, mcp_tool_request_lifecycle, rpc_request_lifecycle};
#[test]
fn documented_surface_keeps_live_runtime_and_mob_methods() {
let methods = rpc_method_names(RpcMethodCatalogOptions::documented_surface());
assert!(methods.iter().any(|m| m == "help/ask"));
assert!(methods.iter().any(|m| m == "session/inject_context"));
assert!(methods.iter().any(|m| m == "runtime/host_info"));
assert!(methods.iter().any(|m| m == "runtime/capabilities"));
assert!(methods.iter().any(|m| m == "runtime/health"));
assert!(methods.iter().any(|m| m == "events/latest_cursor"));
assert!(methods.iter().any(|m| m == "events/list_since"));
assert!(methods.iter().any(|m| m == "events/snapshot"));
assert!(methods.iter().any(|m| m == "approval/request"));
assert!(methods.iter().any(|m| m == "approval/list"));
assert!(methods.iter().any(|m| m == "approval/get"));
assert!(methods.iter().any(|m| m == "approval/decide"));
assert!(
methods
.iter()
.any(|m| m == "session/peer_response_terminal")
);
assert!(methods.iter().any(|m| m == "mob/events"));
assert!(methods.iter().any(|m| m == "mob/member_send"));
assert!(methods.iter().any(|m| m == "mob/wait_kickoff"));
assert!(methods.iter().any(|m| m == "mob/profile/create"));
assert!(methods.iter().any(|m| m == "schedule/list"));
assert!(methods.iter().any(|m| m == "schedule/occurrences"));
assert!(methods.iter().any(|m| m == "schedule/call"));
}
#[test]
fn documented_surface_keeps_live_stream_notifications() {
let notifications = rpc_notification_names(RpcMethodCatalogOptions::documented_surface());
assert!(notifications.iter().any(|n| n == "cancel"));
assert!(notifications.iter().any(|n| n == "session/stream_event"));
assert!(notifications.iter().any(|n| n == "session/stream_end"));
assert!(notifications.iter().any(|n| n == "mob/stream_event"));
assert!(notifications.iter().any(|n| n == "mob/stream_end"));
}
#[test]
fn rpc_catalog_owns_request_lifecycle_rules() {
let methods = rpc_method_catalog(RpcMethodCatalogOptions::documented_surface());
let descriptor = |name: &str| {
methods
.iter()
.find(|method| method.name == name)
.unwrap_or_else(|| panic!("missing descriptor for {name}"))
};
assert_eq!(
descriptor("help/ask").request_lifecycle.resolve(None),
RequestLifecycle::LongRunningPublishOnSuccess
);
assert_eq!(
descriptor("turn/start").request_lifecycle.resolve(None),
RequestLifecycle::LongRunningPublishOnSuccess
);
assert_eq!(
descriptor("mob/turn_start").request_lifecycle.resolve(None),
RequestLifecycle::LongRunningPublishOnSuccess
);
assert_eq!(
descriptor("session/create")
.request_lifecycle
.resolve(Some(r#"{"initial_turn":"deferred"}"#)),
RequestLifecycle::InlineObservation
);
assert_eq!(
rpc_request_lifecycle(
"session/create",
Some(r#"{"initial_turn":"run_immediately"}"#),
),
RequestLifecycle::LongRunningPublishOnSuccess
);
assert_eq!(
rpc_request_lifecycle("session/list", None),
RequestLifecycle::InlineObservation
);
}
#[test]
fn mcp_tool_catalog_owns_request_lifecycle_rules() {
assert_eq!(
mcp_tool_request_lifecycle("meerkat_help"),
RequestLifecycle::LongRunningPublishOnSuccess
);
assert_eq!(
mcp_tool_request_lifecycle("meerkat_run"),
RequestLifecycle::LongRunningPublishOnSuccess
);
assert_eq!(
mcp_tool_request_lifecycle("meerkat_resume"),
RequestLifecycle::LongRunningPublishOnSuccess
);
assert_eq!(
mcp_tool_request_lifecycle("meerkat_sessions"),
RequestLifecycle::LongRunningObservation
);
}
#[test]
fn documented_surface_excludes_retired_auth_probe() {
let methods = rpc_method_names(RpcMethodCatalogOptions::documented_surface());
assert!(
!methods.iter().any(|m| m == "auth/profile/test"),
"retired auth/profile/test must not be advertised by public catalogs"
);
}
#[test]
fn documented_surface_excludes_runtime_session_control_nouns_and_keeps_canonical_ingress() {
let methods = rpc_method_names(RpcMethodCatalogOptions::documented_surface());
for supported in [
"session/external_event",
"session/peer_response_terminal",
"session/inject_context",
] {
assert!(
methods.iter().any(|m| m == supported),
"canonical runtime-backed session control noun must remain advertised: {supported}"
);
}
for retired in [
"runtime/session_status",
"runtime/session_submit",
"runtime/session_retire",
"runtime/session_reset",
"runtime/session_submission",
"runtime/session_submissions",
] {
assert!(
!methods.iter().any(|m| m == retired),
"runtime/session control noun must not be advertised by public catalogs: {retired}"
);
}
}
#[test]
fn workgraph_rpc_surface_is_read_only_observability() {
let methods = rpc_method_names(RpcMethodCatalogOptions::documented_surface());
for supported in [
"workgraph/get",
"workgraph/list",
"workgraph/ready",
"workgraph/snapshot",
"workgraph/events",
] {
assert!(
methods.iter().any(|m| m == supported),
"read-only WorkGraph RPC method must be advertised: {supported}"
);
}
for retired in [
"workgraph/create",
"workgraph/claim",
"workgraph/release",
"workgraph/update",
"workgraph/close",
"workgraph/link",
"workgraph/add_evidence",
] {
assert!(
!methods.iter().any(|m| m == retired),
"mutating WorkGraph RPC method must not be advertised: {retired}"
);
}
}
#[test]
fn basic_turn_and_session_methods_advertise_required_params() {
let methods = rpc_method_catalog(RpcMethodCatalogOptions::documented_surface());
for name in [
"session/create",
"session/read",
"session/history",
"session/archive",
"turn/start",
"turn/interrupt",
] {
let descriptors: Vec<_> = methods
.iter()
.filter(|method| method.name == name)
.collect();
assert_eq!(
descriptors.len(),
1,
"missing or duplicated descriptor for {name}"
);
for descriptor in descriptors {
assert!(
descriptor.params_type.is_some(),
"{name} must advertise its required params type"
);
}
}
}
#[test]
fn auth_response_methods_advertise_concrete_result_types() {
let methods = rpc_method_catalog(RpcMethodCatalogOptions::documented_surface());
for (name, expected_result_type) in [
("auth/profile/list", "WireAuthProfilesList"),
("auth/profile/get", "WireAuthProfileDetail"),
("auth/profile/create", "WireAuthProfileCreated"),
("auth/profile/delete", "WireAuthProfileCleared"),
("auth/login/start", "WireLoginStart"),
("auth/login/complete", "WireLoginReady"),
("auth/login/device_start", "WireDeviceStart"),
("auth/status/get", "WireAuthStatusDetail"),
("auth/logout", "WireAuthProfileCleared"),
("realm/list", "WireRealmList"),
("realm/get", "WireRealmConnectionSet"),
] {
let descriptor = methods
.iter()
.find(|method| method.name == name)
.unwrap_or_else(|| panic!("missing descriptor for {name}"));
assert_eq!(
descriptor.result_type,
Some(expected_result_type),
"{name} must advertise its concrete result type"
);
}
}
#[test]
fn auth_methods_advertise_concrete_request_and_response_contracts() {
let methods = rpc_method_catalog(RpcMethodCatalogOptions::documented_surface());
for (name, expected_params_type, expected_result_type) in [
(
"auth/profile/list",
Some("RealmIdParams"),
Some("WireAuthProfilesList"),
),
(
"auth/profile/get",
Some("BindingIdParams"),
Some("WireAuthProfileDetail"),
),
(
"auth/profile/create",
Some("CreateProfileParams"),
Some("WireAuthProfileCreated"),
),
(
"auth/profile/delete",
Some("BindingIdParams"),
Some("WireAuthProfileCleared"),
),
(
"auth/login/start",
Some("LoginStartParams"),
Some("WireLoginStart"),
),
(
"auth/login/complete",
Some("LoginCompleteParams"),
Some("WireLoginReady"),
),
(
"auth/login/device_start",
Some("DeviceStartParams"),
Some("WireDeviceStart"),
),
(
"auth/login/device_complete",
Some("DeviceCompleteParams"),
Some("WireDeviceCompleteResult"),
),
(
"auth/login/provision_api_key",
Some("ProvisionApiKeyParams"),
Some("WireProvisionApiKeyResult"),
),
(
"auth/status/get",
Some("BindingIdParams"),
Some("WireAuthStatusDetail"),
),
(
"auth/logout",
Some("BindingIdParams"),
Some("WireAuthProfileCleared"),
),
("realm/list", None, Some("WireRealmList")),
(
"realm/get",
Some("RealmIdParams"),
Some("WireRealmConnectionSet"),
),
] {
let descriptor = methods
.iter()
.find(|method| method.name == name)
.unwrap_or_else(|| panic!("missing descriptor for {name}"));
assert_eq!(
descriptor.params_type, expected_params_type,
"{name} must advertise its concrete params type"
);
assert_eq!(
descriptor.result_type, expected_result_type,
"{name} must advertise its concrete result type"
);
}
}
#[test]
fn comms_and_session_stream_methods_advertise_concrete_contracts() {
let methods = rpc_method_catalog(RpcMethodCatalogOptions::documented_surface());
for (name, expected_params_type, expected_result_type) in [
(
"session/stream_open",
"SessionStreamOpenParams",
"SessionStreamOpenResult",
),
(
"session/stream_close",
"SessionStreamCloseParams",
"SessionStreamCloseResult",
),
("comms/send", "CommsSendParams", "CommsSendResult"),
("comms/peers", "CommsPeersParams", "CommsPeersResult"),
] {
let descriptor = methods
.iter()
.find(|method| method.name == name)
.unwrap_or_else(|| panic!("missing descriptor for {name}"));
assert_eq!(
descriptor.params_type,
Some(expected_params_type),
"{name} must advertise its concrete params type"
);
assert_eq!(
descriptor.result_type,
Some(expected_result_type),
"{name} must advertise its concrete result type"
);
}
}
#[test]
fn mob_methods_advertise_only_real_exported_contracts() {
let methods = rpc_method_catalog(RpcMethodCatalogOptions::documented_surface());
for (name, expected_params_type, expected_result_type) in [
(
"mob/create",
Some("MobCreateParams"),
Some("MobCreateResult"),
),
("mob/list", None, Some("MobListResult")),
("mob/status", Some("MobIdParams"), Some("MobStatusResult")),
(
"mob/lifecycle",
Some("MobLifecycleParams"),
Some("MobLifecycleResult"),
),
("mob/spawn", Some("MobSpawnParams"), Some("MobSpawnResult")),
(
"mob/spawn_many",
Some("MobSpawnManyParams"),
Some("MobSpawnManyResult"),
),
(
"mob/ensure_member",
Some("MobEnsureMemberParams"),
Some("MobEnsureMemberResult"),
),
(
"mob/reconcile",
Some("MobReconcileParams"),
Some("MobReconcileResult"),
),
(
"mob/list_members_matching",
Some("MobListMembersMatchingParams"),
Some("MobListMembersMatchingResult"),
),
(
"mob/retire",
Some("MobMemberParams"),
Some("MobRetireResult"),
),
(
"mob/respawn",
Some("MobRespawnParams"),
Some("MobRespawnResult"),
),
("mob/wire", Some("MobWireParams"), Some("MobWireResult")),
(
"mob/wire_members_batch",
Some("MobWireMembersBatchParams"),
Some("MobWireMembersBatchResult"),
),
(
"mob/unwire",
Some("MobUnwireParams"),
Some("MobUnwireResult"),
),
("mob/members", Some("MobIdParams"), Some("MobMembersResult")),
(
"mob/events",
Some("MobEventsParams"),
Some("MobEventsResult"),
),
(
"mob/member_send",
Some("MobMemberSendParams"),
Some("MobMemberSendResult"),
),
(
"mob/ingress_interaction",
Some("MobIngressInteractionParams"),
Some("MobIngressInteractionResult"),
),
(
"mob/append_system_context",
Some("MobAppendSystemContextParams"),
Some("MobAppendSystemContextResult"),
),
("mob/flows", Some("MobIdParams"), Some("MobFlowsResult")),
(
"mob/flow_run",
Some("MobFlowRunParams"),
Some("MobFlowRunResult"),
),
(
"mob/flow_status",
Some("MobFlowStatusParams"),
Some("MobFlowStatusResult"),
),
(
"mob/flow_cancel",
Some("MobFlowCancelParams"),
Some("MobFlowCancelResult"),
),
(
"mob/spawn_helper",
Some("MobSpawnHelperParams"),
Some("MobHelperResult"),
),
(
"mob/fork_helper",
Some("MobForkHelperParams"),
Some("MobHelperResult"),
),
(
"mob/force_cancel",
Some("MobMemberParams"),
Some("MobForceCancelResult"),
),
(
"mob/turn_start",
Some("MobTurnStartParams"),
Some("WireRunResult"),
),
(
"mob/member_status",
Some("MobMemberParams"),
Some("MobMemberStatusResult"),
),
(
"mob/snapshot",
Some("MobIdParams"),
Some("MobSnapshotResult"),
),
("mob/destroy", Some("MobIdParams"), Some("MobDestroyResult")),
(
"mob/rotate_supervisor",
Some("MobIdParams"),
Some("MobRotateSupervisorResult"),
),
(
"mob/wait_kickoff",
Some("MobWaitParams"),
Some("MobWaitMembersResult"),
),
(
"mob/wait_ready",
Some("MobWaitParams"),
Some("MobWaitMembersResult"),
),
(
"mob/submit_work",
Some("MobSubmitWorkParams"),
Some("MobSubmitWorkResult"),
),
(
"mob/cancel_work",
Some("MobCancelWorkParams"),
Some("MobCancelWorkResult"),
),
(
"mob/cancel_all_work",
Some("MobCancelAllWorkParams"),
Some("MobCancelAllWorkResult"),
),
(
"mob/profile/create",
Some("MobProfileCreateParams"),
Some("MobProfileLookupResult"),
),
(
"mob/profile/get",
Some("MobProfileNameParams"),
Some("MobProfileLookupResult"),
),
("mob/profile/list", None, Some("MobProfileListResult")),
(
"mob/profile/update",
Some("MobProfileUpdateParams"),
Some("MobProfileLookupResult"),
),
(
"mob/profile/delete",
Some("MobProfileDeleteParams"),
Some("MobProfileDeleteResult"),
),
(
"mob/stream_open",
Some("MobStreamOpenParams"),
Some("MobStreamOpenResult"),
),
(
"mob/stream_close",
Some("MobStreamCloseParams"),
Some("MobStreamCloseResult"),
),
] {
let descriptor = methods
.iter()
.find(|method| method.name == name)
.unwrap_or_else(|| panic!("missing descriptor for {name}"));
assert_eq!(
descriptor.params_type, expected_params_type,
"{name} must advertise its concrete params type"
);
assert_eq!(
descriptor.result_type, expected_result_type,
"{name} must advertise its concrete result type"
);
}
}
}