Clean up lint/LSP across gateway, Android, and desktop (alpha -> stable)

Gateway (gateway-plugin/):
- Fix interactive_setup broken imports: print helpers were imported from the
  wrong hermes module (hermes_cli.config instead of hermes_cli.cli_output) plus
  a non-existent print_code; the try/except swallowed the ImportError so
  `hermes gateway setup` for android always bailed out early.
- Fix release_scoped_lock type error (str | None passed where str required).
- Rewrite empty `except: pass` blocks as contextlib.suppress with rationale.
- Restructure two ambiguous ws_server try blocks (hello-auth, frame loop).
- Ruff cleanup: type annotations, import sorting, line wrapping, magic values
  -> named constants, `raise ... from e`, complexity. Add gateway-plugin/ruff.toml.
- Add pyrightconfig.json so the Python LSP resolves hermes-runtime imports.
- Suppress verified false positives inline (parameterized SQL, column-name
  "secrets", hermes-generated media path).

Android (app/androidApp + app/shared):
- Consolidate launcher icons into a single mipmap-anydpi (minSdk 29 >= 26) with
  the monochrome layer; clears ObsoleteSdkInt + MonochromeLauncherIcon.
- Bump core-splashscreen 1.0.1 -> 1.2.0; pin targetSdk 34 (deliberate).
- Suppress verified findings inline (LAN ws:// default, correct GCM IV usage).

Desktop (app/desktopApp):
- Move the desktop to a Java 21 runtime (org.gradle.java.home) and set the
  desktop jvmTarget to 21 (Android stays JVM 17 / minSdk 29). Fixes the startup
  UnsupportedClassVersionError and restores Markdown renderer 0.44.0.

Tooling/config:
- .pi-lens.json: disable verified-noisy heuristics (documented in docs).
- .gitleaks.toml: allowlist git-ignored false-positive paths.
- docs/18-code-review.md: full findings + verification.

Verified: ruff clean, pyright 0 errors, 64/64 gateway tests, all Kotlin tests,
Android lint 0 issues, Android installed+launched on device, desktop launches
on JDK 21.
This commit is contained in:
ARIA committed 2026-08-21 18:47:03 +02:00
1 parent 9f3f9842c8
commit 678c0344c8
27 files changed
+928 -454

No files matched your search

+35 -26
View File
@@ -25,7 +25,7 @@ import logging
import threading
import time
from pathlib import Path
from typing import Any, Dict, Optional
from typing import Any
from urllib.parse import quote
import httpx
@@ -41,6 +41,9 @@ _TOKEN_REFRESH_MARGIN_S = 600.0
_DEFAULT_NTFY_SERVER = "https://ntfy.sh"
_NTFY_BODY_LIMIT = 4096
_HTTP_TIMEOUT_S = 15.0
# HTTP status boundaries: 200 == success; >= 300 == redirect/error range.
_HTTP_OK = 200
_HTTP_ERROR_MIN = 300
_NTFY_PRIORITY = {"high": "5", "normal": "3", "low": "1"}
@@ -50,6 +53,8 @@ class PushBackend:
name: str = "push"
# DeviceRegistry column that carries this backend's target token.
# Not a secret: a DB column name (string literal), not a credential.
# pi-lens-ignore: python-hardcoded-secrets
token_field: str = ""
def configured(self) -> bool:
@@ -63,7 +68,7 @@ class PushBackend:
chat_id: str,
title: str,
body: str,
data: Dict[str, Any],
data: dict[str, Any],
token: str,
priority: str = "normal",
data_only: bool = False,
@@ -81,18 +86,20 @@ class FcmBackend(PushBackend):
"""FCM HTTP v1 (service account) or legacy ``/fcm/send`` (server key)."""
name = "fcm"
# Not a secret: a DB column name (string literal), not a credential.
# pi-lens-ignore: python-hardcoded-secrets
token_field = "fcm_token"
def __init__(
self,
service_account: Optional[str] = None,
server_key: Optional[str] = None,
service_account: str | None = None,
server_key: str | None = None,
):
self._sa_path = (service_account or "").strip() or None
self._server_key = (server_key or "").strip() or None
self._sa: Optional[Dict[str, Any]] = None
self._sa: dict[str, Any] | None = None
self._sa_failed = False
self._access_token: Optional[str] = None
self._access_token: str | None = None
self._token_expiry = 0.0
self._lock = threading.Lock()
@@ -101,13 +108,13 @@ class FcmBackend(PushBackend):
return True
return bool(self._sa_path and Path(self._sa_path).is_file())
def _load_sa(self) -> Optional[Dict[str, Any]]:
def _load_sa(self) -> dict[str, Any] | None:
if self._sa is not None:
return self._sa
if not self._sa_path or self._sa_failed:
return None
try:
with open(self._sa_path, "r", encoding="utf-8") as f:
with open(self._sa_path, encoding="utf-8") as f:
sa = json.load(f)
if isinstance(sa, dict) and sa.get("client_email") and sa.get("private_key"):
self._sa = sa
@@ -117,7 +124,7 @@ class FcmBackend(PushBackend):
self._sa_failed = True
return None
async def _authorization(self, client: httpx.AsyncClient) -> Optional[str]:
async def _authorization(self, client: httpx.AsyncClient) -> str | None:
"""Bearer token: the legacy server key, or a cached service-account
OAuth2 access token (JWT-bearer grant, minted with PyJWT)."""
if self._server_key:
@@ -158,7 +165,7 @@ class FcmBackend(PushBackend):
except Exception:
logger.warning("android: FCM token exchange failed", exc_info=True)
return None
if resp.status_code != 200:
if resp.status_code != _HTTP_OK:
logger.warning(
"android: FCM token exchange HTTP %s: %s",
resp.status_code, resp.text[:200],
@@ -186,7 +193,7 @@ class FcmBackend(PushBackend):
chat_id: str,
title: str,
body: str,
data: Dict[str, Any],
data: dict[str, Any],
token: str,
priority: str = "normal",
data_only: bool = False,
@@ -197,7 +204,7 @@ class FcmBackend(PushBackend):
notification = None if data_only else {"title": title or "Iris", "body": body or ""}
async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT_S) as client:
if self._server_key:
payload: Dict[str, Any] = {"to": token}
payload: dict[str, Any] = {"to": token}
if notification:
payload["notification"] = notification
if data:
@@ -209,7 +216,7 @@ class FcmBackend(PushBackend):
project_id = (sa or {}).get("project_id")
if not project_id:
return False
message: Dict[str, Any] = {"token": token}
message: dict[str, Any] = {"token": token}
if notification:
message["notification"] = notification
if data:
@@ -231,7 +238,7 @@ class FcmBackend(PushBackend):
except Exception:
logger.warning("android: FCM send failed (network)", exc_info=True)
return False
if resp.status_code >= 300:
if resp.status_code >= _HTTP_ERROR_MIN:
# 404 NOT_FOUND = stale/invalid registration token.
logger.warning(
"android: FCM send HTTP %s: %s", resp.status_code, resp.text[:200]
@@ -249,13 +256,15 @@ class NtfyBackend(PushBackend):
"""
name = "ntfy"
# Not a secret: a DB column name (string literal), not a credential.
# pi-lens-ignore: python-hardcoded-secrets
token_field = "ntfy_topic"
def __init__(
self,
topic: Optional[str] = None,
server_url: Optional[str] = None,
auth_token: Optional[str] = None,
topic: str | None = None,
server_url: str | None = None,
auth_token: str | None = None,
):
self._topic = (topic or "").strip() or None
self._server = (
@@ -279,7 +288,7 @@ class NtfyBackend(PushBackend):
chat_id: str,
title: str,
body: str,
data: Dict[str, Any],
data: dict[str, Any],
token: str,
priority: str = "normal",
data_only: bool = False,
@@ -306,7 +315,7 @@ class NtfyBackend(PushBackend):
except Exception:
logger.warning("android: ntfy publish failed (network)", exc_info=True)
return False
if resp.status_code >= 300:
if resp.status_code >= _HTTP_ERROR_MIN:
logger.warning(
"android: ntfy publish HTTP %s: %s", resp.status_code, resp.text[:200]
)
@@ -315,17 +324,17 @@ class NtfyBackend(PushBackend):
def build_push_backend(
name: Optional[str],
name: str | None,
*,
fcm_service_account: Optional[str] = None,
fcm_server_key: Optional[str] = None,
ntfy_topic: Optional[str] = None,
ntfy_server_url: Optional[str] = None,
ntfy_auth_token: Optional[str] = None,
fcm_service_account: str | None = None,
fcm_server_key: str | None = None,
ntfy_topic: str | None = None,
ntfy_server_url: str | None = None,
ntfy_auth_token: str | None = None,
) -> PushBackend:
"""Select the backend by name (``ANDROID_PUSH_BACKEND``; fcm default)."""
if (name or "").strip().lower() == "ntfy":
return NtfyBackend(
topic=ntfy_topic, server_url=ntfy_server_url, auth_token=ntfy_auth_token
)
return FcmBackend(service_account=fcm_service_account, server_key=fcm_server_key)
return FcmBackend(service_account=fcm_service_account, server_key=fcm_server_key)