Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions app/src/services/__tests__/rpcMethods.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,24 @@ describe('rpcMethods catalog', () => {
});
});

describe('health legacy alias resolution (Sentry CORE-RUST-FG / CORE-RUST-G0)', () => {
test('health_snapshot resolves to openhuman.health_snapshot', () => {
expect(normalizeRpcMethod('health_snapshot')).toBe(CORE_RPC_METHODS.healthSnapshot);
});

test('openhuman.system_info resolves to openhuman.health_system_info (Sentry CORE-RUST-G0)', () => {
// Older clients called openhuman.system_info before the method was
// namespaced under health as openhuman.health_system_info.
expect(normalizeRpcMethod('openhuman.system_info')).toBe(CORE_RPC_METHODS.healthSystemInfo);
});

test('canonical health_system_info passes through unchanged', () => {
expect(normalizeRpcMethod('openhuman.health_system_info')).toBe(
'openhuman.health_system_info'
);
});
});

test('catalog canonical methods exist in core schema registry (drift guard)', () => {
const schemaSources = [
fs.readFileSync(
Expand Down
5 changes: 5 additions & 0 deletions app/src/services/rpcMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const CORE_RPC_METHODS = {
mcpClientsInstalledList: 'openhuman.mcp_clients_installed_list',
mcpClientsToolCall: 'openhuman.mcp_clients_tool_call',
healthSnapshot: 'openhuman.health_snapshot',
healthSystemInfo: 'openhuman.health_system_info',
} as const;

export type CoreRpcMethod = (typeof CORE_RPC_METHODS)[keyof typeof CORE_RPC_METHODS];
Expand Down Expand Up @@ -77,6 +78,10 @@ export const LEGACY_METHOD_ALIASES: Record<string, CoreRpcMethod> = {
'openhuman.providers_list_models': CORE_RPC_METHODS.inferenceListModels,
'openhuman.inference_embed': CORE_RPC_METHODS.embeddingsEmbed,
health_snapshot: CORE_RPC_METHODS.healthSnapshot,
// `openhuman.system_info` was used by older clients / SDK callers before the
// method was namespaced as `openhuman.health_system_info`.
// Sentry CORE-RUST-G0 — https://sentry.tinyhumans.ai/organizations/tinyhumans/issues/6340/
'openhuman.system_info': CORE_RPC_METHODS.healthSystemInfo,
};

export function normalizeRpcMethod(method: string): string {
Expand Down
16 changes: 16 additions & 0 deletions src/core/legacy_aliases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ const LEGACY_ALIASES: &[(&str, &str)] = &[
// bare `health_snapshot` (no namespace prefix) was used by older clients
// before the canonical `openhuman.health_snapshot` form was established.
("health_snapshot", "openhuman.health_snapshot"),
// `openhuman.system_info` was used by older clients / SDK callers before
// the method was namespaced under `health` as `openhuman.health_system_info`.
// Sentry CORE-RUST-G0 — https://sentry.tinyhumans.ai/organizations/tinyhumans/issues/6340/
("openhuman.system_info", "openhuman.health_system_info"),
("openhuman.inference_embed", "openhuman.embeddings_embed"),
("openhuman.local_ai_presets", "openhuman.inference_presets"),
(
Expand Down Expand Up @@ -422,6 +426,18 @@ mod tests {
);
}

#[test]
fn resolve_legacy_rewrites_system_info() {
// Sentry CORE-RUST-G0: older clients called `openhuman.system_info`
// before the method was namespaced under `health` as
// `openhuman.health_system_info`. The alias table must rewrite it so
// the call resolves against the registered controller.
assert_eq!(
resolve_legacy("openhuman.system_info"),
"openhuman.health_system_info",
);
}

#[test]
fn resolve_legacy_passes_through_unknown_methods() {
assert_eq!(
Expand Down
82 changes: 82 additions & 0 deletions src/openhuman/health/ops.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,90 @@
//! JSON-RPC / CLI controller surface for the process health registry.

use serde::Serialize;

use crate::openhuman::health;
use crate::rpc::RpcOutcome;

pub fn health_snapshot() -> RpcOutcome<serde_json::Value> {
RpcOutcome::single_log(health::snapshot_json(), "health_snapshot requested")
}

/// Static system information returned by `openhuman.health_system_info`.
#[derive(Debug, Serialize)]
pub struct SystemInfo {
/// Cargo package version of the running core binary.
pub version: &'static str,
/// Target operating system name (`linux`, `macos`, `windows`, …).
pub os: &'static str,
/// Target CPU architecture (`x86_64`, `aarch64`, …).
pub arch: &'static str,
/// Current process ID.
pub pid: u32,
}

/// Returns static system information: version, OS, architecture, and PID.
///
/// This is the handler backing the `openhuman.health_system_info` RPC method
/// (legacy callers may send `openhuman.system_info`, which the alias table
/// rewrites before dispatch).
pub fn system_info() -> RpcOutcome<SystemInfo> {
let info = SystemInfo {
version: env!("CARGO_PKG_VERSION"),
os: std::env::consts::OS,
arch: std::env::consts::ARCH,
pid: std::process::id(),
};
tracing::debug!(
version = info.version,
os = info.os,
arch = info.arch,
pid = info.pid,
"[health] system_info requested"
);
RpcOutcome::single_log(info, "system_info requested")
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn system_info_returns_non_empty_version() {
let outcome = system_info();
let json = outcome
.into_cli_compatible_json()
.expect("serialization ok");
let version = json["version"].as_str().expect("version is a string");
assert!(!version.is_empty(), "version must be non-empty");
}

#[test]
fn system_info_returns_known_os() {
let outcome = system_info();
let json = outcome
.into_cli_compatible_json()
.expect("serialization ok");
let os = json["os"].as_str().expect("os is a string");
// std::env::consts::OS is always one of the compile-time Rust target OS names.
assert!(!os.is_empty(), "os must be non-empty");
}

#[test]
fn system_info_returns_non_zero_pid() {
let outcome = system_info();
let json = outcome
.into_cli_compatible_json()
.expect("serialization ok");
let pid = json["pid"].as_u64().expect("pid is a u64");
assert!(pid > 0, "pid must be greater than zero");
Comment on lines +52 to +79
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix assertion paths to use the logged RpcOutcome envelope.

Line 57, Line 67, and Line 78 read top-level fields, but system_info() returns RpcOutcome::single_log(...) (Line 44), which serializes as { "result": ..., "logs": [...] }. These assertions will panic on missing keys.

Suggested patch
     fn system_info_returns_non_empty_version() {
         let outcome = system_info();
         let json = outcome
             .into_cli_compatible_json()
             .expect("serialization ok");
-        let version = json["version"].as_str().expect("version is a string");
+        let version = json["result"]["version"]
+            .as_str()
+            .expect("version is a string");
         assert!(!version.is_empty(), "version must be non-empty");
     }
@@
     fn system_info_returns_known_os() {
         let outcome = system_info();
         let json = outcome
             .into_cli_compatible_json()
             .expect("serialization ok");
-        let os = json["os"].as_str().expect("os is a string");
+        let os = json["result"]["os"].as_str().expect("os is a string");
         // std::env::consts::OS is always one of the compile-time Rust target OS names.
         assert!(!os.is_empty(), "os must be non-empty");
     }
@@
     fn system_info_returns_non_zero_pid() {
         let outcome = system_info();
         let json = outcome
             .into_cli_compatible_json()
             .expect("serialization ok");
-        let pid = json["pid"].as_u64().expect("pid is a u64");
+        let pid = json["result"]["pid"].as_u64().expect("pid is a u64");
         assert!(pid > 0, "pid must be greater than zero");
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn system_info_returns_non_empty_version() {
let outcome = system_info();
let json = outcome
.into_cli_compatible_json()
.expect("serialization ok");
let version = json["version"].as_str().expect("version is a string");
assert!(!version.is_empty(), "version must be non-empty");
}
#[test]
fn system_info_returns_known_os() {
let outcome = system_info();
let json = outcome
.into_cli_compatible_json()
.expect("serialization ok");
let os = json["os"].as_str().expect("os is a string");
// std::env::consts::OS is always one of the compile-time Rust target OS names.
assert!(!os.is_empty(), "os must be non-empty");
}
#[test]
fn system_info_returns_non_zero_pid() {
let outcome = system_info();
let json = outcome
.into_cli_compatible_json()
.expect("serialization ok");
let pid = json["pid"].as_u64().expect("pid is a u64");
assert!(pid > 0, "pid must be greater than zero");
fn system_info_returns_non_empty_version() {
let outcome = system_info();
let json = outcome
.into_cli_compatible_json()
.expect("serialization ok");
let version = json["result"]["version"]
.as_str()
.expect("version is a string");
assert!(!version.is_empty(), "version must be non-empty");
}
#[test]
fn system_info_returns_known_os() {
let outcome = system_info();
let json = outcome
.into_cli_compatible_json()
.expect("serialization ok");
let os = json["result"]["os"].as_str().expect("os is a string");
// std::env::consts::OS is always one of the compile-time Rust target OS names.
assert!(!os.is_empty(), "os must be non-empty");
}
#[test]
fn system_info_returns_non_zero_pid() {
let outcome = system_info();
let json = outcome
.into_cli_compatible_json()
.expect("serialization ok");
let pid = json["result"]["pid"].as_u64().expect("pid is a u64");
assert!(pid > 0, "pid must be greater than zero");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/health/ops.rs` around lines 52 - 79, The tests read top-level
keys but system_info() returns an RpcOutcome envelope (created via
RpcOutcome::single_log) so assertions should inspect the "result" field; update
the three tests (system_info_returns_non_empty_version,
system_info_returns_known_os, system_info_returns_non_zero_pid) to extract
version/os/pid from json["result"] (e.g., json["result"]["version"],
json["result"]["os"], json["result"]["pid"]) after calling
into_cli_compatible_json(), keeping the same type checks (.as_str(), .as_u64())
and assertions otherwise unchanged.

}

#[test]
fn health_snapshot_returns_serializable_value() {
let outcome = health_snapshot();
let json = outcome
.into_cli_compatible_json()
.expect("serialization ok");
assert!(json.is_object(), "snapshot must be a JSON object");
}
}
88 changes: 78 additions & 10 deletions src/openhuman/health/schemas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ use crate::core::{ControllerSchema, FieldSchema, TypeSchema};
use crate::rpc::RpcOutcome;

pub fn all_controller_schemas() -> Vec<ControllerSchema> {
vec![schemas("snapshot")]
vec![schemas("snapshot"), schemas("system_info")]
}

pub fn all_registered_controllers() -> Vec<RegisteredController> {
vec![RegisteredController {
schema: schemas("snapshot"),
handler: handle_snapshot,
}]
vec![
RegisteredController {
schema: schemas("snapshot"),
handler: handle_snapshot,
},
RegisteredController {
schema: schemas("system_info"),
handler: handle_system_info,
},
]
}

pub fn schemas(function: &str) -> ControllerSchema {
Expand All @@ -29,6 +35,39 @@ pub fn schemas(function: &str) -> ControllerSchema {
required: true,
}],
},
"system_info" => ControllerSchema {
namespace: "health",
function: "system_info",
description:
"Return static system information: app version, OS, architecture, and PID.",
inputs: vec![],
outputs: vec![
FieldSchema {
name: "version",
ty: TypeSchema::String,
comment: "Running core binary version (CARGO_PKG_VERSION).",
required: true,
},
FieldSchema {
name: "os",
ty: TypeSchema::String,
comment: "Host operating system name (linux, macos, windows, …).",
required: true,
},
FieldSchema {
name: "arch",
ty: TypeSchema::String,
comment: "CPU architecture (x86_64, aarch64, …).",
required: true,
},
FieldSchema {
name: "pid",
ty: TypeSchema::String,
comment: "Current process ID.",
Comment on lines +63 to +66
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align pid schema type with the actual numeric payload.

Line 65 declares pid as TypeSchema::String, but system_info() returns pid: u32 (src/openhuman/health/ops.rs Line 22), and this file’s test reads it as number on Line 164. This schema mismatch can mislead generated clients and validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/health/schemas.rs` around lines 63 - 66, The FieldSchema for
the "pid" field is declared as TypeSchema::String but the actual payload from
system_info() returns a numeric u32 (and tests read it as a number); update the
schema so the FieldSchema entry with name "pid" uses the numeric type (e.g.,
TypeSchema::Number/Integer as your schema enum provides) to match
system_info()'s u32 return and keep tests/clients consistent. Ensure this change
is applied to the FieldSchema instance that defines "pid" and run the
schema-related tests.

required: true,
},
],
},
_ => ControllerSchema {
namespace: "health",
function: "unknown",
Expand All @@ -48,6 +87,10 @@ fn handle_snapshot(_params: Map<String, Value>) -> ControllerFuture {
Box::pin(async { to_json(crate::openhuman::health::rpc::health_snapshot()) })
}

fn handle_system_info(_params: Map<String, Value>) -> ControllerFuture {
Box::pin(async { to_json(crate::openhuman::health::rpc::system_info()) })
}

fn to_json<T: serde::Serialize>(outcome: RpcOutcome<T>) -> Result<Value, String> {
outcome.into_cli_compatible_json()
}
Expand All @@ -57,13 +100,13 @@ mod tests {
use super::*;

#[test]
fn all_schemas_returns_one() {
assert_eq!(all_controller_schemas().len(), 1);
fn all_schemas_returns_two() {
assert_eq!(all_controller_schemas().len(), 2);
}

#[test]
fn all_controllers_returns_one() {
assert_eq!(all_registered_controllers().len(), 1);
fn all_controllers_returns_two() {
assert_eq!(all_registered_controllers().len(), 2);
}

#[test]
Expand All @@ -75,6 +118,16 @@ mod tests {
assert!(!s.outputs.is_empty());
}

#[test]
fn system_info_schema() {
let s = schemas("system_info");
assert_eq!(s.namespace, "health");
assert_eq!(s.function, "system_info");
assert!(s.inputs.is_empty());
// version, os, arch, pid
assert_eq!(s.outputs.len(), 4);
}

#[test]
fn unknown_function_returns_unknown() {
let s = schemas("bad");
Expand All @@ -86,7 +139,10 @@ mod tests {
fn schemas_and_controllers_match() {
let s = all_controller_schemas();
let c = all_registered_controllers();
assert_eq!(s[0].function, c[0].schema.function);
assert_eq!(s.len(), c.len());
for (schema, controller) in s.iter().zip(c.iter()) {
assert_eq!(schema.function, controller.schema.function);
}
}

#[tokio::test]
Expand All @@ -96,6 +152,18 @@ mod tests {
assert!(result.unwrap().is_object());
}

#[tokio::test]
async fn handle_system_info_returns_json_object() {
let result = handle_system_info(Map::new()).await;
assert!(result.is_ok());
let json = result.unwrap();
assert!(json.is_object());
assert!(json["version"].as_str().is_some());
assert!(json["os"].as_str().is_some());
assert!(json["arch"].as_str().is_some());
assert!(json["pid"].as_u64().is_some());
Comment on lines +156 to +164
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Read system_info fields from result in handler test.

Because handle_system_info() serializes a logged RpcOutcome, the payload is wrapped; Line 161–Line 164 should assert against json["result"][...], not top-level keys.

Suggested patch
     async fn handle_system_info_returns_json_object() {
         let result = handle_system_info(Map::new()).await;
         assert!(result.is_ok());
         let json = result.unwrap();
         assert!(json.is_object());
-        assert!(json["version"].as_str().is_some());
-        assert!(json["os"].as_str().is_some());
-        assert!(json["arch"].as_str().is_some());
-        assert!(json["pid"].as_u64().is_some());
+        assert!(json["result"]["version"].as_str().is_some());
+        assert!(json["result"]["os"].as_str().is_some());
+        assert!(json["result"]["arch"].as_str().is_some());
+        assert!(json["result"]["pid"].as_u64().is_some());
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async fn handle_system_info_returns_json_object() {
let result = handle_system_info(Map::new()).await;
assert!(result.is_ok());
let json = result.unwrap();
assert!(json.is_object());
assert!(json["version"].as_str().is_some());
assert!(json["os"].as_str().is_some());
assert!(json["arch"].as_str().is_some());
assert!(json["pid"].as_u64().is_some());
async fn handle_system_info_returns_json_object() {
let result = handle_system_info(Map::new()).await;
assert!(result.is_ok());
let json = result.unwrap();
assert!(json.is_object());
assert!(json["result"]["version"].as_str().is_some());
assert!(json["result"]["os"].as_str().is_some());
assert!(json["result"]["arch"].as_str().is_some());
assert!(json["result"]["pid"].as_u64().is_some());
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/health/schemas.rs` around lines 156 - 164, The test
handle_system_info_returns_json_object is asserting top-level fields but
handle_system_info returns an RpcOutcome-wrapped payload; update the assertions
to read from the nested "result" object (i.e., assert json["result"]["version"],
json["result"]["os"], json["result"]["arch"], and json["result"]["pid"] are
present) when validating the output of handle_system_info(Map::new()).await so
the test inspects the actual payload returned by handle_system_info.

}

#[test]
fn to_json_helper() {
let outcome = RpcOutcome::single_log(serde_json::json!({"ok": true}), "log");
Expand Down
Loading