Skip to content

Release 0.1.4#35

Open
Josephrp wants to merge 42 commits intomainfrom
dev
Open

Release 0.1.4#35
Josephrp wants to merge 42 commits intomainfrom
dev

Conversation

@Josephrp
Copy link
Copy Markdown
Owner

@Josephrp Josephrp commented Mar 14, 2026

Greptile Summary

Release 0.1.4 is a large feature release adding emergency coordination (§9) with SSE streams and approve/reject workflows, Twilio SMS/WhatsApp webhooks with opt-out handling, a pluggable compliance plugin system covering FCC/CEPT/AU/CA/JP/IN/NZ and ~30 regional backends, pluggable ASR/TTS plugin registries (Voxtral, Whisper, Scribe, ElevenLabs, Kokoro), a remote SDR broker for HackRF TX, occupied-bandwidth compliance checking, new GIS endpoints, and contact preferences with explicit-consent gating for EU/UK/ZA regions.

Key issues found:

  • Critical — radioshaq/loguru/__init__.py shadows real loguru package: A no-op logger stub placed at radioshaq/loguru/__init__.py will be resolved before the installed loguru package whenever radioshaq/ is in sys.path (development, editable installs, pytest), silencing all logging in production and tests.
  • Compliance — Twilio STOP opt-out silently discarded on DB failure: twilio.py acknowledges the opt-out to the user even when the database call fails, meaning the opt-out is not persisted and future messages will still be sent, violating TCPA/CASL opt-out obligations.
  • API contract — approve returns HTTP 200 ok=True when approval rolls back to pending: When the message bus is unavailable, the event is rolled back but the response still signals ok: true, making it impossible for callers to distinguish a successful approval from a failed one.
  • Stale project name — shakods/ model-ID prefix in ASR plugin: The _is_voxtral_like_model_id function still contains s.startswith("shakods/"), a leftover from the SHAKODS → RadioShaq rename made in the same PR.
  • Performance — pending count uses full row fetch instead of COUNT(*): _get_pending_emergency_count fetches up to 1000 full ORM rows every 10 seconds (SSE tick) to count them; a SELECT COUNT(*) query would be far more efficient.

Confidence Score: 2/5

  • Not safe to merge as-is — the loguru stub can silence all production logging, and the Twilio opt-out failure represents a regulatory compliance risk.
  • Two critical issues block merge: (1) radioshaq/loguru/__init__.py placed in the top-level project directory shadows the real loguru package in every development and CI environment, turning all log calls into no-ops silently; (2) STOP opt-out acknowledgement sent even when DB persistence fails, which is a TCPA/CASL compliance violation. Several other previously-flagged issues (null dereference in get_latest_location_decoded, TOCTOU in update_coordination_event, GET handler mutating shared state, TX success flag) remain unaddressed from the previous review cycle. The XSS fixes via escapeHtml and the atomic claim_emergency_event_pending are positive steps.
  • radioshaq/loguru/__init__.py (critical — shadows real loguru), radioshaq/radioshaq/api/routes/twilio.py (opt-out compliance), radioshaq/radioshaq/api/routes/emergency.py (approve status contract), radioshaq/radioshaq/api/routes/config_routes.py (still mutates shared state on GET)

Important Files Changed

Filename Overview
radioshaq/loguru/init.py New no-op logger stub placed in a location that shadows the real installed loguru package, silencing all production and development logging.
radioshaq/radioshaq/api/routes/emergency.py New emergency coordination endpoint (§9); TOCTOU claim pattern is correct, but approve returns HTTP 200 ok=True even when the message bus is unavailable and the approval is rolled back.
radioshaq/radioshaq/api/routes/twilio.py New Twilio webhook handler with signature validation; STOP opt-out DB failures are silently swallowed while confirming success to the user, creating a regulatory compliance risk.
radioshaq/radioshaq/compliance_plugin/init.py New compliance plugin registry with FCC, CEPT, and regional backends; clean pluggable architecture replacing the hardcoded FCC-only list in radio/compliance.py.
radioshaq/radioshaq/audio/asr_plugin/init.py New pluggable ASR backend registry (Voxtral, Whisper, Scribe); contains a stale shakods/ model-ID prefix leftover from the project rename.
radioshaq/radioshaq/database/postgres_gis.py Significant additions: get_latest_location_decoded, claim_emergency_event_pending (atomic), update_coordination_event, get_pending_coordination_events; pending count uses O(N) full-row fetch instead of COUNT(*), and null geometry dereference in get_latest_location_decoded remains.
radioshaq/web-interface/src/utils/escapeHtml.ts New HTML escaping utility introduced to fix previously reported XSS in Google Maps InfoWindows; correctly escapes &, <, >, ", and '.
radioshaq/radioshaq/orchestrator/outbound_dispatcher.py New single outbound consumer with retry logic; dispatches by channel (radio, SMS, WhatsApp) with dead-letter handling and emergency event stamping after confirmed delivery.
radioshaq/radioshaq/radio/sdr_tx.py Refactored with shared _ComplianceCheckedTransmitter base class, occupied-bandwidth compliance check, remote broker TX, and pyhackrf2 dependency; _audit now accepts a success flag addressing prior silent-failure concern.
radioshaq/radioshaq/api/routes/config_routes.py Added X-Config-Effective-After header and _meta field; adds huggingface_api_key and gemini_api_key to redacted keys set; GET handler still mutates the shared override dict in-place (previously flagged, not fixed).

Sequence Diagram

sequenceDiagram
    participant UI as Operator UI
    participant API as FastAPI (emergency.py)
    participant DB as PostGIS (postgres_gis.py)
    participant Bus as MessageBus (nanobot)
    participant Disp as OutboundDispatcher
    participant Twilio as Twilio SMS/WhatsApp

    UI->>API: POST /emergency/request
    API->>API: emergency_messaging_allowed(region, config)
    API->>API: normalize_e164(contact_phone)
    API->>DB: store_coordination_event(status=pending)
    DB-->>API: event_id
    API-->>UI: {ok: true, event_id, status: pending}

    Note over UI,API: Operator reviews pending emergencies

    UI->>API: GET /emergency/events/stream (SSE)
    loop Every 10s
        API->>DB: get_pending_coordination_events(max_results=1000)
        DB-->>API: events list
        API-->>UI: data: {pending_count: N}
    end

    UI->>API: POST /emergency/events/{id}/approve
    API->>DB: claim_emergency_event_pending(id) — atomic UPDATE WHERE status=pending
    DB-->>API: event_id (or None if already processed)
    API->>DB: get_coordination_event_by_id_raw(id)
    DB-->>API: {extra_data: {phone, channel}}
    API->>Bus: publish_outbound(OutboundMessage channel=sms|whatsapp)
    Bus-->>API: ok=True
    API->>DB: update_coordination_event(status=approved, approved_at/by)
    API-->>UI: {ok: true, status: approved, queued: true}

    Note over Bus,Disp: Async consumer loop

    Disp->>Bus: consume_outbound()
    Bus-->>Disp: OutboundMessage(channel=sms, phone, content)
    Disp->>Disp: agent_registry.get_agent("sms")
    Disp->>Twilio: sms_agent.execute({action: send, to, message})
    Twilio-->>Disp: success
    Disp->>DB: update_coordination_event(sent_at)
Loading

Comments Outside Diff (4)

  1. radioshaq/radioshaq/config/schema.py, line 171-174 (link)

    Security: Hardcoded default JWT secret key

    secret_key: str = Field(
        default="dev-secret-change-in-production",
        ...
    )

    This is a well-known anti-pattern. If a deployment fails to override this value, all JWT tokens are signed with a publicly known secret, allowing any attacker to forge authentication tokens. While the comment says "must be secure in production", there is no runtime check or warning.

    Consider:

    1. Removing the default entirely so the application refuses to start without an explicit secret.
    2. Or at minimum, adding a startup validator that warns/errors when the default value is still in use and debug=False.
  2. radioshaq/radioshaq/config/schema.py, line 126-129 (link)

    Default postgres URL contains hardcoded credentials

    postgres_url: str = Field(
        default="postgresql+asyncpg://radioshaq:radioshaq@127.0.0.1:5434/radioshaq",
        ...
    )

    The default includes username radioshaq and password radioshaq. While this is only a default for local development, having credentials in source code makes it easy for them to leak into production if the env var override is missed. Consider using a default URL without credentials (e.g., just the connection string structure) or documenting this more prominently.

  3. radioshaq/web-interface/src/services/radioshaqApi.ts, line 18-21 (link)

    API token baked into every frontend bundle via VITE_ environment variable

    VITE_RADIOSHAQ_TOKEN is embedded at build time into the JavaScript bundle by Vite (all VITE_* env vars are inlined). This means any user who opens the browser dev tools can extract the Bearer token from the compiled JS source. If this token has elevated privileges (e.g., admin/operator role), it grants those privileges to anyone who inspects the page.

    For a local-only dev tool this may be acceptable, but the code structure (auth headers on every request, SSE streams, emergency approve/reject) suggests this is intended to be deployed to real operator stations over a network. In that case, the token should be acquired via a proper login flow (the getToken endpoint already exists at line 29) and stored in memory only, not baked into the bundle.

    Recommendation: Remove VITE_RADIOSHAQ_TOKEN from the build-time env. Use the existing getToken + setApiToken runtime flow exclusively. If a shortcut is needed for development, at least document that it must never be used in production.

  4. radioshaq/radioshaq/api/routes/bus.py, line 24-28 (link)

    No authentication on inbound bus endpoint

    POST /bus/inbound has no Depends(get_current_user) dependency, unlike the opt_out endpoint in the same file (line 69). Any network-reachable client can inject arbitrary messages into the message bus and orchestrator pipeline without authentication.

    If this is intentional for Lambda-to-API communication, consider adding an internal service verification or network-level restriction. Otherwise, add JWT auth similar to the opt_out endpoint.

Last reviewed commit: 4960690

Greptile also left 3 inline comments on this PR.

Josephrp and others added 30 commits March 5, 2026 15:22
… license gates, and split stable/nightly PyPI release lanes (#16)

* adds docs site and ci docs build

* Stop tracking ancient-docs (reference-only, in .gitignore)

* Ignore site/ (MkDocs build output), stop tracking

* resolves url , site/ ignore and uv install security

* adds dashboard

* removes stray md

* adds memory system

* adds memory system (#6)

* adds memory system

* fix failing tests in ci

* fix failing tests in ci

* adds trusted publisher

* adds multiband and relay support

* adds multiband and relay support (#7)

* adds multiband and relay support

* Update radioshaq/radioshaq/listener/relay_delivery.py

* update tests

* adds dbstate __getattr__

* docs ci update pinned mkdocs version

* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* Add frequency-aware radio replies (#8)

* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* adds ci fixes

* Update radioshaq/radioshaq/api/routes/messages.py

* solves code review suggestions

* fixes failing tests

* Fixes PyPI UI bundling flow (#12)

* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* adds ci fixes

* Update radioshaq/radioshaq/api/routes/messages.py

* solves code review suggestions

* fixes failing tests

* fixes build patch

* fixes minor issues

* fixes build env bug (#13)

* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* adds ci fixes

* Update radioshaq/radioshaq/api/routes/messages.py

* solves code review suggestions

* fixes failing tests

* fixes build patch

* fixes minor issues

* fixes ci issue

* Update radioshaq/tests/integration/test_live_e2e_workflows.py

* fixes ci issue (#14)

* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* adds ci fixes

* Update radioshaq/radioshaq/api/routes/messages.py

* solves code review suggestions

* fixes failing tests

* fixes build patch

* fixes minor issues

* fixes ci issue

* Update radioshaq/tests/integration/test_live_e2e_workflows.py

* Fix settings TS build and include web UI in wheel

* adds release , versioning and workflows

* adds ci , build , enfore branch protection and more

* adds contributing

* chore: bump version to 0.1.1

* fix(ci): allow non-interactive GPL acceptance in CLI setup tests

* fix(ci): harden publish/governance workflows and refresh action versions

* Fix CI branch guards, release push race handling, and license/version checks

* adds contributing

* adds validation fixes
* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* adds ci fixes

* Update radioshaq/radioshaq/api/routes/messages.py

* solves code review suggestions

* fixes failing tests

* fixes build patch

* fixes minor issues

* fixes ci issue

* Update radioshaq/tests/integration/test_live_e2e_workflows.py

* Fix settings TS build and include web UI in wheel

* adds release , versioning and workflows

* adds ci , build , enfore branch protection and more

* adds contributing

* chore: bump version to 0.1.1

* fix(ci): allow non-interactive GPL acceptance in CLI setup tests

* fix(ci): harden publish/governance workflows and refresh action versions

* Fix CI branch guards, release push race handling, and license/version checks

* adds contributing

* adds validation fixes

* adds contributing , license gate , ci build and release , pre push testing

* adds docs site and ci docs build

* Stop tracking ancient-docs (reference-only, in .gitignore)

* Ignore site/ (MkDocs build output), stop tracking

* resolves url , site/ ignore and uv install security

* adds dashboard

* removes stray md

* adds memory system

* adds memory system (#6)

* adds memory system

* fix failing tests in ci

* fix failing tests in ci

* adds trusted publisher

* adds multiband and relay support

* adds multiband and relay support (#7)

* adds multiband and relay support

* Update radioshaq/radioshaq/listener/relay_delivery.py

* update tests

* adds dbstate __getattr__

* docs ci update pinned mkdocs version

* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* Add frequency-aware radio replies (#8)

* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* adds ci fixes

* Update radioshaq/radioshaq/api/routes/messages.py

* solves code review suggestions

* fixes failing tests

* Fixes PyPI UI bundling flow (#12)

* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* adds ci fixes

* Update radioshaq/radioshaq/api/routes/messages.py

* solves code review suggestions

* fixes failing tests

* fixes build patch

* fixes minor issues

* fixes build env bug (#13)

* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* adds ci fixes

* Update radioshaq/radioshaq/api/routes/messages.py

* solves code review suggestions

* fixes failing tests

* fixes build patch

* fixes minor issues

* fixes ci issue

* Update radioshaq/tests/integration/test_live_e2e_workflows.py

* fixes ci issue (#14)

* adds radio frequency

* adds radio outbound reply default

* adds ci test on pr

* solves frequency and send tx race conditions

* fixes pending entries & mode clobbering (fm)

* fixes configuration hardcoded path and fixed reject path misleading error , cleans up pending list api semantics

* resolves code review issues

* adds ci fixes

* Update radioshaq/radioshaq/api/routes/messages.py

* solves code review suggestions

* fixes failing tests

* fixes build patch

* fixes minor issues

* fixes ci issue

* Update radioshaq/tests/integration/test_live_e2e_workflows.py

* Fix settings TS build and include web UI in wheel

* adds release , versioning and workflows

* adds ci , build , enfore branch protection and more

* adds contributing

* chore: bump version to 0.1.1

* fix(ci): allow non-interactive GPL acceptance in CLI setup tests

* fix(ci): harden publish/governance workflows and refresh action versions

* Fix CI branch guards, release push race handling, and license/version checks

* adds contributing

* adds validation fixes

* Update GitHub Sponsors username in FUNDING.yml

* Update .githooks/pre-push

* adds license fallbacks for installed package
* readme improvements and readthedocs ci

* remove broken link
@Josephrp
Copy link
Copy Markdown
Owner Author

@greptileai , comprehensively update your review based on the changes above :

Comment thread radioshaq/radioshaq/llm/client.py
Comment thread radioshaq/radioshaq/llm/client.py Outdated
Comment thread radioshaq/radioshaq/database/dynamodb.py
Comment thread radioshaq/radioshaq/database/models.py
Comment thread radioshaq/radioshaq/database/models.py
Comment thread radioshaq/radioshaq/radio/sdr_tx.py
Comment thread radioshaq/radioshaq/api/routes/config_routes.py
Comment thread radioshaq/radioshaq/llm/client.py Outdated
Comment thread radioshaq/radioshaq/database/models.py
Comment thread radioshaq/web-interface/src/services/radioshaqApi.ts
@Josephrp
Copy link
Copy Markdown
Owner Author

@greptileai , comprehensively update your review based on the recent changes made , investigate all relevant in diff and out of diff revisions , produce a new flowchart accordingly , produce a comprehensive review and update :

Comment on lines +1 to +25
"""Minimal loguru stub for environments without the real dependency.

This stub provides a drop-in `logger` object with no-op methods so that
code importing `from loguru import logger` continues to run in test and
development environments where loguru is not installed.
"""

from __future__ import annotations

from typing import Any


class _Logger:
def __getattr__(self, name: str):
def _noop(*args: Any, **kwargs: Any) -> None:
return None

return _noop

def __call__(self, *args: Any, **kwargs: Any) -> None: # pragma: no cover - trivial
return None


logger = _Logger()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stub module shadows the real loguru package, silencing all logging

This file creates a local Python package named loguru at radioshaq/loguru/__init__.py. Because the radioshaq/ top-level directory is typically in sys.path during development, testing (pytest run from radioshaq/), and editable installs (pip install -e . / uv sync), Python's import resolver will find this stub before the installed loguru package. The result is that every from loguru import logger call throughout the entire codebase silently resolves to the no-op _Logger stub, dropping all log output in production.

The file comment says "for environments without the real dependency", but the placement makes it active even when loguru is installed, defeating its stated purpose.

Fix: Guard the stub so it only activates when loguru is genuinely absent, or relocate it out of the top-level directory. For example, wrap it at the call site:

try:
    from loguru import logger
except ImportError:
    from radioshaq._loguru_stub import logger

Or rename/move the file so it does not shadow the installed package (e.g. radioshaq/radioshaq/_loguru_stub.py).

Comment on lines +145 to +152
"signature_validated": signature_validated,
}

# Opt-out handling (STOP): record directly if DB available, then acknowledge.
if body.upper() in _OPTOUT_KEYWORDS:
db = getattr(request.app.state, "db", None)
if db is not None and hasattr(db, "record_opt_out_by_phone"):
try:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

STOP opt-out silently discarded on DB failure — regulatory compliance risk

When a STOP message arrives and db.record_opt_out_by_phone raises an exception, the error is swallowed and the user receives "You have been opted out" even though their opt-out was never persisted. Subsequent messages will continue to be sent to that number.

Under TCPA (US), CASL (CA), and similar regulations, silently failing to honour an opt-out while confirming it to the user is a compliance violation.

try:
    await db.record_opt_out_by_phone(from_phone, channel)
except Exception as e:
    logger.warning("Opt-out record failed ...")
# ← success response sent anyway, opt-out not recorded
return _twiml_response("You have been opted out. Reply START to re-subscribe.")

Consider returning a generic error TwiML (or no response, which causes Twilio to retry) when the opt-out cannot be recorded, so the user can try again:

try:
    await db.record_opt_out_by_phone(from_phone, channel)
    return _twiml_response("You have been opted out. Reply START to re-subscribe.")
except Exception as e:
    logger.error("Opt-out record failed (channel={} phone={}): {}", channel, from_phone, e)
    # Return 500 so Twilio retries, or at minimum do not confirm success
    raise HTTPException(status_code=500, detail="Opt-out could not be recorded")

Comment on lines +183 to +188
raise HTTPException(status_code=503, detail="Database not available")
if not (hasattr(db, "get_coordination_event_by_id_raw") or hasattr(db, "get_coordination_event_by_id")):
raise HTTPException(status_code=503, detail="Database not available")
get_event = getattr(db, "get_coordination_event_by_id_raw", db.get_coordination_event_by_id)
# Atomic claim: only one concurrent approval can transition pending -> approving
claimed = await db.claim_emergency_event_pending(event_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

approve returns HTTP 200 ok=True with status=pending when approval actually failed

When the message bus is unavailable, the event is correctly rolled back to "pending", but the response is:

{"ok": true, "event_id": ..., "status": "pending", "sent": false, "detail": "Message bus not available"}

From the caller's perspective, they invoked POST /emergency/events/{id}/approve and received ok: true — yet the event was not approved and no message was sent. This is misleading and could cause the UI to show "approved" state while the event remains unactioned.

The same pattern exists in the publish_outbound failure branch (lines 193-199) and the queue-full branch (lines 200-203).

Consider returning a non-200 status code (e.g. HTTP 503) when the approval cannot be completed, so callers can distinguish a successful approval from a failed one:

if not message_bus or not hasattr(message_bus, "publish_outbound"):
    await db.update_coordination_event(event_id, status="pending")
    raise HTTPException(
        status_code=503,
        detail="Message bus not available; approval rolled back to pending",
    )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant