Skip to content
Merged
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
57 changes: 56 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,18 +138,73 @@
"runtime_recovery_not_active",
"recovery_control_state_invalid",
"recovery_active_binding_invalid",
"authority_source_incomplete",
"authority_file_digest_mismatch",
"authority_file_invalid",
"authority_file_unreadable",
"authority_file_changed",
"authority_json_invalid",
"authority_duplicate_field",
"authority_non_finite_value",
"authority_fields_invalid",
"authority_decision_not_approve",
"authority_scope_not_live",
"strategy_revision_unavailable",
"strategy_revision_mismatch",
"runner_revision_unavailable",
"runner_revision_mismatch",
"config_digest_mismatch",
"runtime_target_missing",
"runtime_target_identity_incomplete",
"runtime_target_identity_ambiguous",
"runtime_target_mismatch",
"authority_expired_or_not_effective",
})
_RUNTIME_ERROR_NOTIFICATION_REASONS = {
"recovery_control_read_failed": "runtime_error_reason_recovery_control_read_failed",
"runtime_recovery_not_active": "runtime_error_reason_recovery_not_active",
"recovery_control_state_invalid": "runtime_error_reason_recovery_control_state_invalid",
"recovery_active_binding_invalid": "runtime_error_reason_recovery_active_binding_invalid",
"runner_revision_mismatch": "runtime_error_reason_runner_revision_mismatch",
}
_AUTHORITY_ERROR_REASON_CODES = {
"authority source parameters are incomplete": "authority_source_incomplete",
"authority file digest mismatch": "authority_file_digest_mismatch",
"authority file must be a regular non-symlink file": "authority_file_invalid",
"authority file size is invalid": "authority_file_invalid",
"authority file cannot be read": "authority_file_unreadable",
"authority file changed while reading": "authority_file_changed",
"invalid authority JSON": "authority_json_invalid",
"duplicate authority field": "authority_duplicate_field",
"non-finite JSON value": "authority_non_finite_value",
"authority fields are unsupported or incomplete": "authority_fields_invalid",
"authority decision is not APPROVE": "authority_decision_not_approve",
"authority scope is not LIVE": "authority_scope_not_live",
"installed strategy revision is unavailable": "strategy_revision_unavailable",
"strategy revision mismatch": "strategy_revision_mismatch",
"runner revision is unavailable": "runner_revision_unavailable",
"runner revision mismatch": "runner_revision_mismatch",
"runner checkout has tracked modifications": "runner_revision_unavailable",
"config digest mismatch": "config_digest_mismatch",
"runtime target is missing": "runtime_target_missing",
"runtime target identity is incomplete": "runtime_target_identity_incomplete",
"runtime target account identity is ambiguous": "runtime_target_identity_ambiguous",
"runtime target mismatch": "runtime_target_mismatch",
"authority expired or not yet effective": "authority_expired_or_not_effective",
}


def _runtime_setup_reason(exc):
reason = str(exc)
return reason if reason in _SAFE_RUNTIME_SETUP_REASONS else "runtime_startup_failed"
if reason in _SAFE_RUNTIME_SETUP_REASONS:
return reason
authority_prefix = "live risk authority configuration invalid: "
if reason.startswith(authority_prefix):
authority_reason = reason[len(authority_prefix):]
authority_reason = _AUTHORITY_ERROR_REASON_CODES.get(authority_reason, authority_reason)
if authority_reason in _SAFE_RUNTIME_SETUP_REASONS:
return authority_reason
return "runtime_startup_failed"


def _load_import_safe_strategy_runtime():
Expand Down
2 changes: 2 additions & 0 deletions notify_i18n_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"runtime_error_reason_recovery_not_active": "Recovery authorization is not active; trading did not start.",
"runtime_error_reason_recovery_control_state_invalid": "Recovery configuration is invalid; trading did not start.",
"runtime_error_reason_recovery_active_binding_invalid": "Recovery authorization binding is invalid; trading did not start.",
"runtime_error_reason_runner_revision_mismatch": "The approved runtime revision does not match this runner; trading did not start.",
"runtime_error_reason_generic": "The runtime could not start safely; inspect the startup diagnostics.",
"runtime_health_monitor_started": "Health monitor started",
"runtime_setup_failed": "Strategy startup failed: runtime_setup_failed",
Expand Down Expand Up @@ -162,6 +163,7 @@
"runtime_error_reason_recovery_not_active": "恢复授权尚未激活,本次未启动交易。",
"runtime_error_reason_recovery_control_state_invalid": "恢复配置状态无效,本次未启动交易。",
"runtime_error_reason_recovery_active_binding_invalid": "恢复授权绑定无效,本次未启动交易。",
"runtime_error_reason_runner_revision_mismatch": "当前运行版本未获得现有风险授权,本次未启动交易。",
"runtime_error_reason_generic": "运行时无法安全启动,请查看启动诊断。",
"runtime_health_monitor_started": "运行监测已启动",
"runtime_setup_failed": "策略启动失败:runtime_setup_failed",
Expand Down
22 changes: 22 additions & 0 deletions tests/test_main_runtime_error_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,28 @@ def test_runtime_error_notification_uses_fixed_human_reason_text(self):
self.assertIn("The runtime could not start safely", message)
self.assertNotIn("untrusted-provider-detail", message)

def test_runtime_setup_reason_projects_safe_authority_reason(self):
self.assertEqual(
main._runtime_setup_reason(
ValueError("live risk authority configuration invalid: runner revision mismatch")
),
"runner_revision_mismatch",
)
self.assertEqual(
main._runtime_setup_reason(
ValueError("live risk authority configuration invalid: provider-secret-detail")
),
"runtime_startup_failed",
)

def test_runtime_error_notification_uses_fixed_authority_reason_text(self):
with patch.dict(os.environ, {"NOTIFY_LANG": "zh-CN", "STRATEGY_PROFILE": "crypto_live_pool_rotation"}):
message = main._runtime_error_notification_message(
RuntimeError("runner_revision_mismatch")
)
self.assertIn("当前运行版本未获得现有风险授权,本次未启动交易。", message)
self.assertNotIn("runner_revision_mismatch", message)

def test_main_reports_only_fixed_recovery_read_reason(self):
observed = []

Expand Down