mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(rpc): handle legacy namespaced method calls (#3875)
This commit is contained in:
@@ -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}"`);
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user