## Implementation Plan: Admin Notifications on Quota Exhaustion ### Objective Add admin notifications when a user hits their Devii daily quota, closing the feature gap identified in the investigation. The existing reset mechanisms (per-user, bulk, CLI) will remain unchanged and sufficient. ### Scope of Changes Only two files require modifications: - `routers/devii.py` (line ~265-276) – WebSocket entry point - `services/telegram/bridge.py` (line ~222-233) – Telegram entry point No new endpoints, no config changes, no database schema changes. The `create_notification` utility already exists at `utils/notifications.py:61-75` and is async-safe. --- ### Step 1: Add `_notify_admins_on_quota_block` helper Create a shared helper function in a common module (e.g., `services/devii/notifications.py` – new file, or inline in both files). To minimise disruption, add the helper directly in each file after the existing quota-block logic. The investigation says to copy the pattern from `routers/issues/comment.py:36-46`. That pattern iterates over all admins and calls `create_notification`. The helper should accept: - `user_uid` (str) – the blocked user's UID - `user_name` (str) – for the notification message - `block_reason` (optional) – e.g., "Daily quota exhausted" **Suggested implementation** (place at the top of the affected files or import it from a central place; for simplicity, I'll recommend a new helper in `services/devii/helpers.py`): ```python from utils.api import create_notification from services.places import get_table def notify_admins_quota_blocked(user_uid: str, user_name: str) -> None: admins = get_table("users").find(role="Admin") for admin in admins: if admin["uid"] == user_uid: continue # skip notifying the blocked user if they are admin create_notification( recipient_uid=admin["uid"], title="User quota blocked", body=f"User {user_name} ({user_uid}) has been blocked from using Devii due to daily quota exhaustion.", type="system_alert", # or appropriate type from existing constants link=f"/admin/users/{user_uid}/ai-usage", # optional deep link ) ``` The exact import path may vary; verify `create_notification` signature and available notification types in `utils/notifications.py`. The project's notification types can be found via grep on `create_notification` calls (e.g., `"issue_assigned"`, `"quota_reset"`). Use `"system_alert"` if it exists, otherwise create a minimal type string that matches the existing pattern. --- ### Step 2: Modify `routers/devii.py` (WebSocket path) **Current code** (line ~265-276): ```python # audit-only audit.record_system(...) ``` **Add after the audit.record_system call**: ```python # notify admins if owner_kind == "user": # avoid notifying about admin/guest? Or notify for all. Investigation says no current notification for any role. notify_admins_quota_blocked(owner_id, owner_name) # owner_name must be available; look up username if not ``` You may need to fetch the username from the owner_id if it's not passed in scope. The quota check at line ~297-301 is inside `self.quota_exceeded()`, but the block is in the route handler. The block occurs in the `_process_request` method or similar; the notification should be placed right after the `raise BlockedQuota` or the `audit.record_system`. Check the exact location—likely after the audit call, before the exception is raised. Ensure the helper is imported at the top of the file. --- ### Step 3: Modify `services/telegram/bridge.py` (Telegram path) **Current code** (line ~222-233): ```python # audit-only audit.record_system(...) ``` **Add after the audit.record_system call**: ```python # notify admins notify_admins_quota_blocked(owner_id, username) # username is available in handler scope ``` Same helper import. The Telegram handler uses `update.effective_user.id`? Actually `owner_id` is the Devii user UID; ensure it's passed correctly. The existing audit call should have the user context. --- ### Step 4: Add a notification type if needed If there is no existing notification type for quota blocks, add one in `utils/notifications.py` (or wherever notification types are defined as constants). Example: ```python NOTIFICATION_TYPE_QUOTA_BLOCK = "quota_block" ``` But using an existing generic type like `"system_alert"` or `"admin_alert"` is acceptable. Investigate by grepping `type=` in existing `create_notification` calls. --- ### Step 5: Test and lint Run: ```bash pip install -e '.[dev]' -q && make test-unit pip install -q ruff && ruff check . ``` Ensure no existing tests fail. The new notifications will not be tested unless we add a test; but the plan must pass existing tests. Optionally, add a simple unit test for the helper in `tests/unit/...`. However, to keep scope minimal, skip adding new tests unless the project’s test runner requires coverage for new functions. The existing test suite for quota reset still passes; the notification helper will not affect quota logic. --- ### Definition of Done 1. **Code changes applied**: - New helper function `notify_admins_quota_blocked` created in a shared location (e.g., `services/devii/notifications.py`). - `routers/devii.py` updated: after `audit.record_system` in the quota-blocked path, call `notify_admins_quota_blocked`. - `services/telegram/bridge.py` updated: same pattern after `audit.record_system`. - Helper imported correctly in both files (relative or absolute imports consistent with project style). 2. **Notification type** (if required) added to `utils/notifications.py` and used in the helper. 3. **Python syntax checks pass**: all modified files compile with `python3 -m py_compile` or are syntactically valid. 4. **All existing unit tests pass**: ```bash pip install -e '.[dev]' -q && make test-unit ``` Exit code 0. 5. **Lint passes**: ```bash pip install -q ruff && ruff check . ``` No new violations. Existing lint warnings (if any) not worsened. 6. **No regression in quota reset functionality**: manual verification that admin reset endpoints still work (not changed, but ensure imports didn't break anything). 7. **Grep verification**: confirm `create_notification` is now called at least once in each of the two files: ```bash grep -n "create_notification" routers/devii.py services/telegram/bridge.py ``` Should yield >0 matches in each. 8. **No new deadlock or async issues**: The helper uses `create_notification` which wraps via `background.submit`. Ensure the Telegram bridge's sync handler can use it (it may need to be called with `await` or wrapped in `asyncio.run`). Investigate the bridge's event loop integration. If the bridge runs in sync context, use the synchronous wrapper `create_notification_sync` or adapt accordingly. This must be validated during implementation. --- ### Plan Summary | Step | File(s) | Action | |------|---------|--------| | 1 | New file `services/devii/notifications.py` | Define `notify_admins_quota_blocked(user_uid, user_name)` using `create_notification` | | 2 | `routers/devii.py` (~L265) | Import helper; call after audit.record_system | | 3 | `services/telegram/bridge.py` (~L222) | Import helper; call after audit.record_system | | 4 | `utils/notifications.py` (if needed) | Add notification type constant | | 5 | Run tests & lint | Verify no regressions | This plan directly addresses the proven feature gap, does not introduce new administration surfaces, and avoids the inaccurate claims in the original ticket.