diff --git a/HISTORY/2026-08-28-obdai-receipt.md b/HISTORY/2026-08-28-obdai-receipt.md index 6be4fbf..88f3dfa 100644 --- a/HISTORY/2026-08-28-obdai-receipt.md +++ b/HISTORY/2026-08-28-obdai-receipt.md @@ -259,6 +259,65 @@ thoughts 477, total 1627. Стоимость по тарифу Gemini 3.6 Flash candidate output как выход: $0.00092, то есть около 9 копеек при курсе 95 рублей за доллар. +## 2026-08-31: Критическая валидация отчета Opus + +Пользователь запросил документировать анализ Opus, выполнить критическую +сверку по коду и сформулировать вопросы до внесения изменений. + +### Что подтверждено по коду + +- `recipe_service/app.py`: есть дублирование вызовов `record(...)` по веткам + ответа; единый финализатор уменьшит риск расхождений полей метрик. +- `recipe_service/app.py`: `duration_ms` считается только в ветке `401`, + в остальных ветках передается `0`. +- `recipe_service/app.py`: используется `__import__('time').monotonic()` + вместо обычного `import time`. +- `recipe_service/app.py`: сравнение Bearer-токена выполнено через `==`, + без constant-time сравнения. +- `recipe_service/metrics.py`: `record()` вызывает `initialize()` на каждый + запрос, что приводит к повторной инициализации схемы БД. +- `gemini_proxy/app.py`: endpoint принимает `api_key_override` из формы. +- `android-app/app/build.gradle.kts`: `RECEIPT_API_TOKEN` попадает в + `BuildConfig`, значит секрет присутствует в клиентском APK. +- `android-app/app/src/main/java/ru/obdai/receipt/camera/CameraManager.kt`: + на каждом кадре выполняется цепочка YUV -> JPEG -> Bitmap. +- `android-app/app/src/main/java/ru/obdai/receipt/MainActivity.kt`: + текст результата выводится и в `Column`, и в `ResultOverlay`. +- `android-app/app/src/main/java/ru/obdai/receipt/crop/MedicationZoneDetector.kt`: + дата в формате `dd.mm.yyyy` сама по себе триггерит `PatientDataDetected`. + +### Что уточнено по замечаниям Opus + +- Замечание про `client_ip=127.0.0.1` корректно как риск достоверности + метрик за nginx. Для надежного учета реального IP требуется явная политика + доверия прокси и корректная обработка заголовков. +- Замечание про детализацию `502` корректно как вопрос баланса между + диагностикой и минимизацией утечки деталей провайдера. +- Замечание про отсутствие тестов для `recipe_service` подтверждено: в + текущем дереве есть `gemini_proxy/test_app.py`, но нет тестового файла + для `recipe_service`. + +### Вопросы к Opus (блокеры перед правками) + +1. По `gemini_proxy/app.py`: удаляем `api_key_override` полностью или + оставляем только в debug-режиме под отдельным флагом окружения? +2. По `recipe_service/app.py` ветке `502`: какой контракт ошибки нужен + клиенту - единое сообщение, коды причин, или ограниченный whitelist + провайдерских причин без сырого текста? +3. По учету IP за nginx: доверяем строго одному прокси (`x_for=1`) или + нужен более строгий trust chain по сети/подсети? +4. По Android токену: подтверждаем переход на краткоживущие серверные + токены (token exchange), или остается текущая схема MVP с явной + фиксацией риска? +5. По privacy-gate: дата в medication-зоне должна блокировать отправку, + или только снижать confidence и требовать подтверждение пользователя? + +### Итог + +Отчет Opus в значительной части подтвержден фактами по текущему коду. +Критичные изменения backend можно готовить после ответов на вопросы выше, +чтобы не сломать контракт клиента и не ослабить security-policy. + Вывод: лимит 500 токенов оказался недостаточным; следующий независимый тест проводится с существенно большим лимитом и ещё более короткой JSON-схемой. @@ -641,3 +700,49 @@ nginx-маршруты без redirect и health `/receipt/health`. Старые IP `127.0.0.1`, MIME `image/png`, размер 531101 байт, длину prompt 88 и непустой usage JSON. Тестовый image-файл удалён после запроса; приложение читает изображение в память и не пишет его на диск. + +## 2026-08-31: критическая валидация отчета Opus и обновление плана + +Проведена проверка утверждений Opus по фактическому коду и конфигурации. + +Подтверждено по backend: + +- В `recipe_service/app.py` есть многократное дублирование блоков `record(...)` по веткам обработки. +- В `recipe_service/app.py` длительность `duration_ms` в большинстве веток фиксируется как `0`; фактический расчет есть только для `401`. +- В `recipe_service/app.py` используется `__import__('time').monotonic()` вместо нормального `import time`. +- В `recipe_service/app.py` сравнение bearer-токена выполнено через `==`, без constant-time сравнения. +- В `recipe_service/app.py` в `502` наружу пробрасывается provider detail, что может раскрывать внутренние детали upstream-ошибок. +- В `recipe_service/metrics.py` функция `record()` вызывает `initialize()` на каждый запрос. +- `recipe_service/nginx-recipe.conf` передает `X-Real-IP` и `X-Forwarded-For`, но в Flask используется `request.remote_addr`; без `ProxyFix` это адрес прокси. + +Подтверждено по инфраструктуре: + +- `recipe_service/recipe.service` слушает только `127.0.0.1:8770`. +- `gemini_proxy/gemini-proxy.service` слушает только `127.0.0.1:8768`. + +Подтверждено по Android: + +- `android-app/app/build.gradle.kts` задает `BuildConfig.RECEIPT_API_TOKEN`, токен попадает в клиентское приложение. +- `android-app/app/src/main/java/ru/obdai/receipt/network/ApiClient.kt` отправляет `Authorization: Bearer ...` из `BuildConfig`. +- `android-app/app/src/main/java/ru/obdai/receipt/camera/CameraManager.kt` выполняет YUV->JPEG->Bitmap на кадр в analyzer. +- `android-app/app/src/main/java/ru/obdai/receipt/MainActivity.kt` выводит результат и в `Column`, и в `ResultOverlay`. +- `android-app/app/src/main/java/ru/obdai/receipt/crop/MedicationZoneDetector.kt` блокирует при любой дате по regex, что дает ложные privacy-срабатывания. +- `android-app/app/src/main/AndroidManifest.xml` содержит `usesCleartextTraffic="false"`. + +Подтверждено по тестам и зависимостям: + +- Тесты присутствуют только в `gemini_proxy/test_app.py`. +- Для `recipe_service` тестов нет. +- Корневой `requirements.txt` и `recipe_service/requirements.txt` расходятся по version bounds. + +Дополнительные риски, подтвержденные параллельным аудитом секретов: + +- В локальных репозиториях обнаружены реальные токены/JWT и tracked secret-like файлы; часть секретов не отслеживается Git, но присутствует в рабочих каталогах. +- В `tf_provider` обнаружен tracked `secrets/llm.key`; в истории есть многочисленные совпадения по маркерам `sk-`. +- В `nifi_fork` история содержит совпадения по маркерам `sk-` и `AKIA`; часть совпадений относится к документации/тестам и требует ручной triage по значениям. + +Статус: + +- Отчет Opus по backend и Android в критических пунктах в основном подтвержден. +- Для части исторических совпадений по секретам нужна ручная классификация: реальный ключ, тестовый маркер или документационный пример. +- Код в этой итерации не менялся; выполнено документирование и валидация. diff --git a/PLAN/receipt-service-plan.md b/PLAN/receipt-service-plan.md index f5d2c57..b6038b7 100644 --- a/PLAN/receipt-service-plan.md +++ b/PLAN/receipt-service-plan.md @@ -182,3 +182,38 @@ crop лекарственной строки - начать с SQLite и перейти на отдельный PostgreSQL только при появлении требований к параллельной записи, админке или нескольким экземплярам сервиса. Перед фиксацией порогов и коэффициентов scoring провести эксперимент на 50–200 обезличенных рецептах. + +## 12. Критический план по результатам валидации Opus (2026-08-31) + +### Подтвержденные проблемы и приоритет + +1. `recipe_service/metrics.py`: убрать `initialize()` из `record()`; инициализация должна быть только на старте сервиса. +2. `recipe_service/app.py`: устранить дублирование `record(...)` через единый финализатор/обертку завершения запроса. +3. `recipe_service/app.py`: считать `duration_ms` во всех ветках, а не только при `401`. +4. `recipe_service/app.py`: заменить сравнение токена на constant-time (`hmac.compare_digest`). +5. `recipe_service/app.py`: минимизировать детализацию `502` для клиента, подробности оставлять в логах. +6. `recipe_service/app.py`: добавить учет заголовков прокси (`ProxyFix`) для корректного client IP. +7. Тесты: добавить `recipe_service/test_app.py` с кейсами `401/400/415/413/200`. +8. `requirements.txt`: выровнять root и `recipe_service/requirements.txt` по version bounds. + +### Android-блок (после подтверждения модели безопасности) + +1. Убрать долговременный статический секрет из APK или перейти на схему краткоживущих токенов. +2. Оптимизировать pipeline камеры: снизить частоту тяжелой конвертации или заменить способ получения bitmap. +3. Убрать двойной вывод результата (`Column` и `ResultOverlay`) и оставить один источник отображения. +4. Пересмотреть `containsPatientData` в `MedicationZoneDetector`: дата сама по себе не должна блокировать распознавание. + +### Вопросы к Opus (требуют уточнения до правок) + +1. `api_key_override` в `gemini_proxy/app.py`: удалять полностью или оставить только для локального debug-режима за флагом окружения? +2. По `502`: какой клиентский формат ошибки нужен — единое сообщение или кодизированные причины без деталей провайдера? +3. Для `ProxyFix`: доверяем ровно одному прокси (`x_for=1`) или нужен более строгий trust chain? +4. Для Android: подтверждаем перенос от статического API-токена к короткоживущему серверному token exchange? +5. По privacy-гейту: дата рецепта в medication-зоне допустима или должна только понижать confidence без блокировки? + +### План выполнения после подтверждения + +1. Backend фаза A: `metrics.py` + `app.py` (инициализация, duration, record-finalizer, compare_digest, ProxyFix). +2. Backend фаза B: тесты `recipe_service/test_app.py` + проверка pytest. +3. Backend фаза C: harmonize requirements. +4. Android фаза D: безопасность токена + оптимизация камеры + privacy-gate.