mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
This commit is contained in:
@@ -103,6 +103,16 @@ pub async fn rpc_handler(State(state): State<AppState>, Json(req): Json<RpcReque
|
||||
"[rpc] expected-user-state error — skipping Sentry: {}",
|
||||
display_message
|
||||
);
|
||||
} else if is_wallet_not_configured_error(&display_message) {
|
||||
// A `tinyplace_*` RPC needs a wallet-derived signer but the user
|
||||
// has not set one up. Expected user-state (the UI shows a
|
||||
// "set up wallet" prompt), not an internal failure — skip Sentry
|
||||
// here so the message is left untouched for direct (agent-tool)
|
||||
// callers. See `is_wallet_not_configured_error`.
|
||||
tracing::info!(
|
||||
method = %method,
|
||||
"[rpc] wallet-not-configured (expected user-state) — skipping Sentry"
|
||||
);
|
||||
} else if is_param_validation_error(&display_message) {
|
||||
tracing::info!(
|
||||
method = %method,
|
||||
@@ -347,6 +357,27 @@ fn is_param_validation_error(msg: &str) -> bool {
|
||||
|| msg.starts_with("invalid params: ")
|
||||
}
|
||||
|
||||
/// Returns `true` when the error is the wallet's "not configured yet" message.
|
||||
///
|
||||
/// Several `tinyplace_*` RPCs derive a signer seed from the wallet before they
|
||||
/// can run (the feed, signal/messaging, etc. — backend `GraphQLAuth::Agent`
|
||||
/// requires a signer). For a user who has not set up a wallet, the wallet layer
|
||||
/// returns [`crate::openhuman::wallet::WALLET_NOT_CONFIGURED_MESSAGE`]. That is
|
||||
/// an expected user-state, not an internal failure: the UI already renders a
|
||||
/// "set up wallet" prompt, and there is no local lever to make the call succeed
|
||||
/// until the user creates a wallet. Classifying it here — at the single Sentry
|
||||
/// boundary — keeps it out of Sentry for *every* path that surfaces it (the
|
||||
/// shared client builder and the direct `signal_store` seed call alike) without
|
||||
/// the controllers returning a structured envelope, which would leak the raw
|
||||
/// sentinel string to agent tools that call those handlers directly.
|
||||
///
|
||||
/// Matched against the shared wallet constant (exact equality) so a wording
|
||||
/// change in the wallet layer fails the coupling test in `jsonrpc_tests.rs`
|
||||
/// rather than silently letting the noise back into Sentry.
|
||||
fn is_wallet_not_configured_error(msg: &str) -> bool {
|
||||
msg == crate::openhuman::wallet::WALLET_NOT_CONFIGURED_MESSAGE
|
||||
}
|
||||
|
||||
/// Internal method invocation logic.
|
||||
///
|
||||
/// It first attempts to match the method name against the static controller
|
||||
|
||||
@@ -7,8 +7,8 @@ use tokio_util::sync::CancellationToken;
|
||||
|
||||
use super::{
|
||||
build_http_schema_dump, default_state, escape_html, invoke_method, is_param_validation_error,
|
||||
is_session_expired_error, is_unconfirmed_unauthorized_error, params_to_object,
|
||||
parse_json_params, rpc_handler, type_name,
|
||||
is_session_expired_error, is_unconfirmed_unauthorized_error, is_wallet_not_configured_error,
|
||||
params_to_object, parse_json_params, rpc_handler, type_name,
|
||||
};
|
||||
|
||||
struct EnvVarGuard {
|
||||
@@ -1407,3 +1407,40 @@ async fn desktop_auth_rejects_embedded_fetch_metadata() {
|
||||
let body = String::from_utf8(body.to_vec()).expect("html body should be utf8");
|
||||
assert!(body.contains("must be opened as a browser page"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_wallet_not_configured_error_matches_wallet_constant() {
|
||||
// The classifier keys off the wallet layer's exact "not configured"
|
||||
// message so a wallet-less user's tinyplace RPC stays out of Sentry.
|
||||
assert!(is_wallet_not_configured_error(
|
||||
crate::openhuman::wallet::WALLET_NOT_CONFIGURED_MESSAGE
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_wallet_not_configured_error_is_coupled_to_the_wallet_constant() {
|
||||
// Drift guard: if the wallet wording changes without updating the shared
|
||||
// constant the classifier matches, this fails — preventing the noise from
|
||||
// silently returning to Sentry. Mirrors the param-validation prefix locks.
|
||||
assert_eq!(
|
||||
crate::openhuman::wallet::WALLET_NOT_CONFIGURED_MESSAGE,
|
||||
"wallet is not configured; run wallet setup first"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_wallet_not_configured_error_does_not_match_other_errors() {
|
||||
// Other wallet/seed-derivation failures (decrypt, key derivation, locked
|
||||
// keychain) are real defects and must keep reaching Sentry.
|
||||
assert!(!is_wallet_not_configured_error(
|
||||
"tinyplace signer init: bad seed"
|
||||
));
|
||||
assert!(!is_wallet_not_configured_error(
|
||||
"decrypt secret: kms timeout"
|
||||
));
|
||||
assert!(!is_wallet_not_configured_error(""));
|
||||
// Substring-only must not qualify — exact equality is required.
|
||||
assert!(!is_wallet_not_configured_error(
|
||||
"rpc failed: wallet is not configured; run wallet setup first"
|
||||
));
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(crate) use execution::{sign_and_broadcast_evm, sign_and_broadcast_solana};
|
||||
pub(crate) use ops::secret_material;
|
||||
pub use ops::{
|
||||
reveal_recovery_phrase, setup, status, RevealRecoveryPhraseResult, WalletAccount, WalletChain,
|
||||
WalletSetupParams, WalletSetupSource, WalletStatus,
|
||||
WalletSetupParams, WalletSetupSource, WalletStatus, WALLET_NOT_CONFIGURED_MESSAGE,
|
||||
};
|
||||
pub use schemas::{
|
||||
all_controller_schemas, all_registered_controllers, all_wallet_controller_schemas,
|
||||
|
||||
@@ -17,6 +17,14 @@ use crate::rpc::RpcOutcome;
|
||||
|
||||
const LOG_PREFIX: &str = "[wallet]";
|
||||
const WALLET_STATE_FILENAME: &str = "wallet-state.json";
|
||||
/// Error message returned when the wallet has not been set up yet.
|
||||
///
|
||||
/// This is an expected user-state (the user simply has not created a wallet),
|
||||
/// not an internal failure. Downstream boundaries that surface this condition
|
||||
/// — e.g. the `tinyplace` client builder — match against this constant to
|
||||
/// classify it as `expected_user_state` so it stays out of Sentry. Keep it a
|
||||
/// shared constant so the producer here and any classifier cannot drift apart.
|
||||
pub const WALLET_NOT_CONFIGURED_MESSAGE: &str = "wallet is not configured; run wallet setup first";
|
||||
const VALID_MNEMONIC_WORD_COUNTS: [u8; 5] = [12, 15, 18, 21, 24];
|
||||
/// Keychain key for the encrypted mnemonic blob (user_id is added by the keyring module).
|
||||
const KEYCHAIN_MNEMONIC_KEY: &str = "wallet.mnemonic";
|
||||
@@ -738,7 +746,7 @@ pub(crate) async fn secret_material(chain: WalletChain) -> Result<WalletSecretMa
|
||||
"{LOG_PREFIX} secret_material missing wallet state chain={}",
|
||||
chain.as_str()
|
||||
);
|
||||
return Err("wallet is not configured; run wallet setup first".to_string());
|
||||
return Err(WALLET_NOT_CONFIGURED_MESSAGE.to_string());
|
||||
}
|
||||
};
|
||||
let encrypted_mnemonic = state
|
||||
|
||||
Reference in New Issue
Block a user