mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(rpc): rewrite legacy method names server-side before dispatch (OPENHUMAN-TAURI-BQ) (#1544)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
Claude Opus 4.7
Steven Enamakel
parent
2f2f98516a
commit
75097e66d5
@@ -3,6 +3,7 @@
|
||||
//! This module coordinates the routing of incoming requests to either the
|
||||
//! core subsystem or the OpenHuman domain-specific handlers.
|
||||
|
||||
use crate::core::legacy_aliases::resolve_legacy;
|
||||
use crate::core::rpc_log;
|
||||
use crate::core::types::{AppState, InvocationResult};
|
||||
use serde_json::{json, Map, Value};
|
||||
@@ -47,6 +48,25 @@ pub async fn dispatch(
|
||||
rpc_log::redact_params_for_log(¶ms)
|
||||
);
|
||||
|
||||
// Tier 0: Rewrite legacy method names to their canonical form before
|
||||
// any subsystem lookup. Symmetric with the frontend's
|
||||
// `normalizeRpcMethod` (`app/src/services/rpcMethods.ts`): the
|
||||
// frontend rewrites outgoing names for clients that just updated, the
|
||||
// core rewrites incoming names for clients that haven't yet. See
|
||||
// `crate::core::legacy_aliases` for the shared table.
|
||||
let resolved = resolve_legacy(method);
|
||||
if resolved != method {
|
||||
// Per-rewrite log at debug to keep the dispatcher hot path quiet
|
||||
// at scale (per graycyrus review on PR #1544). Aggregate
|
||||
// visibility belongs in the observability layer, not here.
|
||||
log::debug!(
|
||||
"[rpc-legacy-alias] rewrite method={} -> canonical={}",
|
||||
method,
|
||||
resolved
|
||||
);
|
||||
}
|
||||
let method = resolved;
|
||||
|
||||
// Tier 1: Internal core methods.
|
||||
// These are handled directly within the core module and don't require
|
||||
// a separate controller registration.
|
||||
@@ -209,6 +229,18 @@ mod tests {
|
||||
assert_eq!(out, json!({ "ok": true }));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dispatch_rewrites_legacy_alias_before_lookup() {
|
||||
// `openhuman.ping` is a legacy alias for `core.ping` in the shared
|
||||
// alias table. Going through the dispatcher must rewrite it and
|
||||
// route successfully to Tier 1 instead of falling through to the
|
||||
// unknown-method error path.
|
||||
let out = dispatch(test_state(), "openhuman.ping", json!({}))
|
||||
.await
|
||||
.expect("legacy alias openhuman.ping must resolve to core.ping");
|
||||
assert_eq!(out, json!({ "ok": true }));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dispatch_unknown_method_returns_error() {
|
||||
let err = dispatch(test_state(), "does.not.exist", json!({}))
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
//! Server-side legacy RPC method aliases.
|
||||
//!
|
||||
//! Mirrors the frontend's `LEGACY_METHOD_ALIASES` table in
|
||||
//! `app/src/services/rpcMethods.ts`. The frontend rewrites outgoing method
|
||||
//! names for clients that just updated; this module rewrites incoming
|
||||
//! method names for clients that haven't updated yet (older shipped bundles
|
||||
//! in the wild). Together they form a symmetric migration safety net:
|
||||
//! either side can be the one that's behind, and the call still resolves.
|
||||
//!
|
||||
//! When adding or removing an entry here, keep
|
||||
//! `app/src/services/rpcMethods.ts:LEGACY_METHOD_ALIASES` in sync. The two
|
||||
//! tables are intentionally identical: the same legacy → canonical map
|
||||
//! applied at both ends of the wire.
|
||||
//!
|
||||
//! The rewrite is a pure key-to-key lookup. No domain branches, no
|
||||
//! parameter inspection — if a method isn't in the table, it passes through
|
||||
//! untouched.
|
||||
|
||||
/// Legacy → canonical RPC method name pairs.
|
||||
///
|
||||
/// 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)] = &[
|
||||
(
|
||||
"openhuman.get_analytics_settings",
|
||||
"openhuman.config_get_analytics_settings",
|
||||
),
|
||||
(
|
||||
"openhuman.get_composio_trigger_settings",
|
||||
"openhuman.config_get_composio_trigger_settings",
|
||||
),
|
||||
("openhuman.get_config", "openhuman.config_get"),
|
||||
(
|
||||
"openhuman.get_runtime_flags",
|
||||
"openhuman.config_get_runtime_flags",
|
||||
),
|
||||
("openhuman.ping", "core.ping"),
|
||||
(
|
||||
"openhuman.set_browser_allow_all",
|
||||
"openhuman.config_set_browser_allow_all",
|
||||
),
|
||||
(
|
||||
"openhuman.update_analytics_settings",
|
||||
"openhuman.config_update_analytics_settings",
|
||||
),
|
||||
(
|
||||
"openhuman.update_browser_settings",
|
||||
"openhuman.config_update_browser_settings",
|
||||
),
|
||||
(
|
||||
"openhuman.update_composio_trigger_settings",
|
||||
"openhuman.config_update_composio_trigger_settings",
|
||||
),
|
||||
(
|
||||
"openhuman.update_local_ai_settings",
|
||||
"openhuman.config_update_local_ai_settings",
|
||||
),
|
||||
(
|
||||
"openhuman.update_memory_settings",
|
||||
"openhuman.config_update_memory_settings",
|
||||
),
|
||||
(
|
||||
"openhuman.update_model_settings",
|
||||
"openhuman.config_update_model_settings",
|
||||
),
|
||||
(
|
||||
"openhuman.update_runtime_settings",
|
||||
"openhuman.config_update_runtime_settings",
|
||||
),
|
||||
(
|
||||
"openhuman.update_screen_intelligence_settings",
|
||||
"openhuman.config_update_screen_intelligence_settings",
|
||||
),
|
||||
(
|
||||
"openhuman.workspace_onboarding_flag_exists",
|
||||
"openhuman.config_workspace_onboarding_flag_exists",
|
||||
),
|
||||
(
|
||||
"openhuman.workspace_onboarding_flag_set",
|
||||
"openhuman.config_workspace_onboarding_flag_set",
|
||||
),
|
||||
];
|
||||
|
||||
/// Resolves a legacy RPC method name to its canonical form, if any.
|
||||
///
|
||||
/// Returns the canonical name when `method` is a known legacy alias;
|
||||
/// otherwise returns `method` unchanged. This function is idempotent:
|
||||
/// calling it on an already-canonical name (or any unrelated name) is a
|
||||
/// no-op.
|
||||
///
|
||||
/// Returns a borrow that lives for at least the input's lifetime — the
|
||||
/// matched-canonical branch returns `&'static`, the pass-through branch
|
||||
/// returns the input borrow; elision picks the tighter input lifetime.
|
||||
pub fn resolve_legacy(method: &str) -> &str {
|
||||
for (legacy, canonical) in LEGACY_ALIASES {
|
||||
if *legacy == method {
|
||||
return canonical;
|
||||
}
|
||||
}
|
||||
method
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn resolve_legacy_rewrites_every_table_entry() {
|
||||
for (legacy, canonical) in LEGACY_ALIASES {
|
||||
assert_eq!(
|
||||
resolve_legacy(legacy),
|
||||
*canonical,
|
||||
"expected legacy alias {legacy} to resolve to {canonical}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_legacy_rewrites_composio_trigger_settings() {
|
||||
// The specific case observed in Sentry: older bundles called the
|
||||
// bare `openhuman.update_composio_trigger_settings` against a core
|
||||
// that only registers the namespaced form.
|
||||
assert_eq!(
|
||||
resolve_legacy("openhuman.update_composio_trigger_settings"),
|
||||
"openhuman.config_update_composio_trigger_settings",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_legacy_passes_through_unknown_methods() {
|
||||
assert_eq!(
|
||||
resolve_legacy("openhuman.memory_list_namespaces"),
|
||||
"openhuman.memory_list_namespaces"
|
||||
);
|
||||
assert_eq!(resolve_legacy("does.not.exist"), "does.not.exist");
|
||||
assert_eq!(resolve_legacy(""), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_legacy_is_idempotent_for_canonical_names() {
|
||||
// Canonical names already match what the registry expects;
|
||||
// running them through the resolver must be a no-op so callers
|
||||
// can wrap the lookup unconditionally.
|
||||
for (_, canonical) in LEGACY_ALIASES {
|
||||
assert_eq!(
|
||||
resolve_legacy(canonical),
|
||||
*canonical,
|
||||
"canonical {canonical} must pass through unchanged",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_legacy_returned_str_equals_table_value() {
|
||||
// Sanity check: the function returns the canonical str slice from
|
||||
// the table when it matches, not a copy of the input.
|
||||
let out = resolve_legacy("openhuman.ping");
|
||||
assert_eq!(out, "core.ping");
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ pub mod cli;
|
||||
pub mod dispatch;
|
||||
pub mod event_bus;
|
||||
pub mod jsonrpc;
|
||||
pub mod legacy_aliases;
|
||||
pub mod logging;
|
||||
pub mod memory_cli;
|
||||
pub mod observability;
|
||||
|
||||
Reference in New Issue
Block a user