fix(rpc): handle legacy namespaced method calls (#3875)

This commit is contained in:
YOMXXX
2026-06-21 13:27:29 -07:00
committed by GitHub
parent 7758148c2c
commit 6d220a7b2a
4 changed files with 76 additions and 17 deletions
+18 -1
View File
@@ -113,6 +113,17 @@ describe('rpcMethods catalog', () => {
});
});
describe('channels legacy alias resolution (Sentry OPENHUMAN-CORE-1Y / OPENHUMAN-CORE-1Z)', () => {
test('dotted channel list aliases resolve to channels_list', () => {
expect(normalizeRpcMethod('channels.list')).toBe(CORE_RPC_METHODS.channelsList);
expect(normalizeRpcMethod('openhuman.channels.list')).toBe(CORE_RPC_METHODS.channelsList);
});
test('canonical channels_list passes through unchanged', () => {
expect(normalizeRpcMethod('openhuman.channels_list')).toBe('openhuman.channels_list');
});
});
test('catalog canonical methods exist in core schema registry (drift guard)', () => {
const schemaSources = [
fs.readFileSync(
@@ -147,6 +158,10 @@ describe('rpcMethods catalog', () => {
path.resolve(__dirname, '../../../../src/openhuman/health/schemas.rs'),
'utf8'
),
fs.readFileSync(
path.resolve(__dirname, '../../../../src/openhuman/channels/controllers/schemas.rs'),
'utf8'
),
].join('\n');
for (const method of Object.values(CORE_RPC_METHODS)) {
@@ -165,7 +180,9 @@ describe('rpcMethods catalog', () => {
? 'mcp_clients'
: methodRoot.startsWith('health_')
? 'health'
: 'config';
: methodRoot.startsWith('channels_')
? 'channels'
: 'config';
const fnName = methodRoot.slice(`${namespace}_`.length);
expect(schemaSources).toContain(`namespace: "${namespace}"`);
expect(schemaSources).toContain(`function: "${fnName}"`);
+4
View File
@@ -57,6 +57,7 @@ export const CORE_RPC_METHODS = {
embeddingsClearApiKey: 'openhuman.embeddings_clear_api_key',
embeddingsEmbed: 'openhuman.embeddings_embed',
embeddingsTestConnection: 'openhuman.embeddings_test_connection',
channelsList: 'openhuman.channels_list',
mcpClientsInstalledList: 'openhuman.mcp_clients_installed_list',
mcpClientsToolCall: 'openhuman.mcp_clients_tool_call',
healthSnapshot: 'openhuman.health_snapshot',
@@ -66,9 +67,12 @@ export const CORE_RPC_METHODS = {
export type CoreRpcMethod = (typeof CORE_RPC_METHODS)[keyof typeof CORE_RPC_METHODS];
export const LEGACY_METHOD_ALIASES: Record<string, CoreRpcMethod> = {
// #3565: old desktop clients used dotted namespace/function channel calls.
'channels.list': CORE_RPC_METHODS.channelsList,
// MCP clients — old method names that appeared in Sentry (CORE-RUST-DR/DS/DT/DV/DW).
// See src/core/legacy_aliases.rs for the Rust-side mirror of this table.
'mcp_clients.list': CORE_RPC_METHODS.mcpClientsInstalledList,
'openhuman.channels.list': CORE_RPC_METHODS.channelsList,
'openhuman.mcp_clients_list': CORE_RPC_METHODS.mcpClientsInstalledList,
'openhuman.mcp_list': CORE_RPC_METHODS.mcpClientsInstalledList,
'openhuman.mcp_servers_list': CORE_RPC_METHODS.mcpClientsInstalledList,
+40 -16
View File
@@ -85,14 +85,15 @@ pub async fn dispatch(
// Tier 4: unrecognised method. The JSON-RPC response is unchanged — the
// caller still receives a method-not-found error. Only the *severity* of
// how the transport layer records it differs by class (see
// `jsonrpc::rpc_handler`): known external probes (`is_known_probe_method`)
// are debug-only and never reach Sentry (#3567), while any other unknown
// method is downgraded to a warn-level capture (recorded for triage, no
// page) instead of an error event. Log here at debug with the method name
// so the path stays diagnosable without re-creating the Sentry noise.
// `jsonrpc::rpc_handler`): known non-actionable misses
// (`is_known_probe_method`) are debug-only and never reach Sentry
// (#3567, #3565), while any other unknown method is downgraded to a
// warn-level capture (recorded for triage, no page) instead of an error
// event. Log here at debug with the method name so the path stays
// diagnosable without re-creating the Sentry noise.
if is_known_probe_method(method) {
log::debug!(
"[rpc] unknown_method method={} class=known_probe (debug-only; not reported to Sentry)",
"[rpc] unknown_method method={} class=debug_only_known_miss (not reported to Sentry)",
method
);
} else {
@@ -109,23 +110,28 @@ pub async fn dispatch(
/// classifier ([`unknown_method_name`]) cannot drift apart.
pub const UNKNOWN_METHOD_PREFIX: &str = "unknown method: ";
/// Generic external probe / legacy method names that are never real RPC
/// methods and never will be (issue #3567). Infra health-checks and JSON-RPC
/// introspection clients poll these — `rpc.discover` (JSON-RPC service
/// discovery), `list_methods`, liveness `status`, `auth.status`, `config/get`
/// — and each miss previously produced a recurring Sentry ERROR event with
/// zero user impact. The transport layer keeps these debug-only (never
/// captured). The matching health-method *aliases* land separately in
/// `legacy_aliases` (#3566), which depends on this severity change.
/// Known non-actionable unknown method names that are debug-only.
///
/// Most entries are generic external probes that are never real RPC methods
/// and never will be (issue #3567): `rpc.discover` (JSON-RPC service
/// discovery), `list_methods`, liveness `status`, `auth.status`, `config/get`.
/// This also covers retired feature calls from older clients when no safe
/// canonical handler exists (#3565: `openhuman.memory_tree_create_namespace`).
///
/// Each miss previously produced recurring Sentry ERROR events with zero user
/// impact. The transport layer keeps these debug-only (never captured). The
/// matching health-method *aliases* land separately in `legacy_aliases`
/// (#3566), which depends on this severity change.
const KNOWN_PROBE_METHODS: &[&str] = &[
"rpc.discover",
"list_methods",
"status",
"auth.status",
"config/get",
"openhuman.memory_tree_create_namespace",
];
/// Returns `true` when `method` is a known external probe / legacy health name
/// Returns `true` when `method` is a known non-actionable unknown method name
/// from [`KNOWN_PROBE_METHODS`]. Matched against the *resolved* method name
/// (after legacy-alias rewrite), i.e. the name embedded in the
/// [`UNKNOWN_METHOD_PREFIX`] error string.
@@ -378,8 +384,12 @@ mod tests {
"status",
"auth.status",
"config/get",
"openhuman.memory_tree_create_namespace",
] {
assert!(is_known_probe_method(m), "{m} should be a known probe");
assert!(
is_known_probe_method(m),
"{m} should be a debug-only known miss"
);
}
// Genuinely-unknown methods and near-misses are NOT allow-listed, so
// they stay on the warn-for-triage path rather than being silenced.
@@ -387,9 +397,23 @@ mod tests {
assert!(!is_known_probe_method("core.not_a_real_method"));
assert!(!is_known_probe_method("Status")); // case-sensitive
assert!(!is_known_probe_method("rpc.discover.extra")); // exact match only
assert!(!is_known_probe_method("memory_tree_create_namespace"));
assert!(!is_known_probe_method(""));
}
#[tokio::test]
async fn dispatch_dotted_channel_list_aliases_route_to_registry() {
for method in ["channels.list", "openhuman.channels.list"] {
let out = dispatch(test_state(), method, json!({}))
.await
.unwrap_or_else(|err| panic!("{method} should route via channels_list: {err}"));
assert!(
out.is_array() || out.get("result").is_some(),
"expected {method} to return the channels_list payload, got {out}"
);
}
}
#[test]
fn unknown_method_name_extracts_from_error_string_only() {
// The classifier round-trips the exact string `dispatch` emits.
+14
View File
@@ -21,6 +21,10 @@
/// Order doesn't matter for correctness, but is kept alphabetical by legacy
/// key for easier diffing against the frontend table.
const LEGACY_ALIASES: &[(&str, &str)] = &[
// #3565: old desktop clients called the channels controller with a dotted
// namespace/function spelling before the canonical
// `openhuman.<namespace>_<function>` form was established.
("channels.list", "openhuman.channels_list"),
// MCP clients — old method names that appeared in Sentry (CORE-RUST-DR/DS/DT/DV/DW).
// Callers used dotted namespace, bare `mcp_list`, `mcp_servers_list`, and
// `mcp_clients_list` before the canonical `mcp_clients_installed_list` was
@@ -28,6 +32,7 @@ const LEGACY_ALIASES: &[(&str, &str)] = &[
// `mcp_clients_tool_call` that shipped in at least one older bundle.
// `mcp_clients.list` sorts before all `openhuman.*` entries (m < o).
("mcp_clients.list", "openhuman.mcp_clients_installed_list"),
("openhuman.channels.list", "openhuman.channels_list"),
(
"openhuman.get_analytics_settings",
"openhuman.config_get_analytics_settings",
@@ -563,6 +568,15 @@ mod tests {
assert_eq!(out, "core.ping");
}
#[test]
fn resolve_legacy_rewrites_dotted_channel_list_aliases() {
assert_eq!(resolve_legacy("channels.list"), "openhuman.channels_list");
assert_eq!(
resolve_legacy("openhuman.channels.list"),
"openhuman.channels_list"
);
}
#[test]
fn frontend_core_rpc_methods_exist_in_core_schema_registry() {
let source = read_frontend_rpc_catalog();