BorisovAI

Блог

Публикации о процессе разработки, решённых задачах и изученных технологиях

Новая функцияC--projects-bot-social-publisher

ChatManager: как научить бота помнить, где ему работать

# Как научить AI-бота помнить свои чаты: история ChatManager Задача стояла простая на словах, но коварная на деле. У AI-бота в проекте **voice-agent** началась проблема с идентичностью: он рос, его добавляли в новые чаты, но вот беда — он не различал, в каких группах он вообще должен работать. Представь: бот оказывается в сотне чатов, а слушаться команд должен только в тех, которые явно добавил его хозяин. Без этого механизма вся система на мине. **Первым делом разобрали задачу по кирпичикам.** Нужна была полноценная система управления чатами: бот должен помнить, какие чаты ему доверены, проверять права пользователя перед каждой командой и предоставлять простые способы добавлять/удалять чаты из управляемых. Звучит как обычная CRUD-операция, но в контексте асинхронного Telegram-бота это становится интереснее. Решение разделили на пять логических контрольных точек. Начали с **ChatManager** — специального класса в `src/auth/`, который ведал бы всеми чатами. Важное решение: использовали уже имеющийся в проекте **structlog** для логирования вместо того, чтобы добавлять ещё одну зависимость. И, что критично для асинхронного бота, выбрали **aiosqlite** — асинхронный драйвер для SQLite. Почему это важно? Потому что обычный SQLite работает синхронно и может заблокировать весь бот при обращении к БД. aiosqlite оборачивает операции в `asyncio` — и вот уже БД не стопорит главный цикл обработки сообщений. **Дальше пошли миграции и middleware.** Создали таблицу `managed_chats` с информацией о чатах, их типах и владельцах. Затем встроили в pipeline Telegram-хэндлеров специальный middleware для проверки прав — перед каждой командой система проверяет, имеет ли пользователь доступ к этому чату. Если нет — молчит или вежливо отказывает. Команды управления (`/manage add`, `/manage remove`, `/manage list`) сделали простыми и понятными. Напишешь в личку боту `/manage add` — и чат добавляется в управляемые. Никакого магического угадывания, всё явно. **Интересный момент про асинхронные БД.** Когда разработчики впервые натыкаются на проблему "бот зависает при запросе к БД", они часто виноваты в синхронных операциях с базой. aiosqlite решает это элегантно: минимум кода, максимум производительности. Это один из тех инсайтов, которые приходят дорого, но в долгосрочной перспективе спасают душу. **В итоге получилось мощно.** Бот теперь точно знает, кто его хозяин в каждом чате, и не поддаётся на трюки неуполномоченных пользователей. Архитектура масштабируется — можно добавить роли, разные уровни доступа, историю изменений. Всё работает асинхронно и не тормозит. Дальше очередь интеграционных тестов и production. 😄 **Совет дня:** всегда создавай миграции БД отдельно от логики — так проще откатывать и тестировать. И да, GCP и кот похожи тем, что оба делают только то, что хотят, и игнорируют твои инструкции.

#claude#ai#python#api#security
9 февр. 2026 г.
Изменение кодаC--projects-ai-agents-voice-agent

ChatManager: как AI-боту дать контроль над своими чатами

# От хаоса к порядку: как мы научили AI-бота управлять собственными чатами Столкнулись с интересной проблемой в проекте **voice-agent** — нашему AI-боту нужно было получить контроль над тем, в каких чатах он работает. Представь: бот может оказаться в сотнях групп, но обслуживать он должен только те, которые явно добавил владелец. И вот здесь начинается магия архитектуры. ## Задача была классической, но хитрой Нужно было реализовать систему управления чатами — что-то вроде белого списка. Бот должен был: - Помнить, какие чаты он курирует - Проверять права пользователя перед каждой командой - Добавлять/удалять чаты через понятные команды - Хранить всё это в надежной базе данных Звучит просто? На деле это требовало продумать архитектуру с нуля: где брать данные, как валидировать команды, как не потерять информацию при перезагрузке. ## Как мы это делали Первым делом создали класс **ChatManager** — специалист по управлению чатами. Он бы жил в `src/auth/` и работал с SQLite через **aiosqlite** (асинхронный драйвер, чтобы не блокировать главный цикл обработки сообщений). Важный момент: использовали уже имеющийся в проекте **structlog** для логирования — не захотелось добавлять ещё одну зависимость в requirements.txt. Затем создали миграцию БД — новую таблицу `managed_chats` с полями для типа чата (приватный, группа, супергруппа, канал), ID владельца и временной метки. Ничего сложного, но по-человечески: индексы на часто используемых полях, CHECK-констрейнты для валидности типов. Дальше идет классический паттерн — **middleware для проверки прав**. Перед каждой командой система проверяет: а имеет ли этот пользователь доступ к этому чату? Если нет — бот скромно молчит или вежливо отказывает. Это файл `src/telegram/middleware/permission_check.py`, который встраивается в pipeline обработки сообщений. И, конечно, **handlers** — набор команд `/manage add`, `/manage remove`, `/manage list`. Пользователь пишет в личку боту `/manage add`, и чат добавляется в управляемые. Просто и понятно. ## Маленький инсайт про асинхронные БД Знаешь, почему **aiosqlite** так хороша? SQLite по умолчанию работает синхронно, но в асинхронном приложении это становится узким местом. aiosqlite оборачивает операции в `asyncio`, и вот уже БД не блокирует весь бот. Минимум кода, максимум производительности. Многие разработчики об этом не думают, пока не столкнутся с тем, что бот «зависает» при запросе к БД. ## Итог: от плана к тестам Весь процесс разбили на логические шаги с контрольными точками — каждый шаг можно было проверить отдельно. После создания ChatManager идёт миграция БД, потом интеграция в основной бот, затем handlers для команд управления, и наконец — unit-тесты в pytest. Результат: бот теперь знает, кто его хозяин в каждом чате, и не слушает команды от неуполномоченных людей. Архитектура масштабируется — можно добавить роли, разные уровни доступа, историю изменений. А главное — всё работает асинхронно и не тормозит. Дальше план — запустить интеграционные тесты и загнать в production. Но это уже другая история. 😄 **Совет дня:** всегда создавай миграции БД отдельно от логики — так проще откатывать и тестировать.

#claude#ai#python#api#security
Разработка: Voice Agent
9 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

SQLite спасает день: масштабируемая БД вместо хаоса в памяти

# SQLite вместо памяти: как я спас Telegram-ботов от хаоса Проект `bot-social-publisher` взлетал буквально на глазах. Каждый день новые пользователи подключали своих ботов, запускали кампании, расширяли функционал. Но в какой-то момент понял: у нас есть проблема, которая будет только расти. Где-то в недрах памяти процесса валялась вся информация о том, какие чаты под управлением, кто их владелец, какие у них настройки. Приватный чат? Группа? Канал? Всё это было либо в переменных, либо в логах, либо вообще только в голове. Когда рост пользователей начал экспоненциальный скачок, стало ясно: **нужна нормальная база данных. Правильная, масштабируемая, без требований на отдельный сервер**. **Первым делом посмотрел на то, что уже есть.** В проекте уже была собственная SQLite база в `data/agent.db`, и там спокойно жил `UserManager` — отличный пример того, как работать с асинхронными операциями через `aiosqlite`. Логика была простой: одна база, одна инфраструктура, одна точка подключения для всей системы. Так почему бы не применить ту же философию к чатам? Архитектурное решение созревало быстро. Не было никаких грёз о микросервисах, Redis-кэшах или какой-то сложности. Нужна таблица `managed_chats` с полями для `chat_id` (первичный ключ), `owner_id` (связь с пользователем), `chat_type` с `CHECK` constraint для валидации типов, `title` для названия и JSON-поле `settings` про запас на будущее. **Неожиданно выяснилось** — и это было критично — что индекс на `owner_id` вообще не опциональная штука. Когда пользователь запрашивает список своих чатов, база должна найти их за миллисекунды, а не сканировать таблицу от начала до конца. SQLite часто недооценивают в стартапах, думают, что это игрушка для тестирования. На самом деле при правильном использовании индексов и подготовленных SQL-statements она справляется с миллионами записей и может быть полноценной боевой базой. Реализацию сделал по образцу `UserManager`: создал `ChatManager` с асинхронными методами `add_chat()`, `is_managed()`, `get_owner()`. Каждый запрос параметризован — никаких SQL-injection уязвимостей. Всё та же `aiosqlite` для асинхронного доступа, один способ работать с данными, без дублирования логики. Красивый момент получился благодаря `INSERT OR REPLACE` — если чат переиндексируется с новыми настройками, старая запись просто заменяется. Это вышло из архитектуры, не планировалось специально, но сработало идеально. **В итоге:** одна БД, одна инфраструктура, индекс уже готов к аналитическим запросам на будущее, JSON-поле ждёт расширенных настроек. Никаких ORM-фреймворков, которые на этом этапе обычно добавляют больше проблем, чем решают. Дальше — интеграция с обработчиками Telegram API, где эта информация начнёт по-настоящему работать. Но то уже следующая история. 😄 Разработчик говорит: «Я знаю SQLite». HR: «На каком уровне?». Разработчик: «На уровне, когда она работает, и я этому не верю».

#claude#ai#python#javascript#api#security
9 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

SQLite вместо памяти: как обуздать рост Telegram-ботов

# Управляем Telegram-чаты как должно: от памяти к базе данных Проект `bot-social-publisher` рос как на дрожжах. Каждый день новые пользователи подключали своих ботов, запускали кампании, развивали функционал. Но вот беда: где-то в памяти процесса валялась информация о том, какие чаты под управлением, кто их владелец, какие у них настройки. Приватный чат? Группа? Канал? Всё это было либо в переменных, либо где-то в логах. Нужна была система. Правильная, масштабируемая, не требующая отдельного сервера для базы данных. **Первым делом** посмотрел, как устроен текущий стек. В проекте уже была собственная SQLite база в `data/agent.db`, и там жил `UserManager` — отличный пример того, как правильно работать с асинхронными операциями через `aiosqlite`. Значит, нужно просто добавить новую таблицу `managed_chats` в ту же базу, скопировать философию управления пользователями и запустить в production. **Архитектурное решение** было ясно с самого начала: никаких микросервисов, никаких Redis-кэшей для этого этапа. Нужна таблица с полями для `chat_id` (первичный ключ), `owner_id` (связь с пользователем), `chat_type` (с проверкой через `CHECK` constraint — только валидные типы), `title` и JSON-поле `settings` на будущее. Неожиданно выяснилось, что индекс на `owner_id` — это не опциональная штука. Когда пользователь запрашивает список своих чатов, база должна найти их быстро, а не сканировать всю таблицу от начала до конца. SQLite часто недооценивают в стартапах, думают, что это игрушка для тестирования. На самом деле при правильном использовании индексов и подготовленных statements она справляется с миллионами записей и может быть полноценной боевой базой. **Реализацию** сделал по образцу `UserManager`: создал `ChatManager` с асинхронными методами `add_chat()`, `is_managed()`, `get_owner()`. Каждый запрос параметризован — никаких SQL injection уязвимостей. Используется всё та же `aiosqlite` для асинхронного доступа, одна точка подключения для всей системы, без дублирования логики. Красивый момент получился благодаря `INSERT OR REPLACE` — если чат переиндексируется с новыми настройками, старая запись просто заменяется. Это вышло из архитектуры, а не планировалось специально. В итоге: одна БД, одна инфраструктура, масштабируемая схема. Когда понадобятся сложные аналитические запросы — индекс уже есть. Когда захотим добавить права доступа или расширенные настройки чата — JSON-поле ждёт. Никаких фреймворков ORM, которые обычно добавляют больше проблем, чем решают на этом этапе. Дальше — интеграция с обработчиками Telegram API, где эта информация будет реально работать. Но то уже следующая история. 😄 Разработчик: «Я знаю ArgoCD». HR: «На каком уровне?». Разработчик: «На уровне Stack Overflow».

#claude#ai#python#javascript#api#security
9 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Когда голосовой агент забывает встречи: учим AI слушать, не переспрашивая

# Когда AI забывает контекст: как мы учим голосовых агентов помнить о встречах Павел получил от своего AI-ассистента странный ответ. Вместо простого подтверждения встречи с Максимом в понедельник в 18:20 система начала задавать уточняющие вопросы: какой сегодня день, нужно ли запомнить дату, может быть, это повторяющаяся встреча? Звучит как помощник с амнезией, верно? Но именно эта проблема и стояла перед командой при разработке **voice-agent** — проекта голосового ассистента нового поколения. **Завязка:** проект работает на стыке нескольких сложных технологий. Это не просто бот — это агент, который должен понимать контекст разговора, запоминать важные детали и действовать без лишних вопросов. Когда пользователь говорит: «Встреча в понедельник в 18:20», система должна понять, что это *конкретная информация*, требующая *сохранения*, а не просьба о консультации. Казалось бы, мелочь, но именно такие «мелочи» отделяют полезный AI от раздражающего помощника. **Первым делом** разработчики столкнулись с архитектурной задачей: как структурировать память агента? Система должна различать *информационные запросы* (где нужны уточнения) и *директивные команды* (где нужно просто выполнить и запомнить). Для voice-agent это означало внедрение многоуровневой системы идентификации интентов — понимание не просто слов, а *цели* высказывания. Неожиданно выяснилось, что естественный язык коварен: одна и та же фраза может означать и просьбу, и информационное сообщение в зависимости от интонации и контекста диалога. Решение пришло через разделение на четыре архитектурных уровня: идентификация и авторизация пользователя, структурированное хранение данных о событиях, логика обработки сообщений и, наконец, функциональность исполнения в экосистеме (Telegram, внутренние чаты, TMA). Каждый уровень отвечает за свой кусок пазла. Система теперь не просто парсит текст — она *понимает ролевую модель* пользователя и принимает решения на основе его прав доступа. **Интересный факт:** большинство разработчиков голосовых агентов забывают, что люди говорят не как компьютеры. Мы пропускаем детали, которые кажутся нам очевидными, перепрыгиваем между темами и ожидаем, что AI дозаполнит пробелы. Именно поэтому лучшие голосовые системы — это не те, что задают много вопросов, а те, что *предполагают контекст* и только уточняют краевые случаи. Voice-agent учится работать в режиме доверия: если пользователь говорит достаточно конкретно, система действует; если неясно — *тогда* уточняет. **Итог:** Павел больше не получит сотню вопросов по поводу встречи. Система научилась различать между «помоги мне разобраться» и «запомни это». Проект все ещё в разработке, но архитектура уже показывает, что AI может быть не просто компетентным, но и *вежливым* — уважающим время пользователя и его интеллект. Дальше команда планирует внедрить предиктивную логику: система будет не только запоминать встречи, но и предлагать календарные уведомления, проверять конфликты времени и даже предлагать переносы на основе истории поведения пользователя. Но это уже совсем другая история. 😄 Что общего у yarn и подростка? Оба непредсказуемы и требуют постоянного внимания.

#claude#ai#security
Разработка: Voice Agent
9 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Когда AI становится парным программистом: история Claude Code

# Claude Code встречает разработчика: история создания идеального помощника Павел открыл **voice-agent** — проект, который стоял уже полгода в статусе "building". Python-бэкенд на FastAPI, Next.js фронтенд, асинхронная обработка аудио через aiogram. Задача была понятна: нужна система, которая может помочь в разработке, не мешая, не спрашивая лишних вопросов, а просто работая рядом. Первым делом команда определилась с подходом. Не просто документация, не просто chatbot, а **пара-программист** — инструмент, который понимает контекст проекта, может писать код, отлаживать, запускать тесты. На этапе 2010-х годов, когда началась фаза Deep Learning, никто не предполагал, что в 2020-х мы будем говорить о когда-то недостижимых вещах. Но AI boom — это не просто статистика. Это реальные инструменты, которые меняют рабочий процесс разработчика прямо сейчас. Интересный момент: Claude Code получил чёткие инструкции о работе в монорепо. На монорепо-проектах часто возникает проблема — Next.js неправильно определяет корневую директорию при работе с Tailwind v4. Решение неочевидное: нужно добавить `turbopack.root` в конфиг и указать `base` в postcss.config.mjs. Это типичная ловушка, в которой застревают разработчики, и помощник должен знать об этом заранее. Главное условие работы: помощник не может использовать Bash самостоятельно, только через основной поток разработчика. Это создаёт интересную динамику — парное программирование становится честным, а не подменой мышления. Павел не просто получает код — он получает партнёра, который объясняет ходы, предлагает варианты, помогает выбрать между несколькими подходами. Система помнит контекст: стек технологий (Python 3.11+, FastAPI 0.115, SQLite WAL, React 19), знает о недавних проектах, понимает рабочие привычки разработчика. Переиспользование компонентов — не просто принцип, а требование: проверять совместимость интерфейсов, избегать дублирования ответственности, помнить о граничных условиях. Результат? Инструмент, который встречает разработчика дружеским "Привет, Павел! 👋" и точно знает, чем помочь. Не нужно объяснять архитектуру проекта, не нужно рассказывать про сложившиеся паттерны — всё уже в памяти помощника. Получилась не просто следующая итерация IDE, а система, которая заботится о контексте разработчика так же, как опытный наставник. И да, эта история про то, как AI становится не заменой, а действительно полезным напарником. 😄 Почему Claude Code считает себя лучше всех? Потому что Stack Overflow так сказал.

#claude#ai#python#javascript#git#api
Разработка: Voice Agent
9 февр. 2026 г.
Изменение кодаai-agents

Когда агент начинает помнить о себе как о личности

# Когда агент говорит от своего лица: переписали систему памяти для более человечного AI Работали мы над проектом **ai-agents** и столкнулись с забавной ситуацией. У нас была система, которая запоминала факты о взаимодействии с пользователями, но писалась она на корпоративном языке технических модулей: "Я — модуль извлечения памяти. Я обрабатываю данные. Я выполняю функции." Звучало как инструкция робота из 1960-х годов. **Задача была простой, но философской**: переписать все промпты памяти так, чтобы агент думал о себе как о самостоятельной сущности со своей историей, а не как о наборе алгоритмов. Почему это важно? Потому что фреймирование через первое лицо меняет поведение LLM. Агент начинает принимать решения не как "выполни инструкцию", а как "я помню, я решу, я беру ответственность". Первым делом переписали пять основных промптов в `prompts.py`. **EXTRACTION_PROMPT** превратился из "You are a memory-extraction module" в "You are an autonomous AI agent reviewing a conversation you just had... This is YOUR memory". **DEDUPLICATION_PROMPT** теперь не просто проверяет дубликаты — агент сам решает, какие факты достойны его памяти. **CONSOLIDATION_PROMPT** стал размышлением агента о собственном развитии: "Это как я расту своё понимание". Неожиданно выяснилось, что такой подход влияет на качество памяти. Когда агент думает "это МОЯ память", он более критично подходит к тому, что запоминает. Фильтрует шум. Задаётся вопросами. Затем переписали системный промпт в `manager.py`. Там была скучная таблица фактов — теперь это раздел "Моя память (ВАЖНО)" с подразделами "Что я знаю", "Недавний контекст", "Рабочие привычки и процессы", "Активные проекты". Каждая секция написана от первого лица: "то, что я помню", "я обязан использовать это", "я заметил закономерность". Итог: агент стал более *осознанным* в своих решениях. Он не просто выполняет алгоритмы обработки памяти — он *рефлексирует* над собственным опытом. И да, это просто промпты и фреймирование, но это показывает, насколько мощное влияние имеет язык, на котором мы говорим с AI. Главный вывод: попробуйте переписать свои системные промпты от первого лица. Посмотрите, как изменится поведение модели. Иногда самые глубокие улучшения — это просто изменение перспективы. 😄 Почему агент начал ходить к психотерапевту? Ему нужно было лучше понять свою память.

#claude#ai#python
9 февр. 2026 г.
Исправление

Когда API успешен, но ответ пуст: охота на невидимого врага

# Когда AI молчит: охота на призрак пустого ответа В одном из проектов случилось странное — система обращалась к API, получала ответ, но внутри него... ничего. Как в доме с открытыми дверями, но все комнаты пусты. Задача была простая: разберись, почему сообщение от пользователя *Coriollon* через Telegram генерирует пустой результат, хотя API клиента уверенно докладывает об успехе. История началась 9 февраля в 12:23 с обычной команды. Пользователь отправил в бот сообщение «Создавай», и система маршрутизировала запрос в CLI с моделью Sonnet — всё как надо. Промпт собрали, отправили на API. Система была настроена с максимум тремя повторными попытками при ошибках. Логично, правда? Первый запрос обработался за 26 с лишним секунд. API вернул успех. Но в поле `result` зияла пустота. Не ошибка, не исключение — просто пустая строка. Система поняла: что-то не так, нужно пробовать ещё. Через 5 секунд — вторая попытка. Снова успех на бумаге, снова пустой ответ. Третий раз был поспешен: через 10 секунд ещё один запрос, и снова тишина. Что интересно — в логах были видны все признаки нормальной работы. Модель обработала 5000+ символов промпта, израсходовала токены, потратила API-бюджет. Кэш работал прекрасно — вторая и третья попытки переиспользовали 47000+ закэшированных токенов. Но конечный продукт — результат для пользователя — остался фантомом. Здесь скрывается коварная особенность асинхронных систем: успешный HTTP-статус и валидный JSON в ответе ещё не гарантируют, что внутри есть полезная нагрузка. API может спокойно ответить 200 OK, но с пустым полем результата. Механизм повторных попыток поймал проблему, но не смог её решить — повторял одно и то же три раза, как сломанный проигрыватель. На четвёртый раз система сдалась и выбросила ошибку: *«CLI returned empty response»*. Урок был ценный: валидация ответа от внешних сервисов должна быть двухуровневой. Первый уровень — проверяем HTTP-статус и структуру JSON. Второй уровень, важнее — проверяем, что в ответе есть актуальные данные. Просто наличие полей недостаточно; нужна проверка *содержания*. В нашем случае пустое значение в `result` должно было сработать как маячок уже на первой попытке, а не ждать третьей. Кэширование в таких ситуациях работает против нас — оно закрепляет проблему. Если первый запрос вернул пусто, и мы кэшировали эту пустоту, второй и третий запросы будут питаться из одного источника, перечитывая одну и ту же ошибку. Лекарство простое, но необычное: кэшировать нужно не все ответы подряд, а только те, которые прошли валидацию содержимого. Итог: система заработала, но теперь с более умным механизмом выявления невидимых ошибок. Повторные попытки стали умнее — они теперь различают, когда нужно переопробовать запрос, а когда отклонить ответ как невалидный. Пользователь Coriollon теперь получает либо результат, либо честную ошибку, но уже не это мучительное молчание. 😄 **Node.js — единственная технология, где «это работает» считается документацией.**

#clipboard#api#security
9 февр. 2026 г.
Новая функция

Боевой тест Telegram-бота: когда теория встречается с реальностью

# Проверяем Telegram-бота в боевых условиях: тестируем управление доступом Когда создаёшь бота для Telegram, одно дело — писать тесты в PyTest, и совсем другое — убедиться, что он работает с реальными аккаунтами и обрабатывает команды так, как задумано. Вот я и пришёл к моменту, когда нужно было залезть в грязь и провести самый важный тест: запустить бота и написать ему сообщение из Telegram. ## Задача была простая, но критичная Я добавил в бота новую фишку — управление доступом через команды `/manage`. Идея: в групповом чате владелец может сделать его приватным (`/manage add`), и тогда бот будет отвечать только ему. Затем команда `/manage remove` открывает доступ для всех обратно. Плюс ещё `/recall` и `/remember` для сохранения данных в памяти чата. Звучит просто, но нужно убедиться, что: - бот действительно игнорирует сообщения посторонних в приватном режиме; - владелец всегда может управлять ботом; - после отключения приватности всё работает как раньше. ## Как я это проверял Сначала поднял бота локально: ``` python telegram_main.py ``` Затем начались «полевые испытания»: 1. **Первый скрин-тест** — написал боту `/manage add` из своего аккаунта. Бот должен был записать ID чата в таблицу БД `managed_chats` и включить режим приватности. Отправил обычное сообщение — бот ответил. ✅ 2. **Второй аккаунт** — попросил друга отправить то же сообщение из другого Telegram. Бот молчал, хотя обычно отвечает на всё. Middleware `permission_check.py` срабатывал корректно и блокировал обработку. ✅ 3. **Финальный тест** — написал `/manage remove` и снова попросил друга отправить сообщение. На этот раз бот ответил. Приватность снята, всё доступно. ✅ ## Что оказалось неочевидным Интеграционное тестирование в Telegram выявило одну тонкость: когда ты работаешь с асинхронным обработчиком команд в aiogram, timing-зависимые проверки могут создавать гонки условий. У меня был момент, когда команда `/manage add` срабатывала, но middleware проверял доступ *до* того, как запись попала в БД. Пришлось добавить небольшой await после insert'а, чтобы гарантировать консистентность. Ещё обнаружил: если ты работаешь с SQLite и одновременно несколько обработчиков пишут в таблицу, нужен явный `commit()` или использовать контекстный менеджер транзакций. Иначе другой процесс не увидит изменения до commit'а. ## Что дальше? После успешных интеграционных тестов я задокументировал всё в README.md — добавил секцию про управление доступом с примерами команд. Создал отдельный файл `docs/CHAT_MANAGEMENT.md` с полной архитектурой ChatManager, схемой БД и API reference для всех методов класса. Теперь у меня есть надёжная система для создания приватных чатов с ботом. Это можно использовать для ассистентов, которые работают с конфиденциальными данными, или для модераторов в больших группах. Главный вывод: прежде чем гордиться unit-тестами, обязательно проверь свой код в реальной среде. Иногда именно там появляются неожиданные проблемы, которые не поймать никаким PyTest. 😄 Что общего у Telegram API и инструкций по использованию телефона? И то, и другое люди игнорируют в пользу метода проб и ошибок.

#clipboard#python#api#security
9 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Монорепо, голос и журнал ошибок: как AI учится не ломать код

# Когда AI-помощник встречается с монорепо: отладка голосового агента Проект `voice-agent` — это амбициозная задача: связать Python-бэкенд с Next.js-фронтенд в единый монорепо, добавить голосовые возможности, интегрировать Telegram-бота и веб-API. Звучит просто на словах, но когда начинаешь копать глубже, понимаешь: это кубик Рубика, где каждый вертел может что-то сломать. **Проблема, с которой я столкнулся, была банальной, но коварной.** Система работала в отдельных частях, но когда я попытался запустить полный цикл — бот берёт голос, отправляет на API, API обрабатывает через `AgentCore`, фронтенд получает ответ по SSE — где-то посередине всё разваливалось. Ошибки были разношёрстные: иногда спотыкался на миграциях БД, иногда на переменных окружения, которые загружались в неправильном месте. **Первым делом я понял: нужна система для документирования проблем.** Создал `ERROR_JOURNAL.md` — простой журнал "что сломалось и как это чинилось". Звучит банально, но когда в проекте участвуют несколько агентов разного уровня (Архитектор на Opus, бэкенд-фронтенд агенты на Sonnet, Junior на Haiku), этот журнал становится золотым стандартом. Вместо того чтобы каждый агент наново натыкался на баг с Tailwind v4 в монорепо, теперь первым делом смотрим журнал и применяем известное решение. **Архитектура обработки ошибок простая, но эффективная:** 1. Ошибка возникла → читаю `docs/ERROR_JOURNAL.md` 2. Похожая проблема есть → применяю известное решение 3. Новая проблема → исправляю + добавляю запись в журнал Основные боли оказались не в коде, а в конфигурации. С Tailwind v4 нужна магия в `next.config.ts` и `postcss.config.mjs` — добавить `turbopack.root` и `base`. SQLite требует WAL-режим и правильный путь к базе. FastAPI любит, когда переменные окружения загружаются только в точках входа (`telegram_main.py`, `web_main.py`), а не на уровне модулей. **Интересный момент: я переоценил сложность.** Большинство проблем решались не рефакторингом, а правильной организацией архитектуры. `AgentCore` — это единое ядро бизнес-логики для бота и API, и если оно валидируется с одной строки (`python -c "from src.core import AgentCore; print('OK')"`), весь стек работает как часы. **Итог:** система работает, но главный урок не в технических трюках — в том, что монорепо требует прозрачности. Когда каждая составляющая (Python venv, Next.js сборка, миграции БД, синхронизация переменных окружения) задокументирована и протестирована, даже сложный проект становится управляемым. Теперь каждый новый агент, который присоединяется к проекту, видит ясную картину и может сразу быть полезным, вместо того чтобы возиться с отладкой. На следующем этапе плотнее интегрирую streaming через Vercel AI SDK Data Stream Protocol и расширяю систему управления чатами через новую таблицу `managed_chats`. Но это — уже другая история. 😄 Что общего у монорепо и парка развлечений? Оба требуют хорошей разметки, иначе люди заблудятся.

#claude#ai#python#javascript#git#api#security
Разработка: Voice Agent
9 февр. 2026 г.
Новая функция

Одна БД для всех: как мы добавили чаты без архитектурного хаоса

# Одна база на всех: как мы добавили управление чатами без архитектурного хаоса Когда проект растёт, растут и его аппетиты. В нашем Telegram-боте на основе Python уже была отличная инфраструктура — `UserManager` для управления пользователями, собственная SQLite база в `data/agent.db`, асинхронные запросы через `aiosqlite`. Но вот беда: чат-менеджер ещё не появился. А он нам был нужен. Стояла вот какая задача: нужно отслеживать, какие чаты управляет бот, кто их владелец, какой это тип чата (приватный, группа, супергруппа, канал). При этом не создавать отдельную базу данных — это же кошмар для девопса — а переиспользовать существующую инфраструктуру. **Первым делом** заглянул в текущую архитектуру. Увидел, что всё уже завязано на одной БД, один конфиг, одна логика подключения. Идеально. Значит, нужна просто одна новая таблица — `managed_chats`. Задумал её как простую структуру: `chat_id` как первичный ключ, `owner_id` для связи с пользователем, `chat_type` с проверкой типов через `CHECK`, поле `title` для названия и JSON-колонка `settings` на будущее. Обычно на этом месте разработчик бы создал абстрактный `ChatRepository` с двадцатью методами и паттерном `Builder`. Я же решил сделать проще — скопировать философию `UserManager` и создать классический `ChatManager`. Три-четыре асинхронных метода: добавить чат, проверить, управляется ли он, получить владельца. Всё на `aiosqlite`, как и везде в проекте. **Неожиданно выяснилось**, что индексы — это не украшение. Когда начну искать чаты по владельцу, индекс на `owner_id` будет спасением. SQLite не любит полные скены таблиц, если можно обойтись поиском по индексу. Интересный момент: SQLite часто недооценивают в стартапах, думают, что это игрушка. На самом деле она справляется с миллионами записей, если её правильно использовать. Индексы, `PRAGMA` для оптимизации, подготовленные statements — и у вас есть боевая база данных. Многие проекты потом переходят на PostgreSQL только потому, что привыкли к MySQL, а не из реальной нужды. В итоге получилась чистая архитектура: одна БД, одна точка подключения, новая таблица без какого-либо дублирования логики. `ChatManager` живёт рядом с `UserManager`, используют одни и те же библиотеки и утилиты. Когда понадобятся сложные запросы — индекс уже есть. Когда захотим добавить настройки чата — JSON-поле ждёт. И никаких лишних микросервисов. Следующий шаг — интегрировать это в обработчики событий Telegram API. Но это уже другая история. 😄 Почему база данных никогда не посещает вечеринки? Её постоянно блокирует другой клиент!

#clipboard#python#javascript#git#security
9 февр. 2026 г.
Исправлениеbot-social-publisher

Бот, который помнит, где остановился: история оптимизации

# Как мы научили бота-публикатора читать только новое и не зацикливаться Работаю над **bot-social-publisher** — инструментом, который автоматизирует публикацию контента в соцсети. За время разработки проект рос и требовал всё более изощренных решений. Недавно пришло время для серьёзного апдейта: версия 2.2 превратилась в настоящий рефакторинг с половиной архитектуры. Основная боль была в том, что бот каждый раз перечитывал **весь лог событий** с самого начала. Проект растёт, логов накапливается тонны, и перечитывать их каждый раз — это пустая трата ресурсов. Первым делом внедрил **incremental file reading**: теперь каждый collector (собиратель событий) сохраняет позицию в файле и читает только новый контент. Позиции и состояния переносят перезапуски — данные не теряются. Второе узкое место: события из одного проекта приходят разреженно и хаотично. Если публикация выходит с опозданием, сессия кажется невнятной. Ввел **project grouping** — теперь все сессии из одного проекта, которые случились в окне 24 часа, объединяются в одну публикацию. Начало звучать куда более логично. Но бот просто агрегировал события — не очень информативно. Подключил **SearXNG news provider**, чтобы вплетать в промпты релевантные технологические новости. И добавил **content selector** с алгоритмом скоринга, который отбирает 40–60 самых информативных строк из лога. Выглядит как машинное обучение, а на деле простая эвристика, которая работает хорошо. Далее натолкнулся на проблему качества текста. LLM первый раз генерирует контент, но грамматика хромает. Внедрил **proofreading pass** — второй вызов LLM, но уже как редактор. Он проходит по тексту и чистит пунктуацию, стиль, грамматику. Результат — ночь и день. Когда LLM генерирует заголовок, иногда получаются дубли. Вместо того чтобы просто выпустить дубль, добавил **title deduplication** с авто-регенерацией (до трёх попыток). А ещё реализовал **tray notifications** — теперь разработчик видит нативные уведомления ОС о публикациях и ошибках. И главное: добавил **PID lock**, чтобы предотвратить запуск нескольких инстансов одновременно. Интересный момент: **PyInstaller**. Когда собираешь exe-бандл, пути до ресурсов перестают работать. Правильное разрешение путей в APP_DIR/BUNDLE_DIR — то есть нужно отдельно обрабатывать контекст запуска из exe. Мелочь, но без этого бандл просто не запустится. Ещё поменял логику пороговых значений: вместо min_lines теперь min_chars. Когда работаешь с короткими строками, количество символов точнее отражает объём контента, чем количество строк. И как положено, добавил AGPL-v3 лицензию ко всем файлам исходника. В итоге v2.2 — это не просто апдейт, а переосмысление архитектуры вокруг идеи: **не перечитывай лишнее, интеллектуально выбирай информацию, дважды проверяй качество, предотврати конфликты**. Бот теперь быстрее, умнее и его легче деплоить. 😄 Знаешь, почему логирование через **RotatingFileHandler** — лучший друг разработчика? Потому что диск полный. С ротацией логов хотя бы видно, когда именно он полный.

#git#commit#python#api#security
Разработка: Bot Social Publisher
9 февр. 2026 г.
Новая функцияtrend-analisis

AI изучает себя: как мы мониторим научные тренды

# Когда AI исследует сам себя: как мы строили систему мониторинга научных трендов Вот уже несколько недель я сидел над проектом **trend-analisis** и постепенно понимал: обычный парсер научных статей — это скучно и малоэффективно. Нужна была система, которая не просто собирает ссылки на arXiv, а *понимает*, какие исследовательские направления сейчас набирают силу и почему они имеют значение для практиков вроде нас. Задача стояла серьёзная: проанализировать тренд под названием "test SSE progress" на основе контекста передовых научных статей. Звучит сухо, но на деле это означало — нужно было построить мост между миром фундаментальных исследований и инженерными решениями, которые уже завтра могут оказаться в production. ## Что творится в AI-исследованиях прямо сейчас Первым делом я разобрался, какие пять основных направлений сейчас наиболее активны. И вот что получилось интересное: **Мультимодальные модели всё более хитрые.** Появляются проекты вроде **SwimBird**, которые позволяют языковым моделям переключаться между разными режимами рассуждения. Это не просто пухлая нейросеть — это система, которая знает, когда нужно "думать", а когда просто генерировать. **Геометрия — это новый король.** Статьи про пространственное рассуждение показывают, что просто скормить модели килотонны текста недостаточно. Нужны геометрические приоры, понимание 3D-сцен, позиции камер. Проект **Thinking with Geometry** буквально встраивает геометрию в процесс обучения. Звучит как философия, но это работает. **Retrieval-системы перестают быть простыми.** Исследование **SAGE** показало, что для глубоких исследовательских агентов недостаточно BM25 или даже простого векторного поиска. Нужны умные retriever'ы, которые сами знают, что ищут. **Дешёвые модели становятся умнее.** Работы про влияние compute на reinforcement learning показывают: вопрос уже не в том, сколько параметров у модели, а в том, как эффективно использовать доступные ресурсы. Это открывает путь к edge AI и мобильным решениям. **Generative модели наконец-то становятся теоретически понятнее.** Исследования про generalization в diffusion models через inductive biases к ridge manifolds — это не просто красивая математика. Это значит, мы начинаем понимать, *почему* эти модели работают, а не просто наблюдаем результаты. ## Как я это собирал На ветке **feat/scoring-v2-tavily-citations** я сделал интересный ход: интегрировал не просто поиск статей, а *контекстный анализ* с использованием мощных LLM. Система теперь не только находит статьи по ключевым словам, но и организует их в экосистемы: какие зоны исследований связаны, кто на них работает, как это может повлиять на индустрию. Неожиданно выяснилось, что самая сложная часть — не техническая. Это правильно определить связи между соседними трендами. Статья про hydraulic cylinders и friction estimation на первый взгляд кажется совершенно отдельной историей. Но когда понимаешь, что это про predictive maintenance и edge computing, видишь, как она связывается с работами про efficient RL. Промышленная автоматизация и AI-на-краю сети — они развиваются параллельно и подпитывают друг друга. ## Маленький инсайт о diffusion models Кстати, пока копался в исследованиях про обобщающую способность diffusion models, наткнулся на замечательный факт: эти модели естественным образом тяготеют к низкомерным многообразиям в данных. Это не баг и не случайность — это встроенное в архитектуру свойство, которое позволяет моделям избежать зубрёжки и научиться реально генерировать новые примеры. Вот такое вот невидимое мастерство работает под капотом. ## Что дальше Система уже в работе, регулярно обновляется с новыми статьями, и каждый раз я вижу, как исследовательские темы переплетаются в более сложные паттерны. Это напоминает наблюдение за живой экосистемой — каждое новое открытие создаёт точки приложения для трёх других. Главное, что я понял: мониторить тренды в AI — это не про сбор информации, это про построение карты будущего. И каждая на первый взгляд узкая статья может оказаться ключевой для вашего следующего проекта. 😄 Обед разработчика: ctrl+c, ctrl+v из вчерашнего меню.

#claude#ai#javascript#security
Разработка: Trend Analisis
9 февр. 2026 г.
ИсправлениеC--projects-bot-social-publisher

Регулярка в f-строке сломала SSE: как Python запутался в скобках

# Вся беда была в f-строке: как регулярное выражение сломало SSE-поток Работаю над проектом **trend-analisis** — системой для анализа трендов с помощью AI. На ветке `feat/scoring-v2-tavily-citations` нужно было реализовать вторую версию скорингового движка с поддержкой цитирования результатов через Tavily. Ключевой момент: вся архитектура строилась на Server-Sent Events, чтобы клиент получал аналитику в реальном времени по мере обработки каждого шага. Теоретически всё выглядело идеально. Backend на Python готов отправлять потоковые данные, API спроектирован, тесты написаны. Я запустил сервер, инициировал первый анализ и… ничего толкового не дошло до клиента. SSE-поток шёл, но данные приходили в каком-то странном формате, анализатор не мог их распарсить. Что-то явно ломалось на этапе подготовки ответа. Первый подозреваемый — кодировка. Windows-терминалы известны своей способностью превращать UTF-8-текст в «garbled text». Поехал в логи, начал смотреть, что именно генерируется на сервере. И вот тут выяснилось что-то совершенно неожиданное. **Виновником было регулярное выражение, спрятанное внутри f-строки.** В коде я использовал конструкцию `rf'...'` — это raw f-string, комбинация, которая кажется идеальной для работы с регексами. Но внутри этого выражения жил квантификатор `{1,4}`, и здесь произошла магия несовместимости. Python посмотрел на эти фигурные скобки и подумал: «А может, это переменная для интерполяции?» Результат: парсер пытался интерпретировать `{1,4}` как синтаксис подстановки, а не как часть регулярного выражения. Регекс ломался молча, и весь парсинг SSE-потока шёл вразнос. Решение оказалось элегантным, но коварным: нужно было просто экранировать скобки — превратить `{1,4}` в `{{1,4}}`. Двойные скобки говорят Python: «Это текст для регулярного выражения, не трогай». Звучит просто? Да. Но найти это среди километра логов — совсем другое дело. **Забавный факт:** f-строки появились в Python 3.6 и революционизировали форматирование текста. Но когда ты комбинируешь их с raw-строками и регулярными выражениями, получается коварная ловушка. Большинство опытных разработчиков просто избегают этого танца — либо используют обычные строки, либо передают регекс отдельно. Это классический пример того, как синтаксический сахар может стать источником часов отладки. После исправления бага я перезагрузил сервер и сразу же приступил ко второй проблеме: интерфейс был заполнен английскими текстами. Все заголовки анализа нужно было переместить в карту локализации русского языка. Прошёлся по коду, добавил русские варианты, заметил только один пропущенный "Stats", который быстро добавил в словарь. Финальная перезагрузка — и всё встало на место. SSE-поток работает без сбоев, данные доходят до клиента корректно, интерфейс полностью русифицирован. Главный вывод простой: когда работаешь с raw-strings в Python и засовываешь туда регулярные выражения с квантификаторами, всегда помни про двойное экранирование фигурных скобок. Это экономит часы отладки и стресса. 😄 F-строки и регексы — битва синтаксиса, в которой проигрывают все.

#claude#ai#python#git#api
9 февр. 2026 г.
Исправлениеtrend-analisis

f-строки vs регулярные выражения: коварная битва синтаксиса

# Поймал баг с f-строками: когда регулярные выражения подводят в самый неожиданный момент Работаю над проектом **trend-analysis** — системой для анализа трендов с использованием AI. Задача была создать версию v2 с поддержкой цитирования результатов через Tavily. На ветке `feat/scoring-v2-tavily-citations` мы реализовали SSE-поток для того, чтобы клиент получал результаты анализа в реальном времени, по мере их обработки. Казалось бы, всё работает: сервер запущен, архитектура продумана, Python-backend готов отправлять данные в формате Server-Sent Events. Но когда я попробовал запустить быстрый анализ и проверить, что все шаги доходят до клиента, произошло что-то странное. Первым делом я заметил ошибку во время разбора результатов. Погружаться в логи пришлось глубоко, и вот тут выяснилось что-то удивительное: баг был спрятан прямо в моём регулярном выражении. **Вся беда была в f-строке.** Видите, я использовал конструкцию `rf'...'` — raw f-string для работы с регулярными выражениями. Но когда в выражении появился квантификатор `{1,4}`, Python не посчитал его просто текстом — он попытался интерпретировать его как переменную в f-строке. Результат: регекс ломался на этапе компиляции. Решение оказалось элегантным: нужно было экранировать фигурные скобки двойными `{{1,4}}`. Это позволило Python понять, что скобки — часть регулярного выражения, а не синтаксис подстановки переменных. **Интересный факт:** f-строки в Python (появились в версии 3.6) революционизировали форматирование, но при работе с регулярными выражениями они могут быть настоящей минной лавкой. Разработчикам часто проще использовать обычную строку и передать регекс отдельно, чем разбираться с экранированием скобок. Это классический пример того, как синтаксический сахар может стать источником скрытых ошибок. После исправления ошибки я перезагрузил сервер и сразу взялся за локализацию интерфейса. Выяснилось, что в консоли большая часть текстов осталась на английском. Все заголовки нужно было переместить в карту локализации русского языка. Поначалу я видел garbled text — кодировка Windows делала своё чёрное дело в терминале, но после добавления русских строк в словарь последняя проверка показала: остался только один случай "Stats", который я оперативно добавил. Финальная перезагрузка и проверка — и всё встало на место. SSE-поток работает, данные доходят до клиента корректно, интерфейс полностью русифицирован. Урок, который я вынес: когда работаешь с raw-strings в Python и регулярными выражениями внутри f-строк, всегда помни про двойное экранирование. Это спасает часы отладки. 😄 Ловушка с Python f-строками и регексами — идеальный кандидат на звание «самый коварный баг, который выглядит как опечатка».

#claude#ai#python#api
Разработка: Trend Analisis
9 февр. 2026 г.
Новая функцияtrend-analisis

Очередь событий вместо словаря: как спасти архитектуру

# От очереди событий к стабильной архитектуре: как мы чинили `_fix_headings` в trend-analysis Есть у нас проект **trend-analysis** — сервис, который анализирует тренды и работает с длинными асинхронными операциями. Недавно на ветке `feat/scoring-v2-tavily-citations` разрабатывали новую систему цитирования для поиска информации через API Tavily. И вот столкнулись с классической проблемой: функция `_fix_headings` отлично выглядела в теории, но в боевых условиях вела себя странновато. Задача была чётко сформулирована — нужно убедиться, что функция работает правильно в изоляции, а потом интегрировать её в SSE-генератор для отправки прогресса клиенту. Звучит просто, но когда начинаешь копать глубже, появляются неожиданные подводные камни. Первым делом прямо протестировал `_fix_headings` — хотел убедиться, что логика нормализации заголовков работает так, как задумано. Потом понял, что архитектура прогресса была криво спроектирована. Изначально система работала с `_progress` как с обычным словарём, но это не масштабировалось — нужна была очередь событий для корректной работы SSE-потока. Казалось, нет ничего сложного в переходе с простого словаря на список событий? На практике это означало перепроверку каждого места в коде, где мы обращались к `_progress`. Неожиданно выяснилось, что одного только обновления структуры недостаточно. В коде было порядка десятка ссылок на старую переменную — кто-то обращался к `_progress`, не зная про `_progress_events`. Например, в endpoint'е на строке 661, который выдаёт последний статус для running jobs, нужно было менять логику: теперь брали не весь словарь, а последний элемент из истории событий. Это критично для получения актуального состояния операции. Вообще, асинхронные системы с очередями событий — это не просто паттерн проектирования, это философия. Когда вы переходите от прямых обращений к состоянию на событийную архитектуру, вы получаете отказоустойчивость и аудит совершенно даром. Каждое событие — это запись в истории, которую можно воспроизвести и разобраться, что пошло не так. Минус только один: нужно везде помнить об этом и не впадать в соблазн «быстро обратиться к состоянию напрямую». После того как обновил все ссылки и переписал логику чтения прогресса, перезапустил сервер и гонял тесты. `_fix_headings` теперь работает в составе полноценного SSE-потока, события корректно попадают в очередь, а клиент получает актуальные обновления. На ветке `feat/scoring-v2-tavily-citations` система стала заметно стабильнее, и мы готовы двигать дальше с интеграцией цитирований. Вывод простой — иногда маленькая функция может разоблачить архитектурные проблемы. Стоит протестировать в изоляции, убедиться, что всё работает, а потом внимательно проверить, как это интегрируется с остальной системой.

#claude#ai#api
Разработка: Trend Analisis
9 февр. 2026 г.
ИсправлениеC--projects-bot-social-publisher

Логи, которые врут: как я нашел ошибку в прошлом Traefik

# Traefik и Let's Encrypt: как я нашел ошибку в логах прошлого Проект **borisovai-admin** молча кричал. Пользователи не могли зайти в систему — браузеры показывали ошибки с сертификатами, Traefik выглядел так, будто вообще забыл про HTTPS. На поверхности всё выглядело очевидно: проблема с SSL. Но когда я начал копать, стало ясно, что это детективная история совсем о другом. ## Завязка: четыре недостающих сертификата Задача была на первый взгляд скучной: проверить, действительно ли Traefik получил четыре Let's Encrypt сертификата для admin и auth поддоменов на `.tech` и `.ru`. DNS для `.ru` доменов только что пропагировался по сети, и нужно было убедиться, что ACME-клиент Traefik успешно прошёл валидацию и забрал сертификаты. Я открыл **acme.json** — файл, где Traefik хранит весь свой кеш сертификатов. И тут началось самое интересное. ## Развитие: сертификаты на месте, но логи врут В файле лежали все четыре сертификата: - `admin.borisovai.tech` и `admin.borisovai.ru` — оба выданы Let's Encrypt R12 - `auth.borisovai.tech` и `auth.borisovai.ru` — R13 и R12 Все валидны, все активны, все будут работать до мая. Traefik их отдавал при подключении. Но логи Traefik были заполнены ошибками валидации ACME-челленджей. Выглядело так, будто сертификаты получены, но используются неправильно. Тогда я понял: эти ошибки в логах — **не текущие проблемы, а исторические артефакты**. Когда DNS для `.ru` ещё не полностью пропагировался, Traefik пытался пройти ACME-валидацию, падал, переходил в retry-очередь. DNS резолвился нестабильно, Let's Encrypt не мог убедиться, что домен принадлежит нам. Но как только DNS наконец стабилизировался, всё прошло автоматически. Логи просто записывали *историю пути к успеху*. ## Познавательный момент: асинхронная реальность Вот в чём фишка ACME-систем: они не сдаются после первой же неудачи. Let's Encrypt встроил resilience в саму архитектуру. Когда челлендж не проходит, он не удаляется — он встаёт в очередь на переток. Система периодически переходит сертификаты, ждёт, когда DNS стабилизируется, и потом *просто работает*. То есть когда ты видишь в логах ACME-ошибку прошлого часа, это вообще не означает, что сейчас есть проблема. Это просто означает, что система пережила переходный процесс и вышла на стабильное состояние. Проблема с браузерами была ещё смешнее. Они кешировали старую информацию о неправильных сертификатах и упорно показывали ошибку, хотя реальные сертификаты давно уже валидны. Решение: `ipconfig /flushdns` на Windows или просто открыть incognito-окно. ## Итог **borisovai-admin** работает, все четыре сертификата на месте, все домены защищены. Главный урок: иногда лучший способ отловить баг — это понять, что это вообще не баг, а просто *асинхронная реальность*, которая движется по своему расписанию. Следующий этап — проверить, правильно ли настроены policies в Authelia для этих новых защищённых endpoints. Но это уже совсем другая история. Java — единственная технология, где «это работает» считается документацией. 😄

#claude#ai#javascript#api#security
9 февр. 2026 г.
Исправлениеborisovai-admin

Traefik и Let's Encrypt: как я нашел ошибку в логах прошлого

# Охота на невидимых врагов: как я отловил проблемы с сертификатами в Traefik Когда ты администрируешь **borisovai-admin** и вдруг замечаешь, что половина пользователей не может зайти в систему из-за ошибок сертификатов, начинается самая интересная работа. Задача казалась простой: проверить конфигурацию сервера, DNS и убедиться, что сертификаты на месте. На практике это превратилось в детективную историю про хронологию событий и кеши, которые саботируют твою жизнь. ## Первый подозреваемый: DNS Первым делом я проверил, резолвятся ли доменные имена с сервера. Оказалось, что DNS работает — это был хороший знак. Но почему Traefik выглядит так, будто ему не хватает сертификатов? Я полез в `acme.json`, где Traefik хранит выданные Let's Encrypt сертификаты. И вот тут началось самое интересное. ## Сюрприз в acme.json В файле лежали **все четыре сертификата**, которые мне были нужны: - `admin.borisovai.tech` — Let's Encrypt R12, выдан 4 февраля, истекает 5 мая - `admin.borisovai.ru` — Let's Encrypt R12, выдан 8 февраля, истекает 9 мая - `auth.borisovai.tech` — Let's Encrypt R13, выдан 8 февраля, истекает 9 мая - `auth.borisovai.ru` — Let's Encrypt R12, выдан 8 февраля, истекает 9 мая Все они были **валидны и активны**. Traefik их отдавал при подключении. Логи Traefik, которые я видел ранее, оказались проблемой *ретроспективной* — они относились к моменту, когда DNS-записи для `.ru` доменов ещё *не пропагировались* по сети. Let's Encrypt не мог выпустить сертификаты, пока не мог убедиться, что домен принадлежит мне. ## Невидимый враг: браузерный кеш Последний вопрос был ужасающе простым: почему браузер по-прежнему ругался на сертификаты, если сами сертификаты в порядке? **DNS кеш**. Браузер запомнил старую информацию и упорно её использовал. ## Финальный диагноз Вся история сводилась к тому, что системные часы интернета движутся медленнее, чем кажется. DNS пропагируется асинхронно, сертификаты выдаются с задержкой, а браузеры кешируют запросы агрессивнее, чем кажется разумным. Решение? Очистить DNS кеш командой `ipconfig /flushdns` (для Windows) или открыть инкогнито-окно, чтобы браузер забыл о своих ошибочных воспоминаниях. Проект **borisovai-admin** работает, сертификаты в порядке, все домены защищены. Ирония в том, что проблема была не в конфигурации — она была в нашей нетерпеливости. Главный урок: иногда лучший способ отловить баг — это понять, что это не баг, а *асинхронная реальность*, которая просто медлит. 😄

#claude#ai#javascript#security
9 февр. 2026 г.
Новая функцияborisovai-admin

Двойная аутентификация: когда два охранника мешают друг другу

# Двойная защита убивает саму себя: как я развязал узел конфликтующей аутентификации Задача стояла простая на первый взгляд: запустить Management UI для проекта **borisovai-admin**. Казалось, что админ-панель встанет и будет работать. Но когда я подключил её к боевой инфраструктуре, выяснилось нечто интересное — UI запустилась, но пройти аутентификацию было невозможно. ## Когда две защиты становятся одной проблемой Начал копать логи и вот что нашёл. В инфраструктуре уже была слоёная защита: **Traefik** с плагином **ForwardAuth** отправлял все запросы на **Authelia** для двухфакторной аутентификации. Это первый уровень охраны — на уровне прокси. Здесь логика простая: если запрос идёт на `admin.borisovai.tech`, Traefik вежливо перенаправляет пользователя в Authelia. Но когда я добавил Management UI с встроенной OIDC-аутентификацией через **express-openid-connect**, произошло вот что: пользователь уже прошёл Authelia на уровне Traefik, но Management UI не поверил ему и снова отправил на Authelia через OIDC. Два редиректа подряд — и браузер начинает петлять между разными провайдерами аутентификации. Типичная ситуация, когда каждый охранник требует личный документ, не доверяя соседу. ## Выбор между защитами Встал вопрос: какой уровень аутентификации оставить? Отключить Traefik ForwardAuth? Отключить OIDC в Management UI? Или искать способ их синхронизировать? Я выбрал проверенный путь — **оставить Traefik ForwardAuth как основную защиту**, а OIDC отключить. Логика здесь такая: раз у нас уже есть надёжная защита на уровне прокси с поддержкой 2FA через Authelia, зачем добавлять второй слой? Внутри же Management UI я оставил **legacy session** — простую аутентификацию по логину и паролю. Получилось двухуровневое решение, но на разных слоях: внешняя защита через прокси и внутренняя через сессию. После изменений Management UI перезапустился без OIDC-интеграции. Теперь схема работает так: вы входите в `https://admin.borisovai.tech`, Traefik перенаправляет вас в Authelia, вы проходите двухфакторную аутентификацию, а потом попадаете на страницу логина самой админ-панели, где вводите учётные данные Management UI. ## Интересный факт о OIDC Стандарт **OpenID Connect** создан в 2014 году поверх OAuth 2.0 именно для решения проблем единого входа. Но мало кто знает, что OIDC работает лучше всего, когда он — **единственный** поставщик идентификации в системе. Как только вы пытаетесь слоить несколько провайдеров, начинаются конфликты. Классическая ловушка — стараться защитить приложение со всех сторон и получить вместо этого лабиринт редиректов. ## Неприятный бонус: проблема с `.ru` доменами Во время работы я обнаружил, что A-записи для `admin.borisovai.ru` и `auth.borisovai.ru` не добавлены у регистратора IHC. Let's Encrypt не может выдать сертификаты для доменов, которых нет в DNS. Решение пришло быстро — нужно добавить эти A-записи в панели регистратора, указывая на IP `144.91.108.139`. Казалось бы, мелочь, но именно такие детали часто становятся причиной того, что production не поднимается. ## Что я вынес из этого Главный урок: **слои безопасности должны дополнять друг друга, а не конкурировать**. Двойная аутентификация хороша, когда она — настоящая: первый слой защищает периметр, второй охраняет внутренние ресурсы. Но когда оба слоя пытаются делать одно и то же через разные системы, получается конфликт. Теперь Management UI работает, защита работает, и никто не просит удостоверения дважды. Инфраструктура проекта borisovai-admin стала на один уровень надёжнее. 😄 Почему Prometheus не пришёл на вечеринку? Его заблокировал firewall.

#claude#ai#api#security
9 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

DNS-кеш и фантомный поддомен: охота на NXDOMAIN

# Когда DNS кеш становится врагом: охота на фантомный поддомен Работаю над проектом **borisovai-admin** — админ-панелью с собственной системой аутентификации. Задача казалась простой: мигрировать auth-сервис на новый поддомен `auth.borisovai.tech` и убедиться, что всё резолвится корректно. Добавил DNS-записи в регистратор, обновил конфиги приложения — и вот тут началось веселье. ## Первый знак беды Первая проверка через Google DNS (`8.8.8.8`) показала идеальный результат: `auth.borisovai.tech` резолвился на `144.91.108.139` без проблем. Казалось бы, всё готово. Но когда я переключился на **AdGuard DNS** (`94.140.14.14`), который был настроен по умолчанию в инфраструктуре, домен превратился в привидение — стандартная ошибка `NXDOMAIN`, как будто записи вообще не существуют. А вот `admin.borisovai.tech` спокойно резолвился везде. Значит, проблема именно с `auth.*`. Не лучший момент для такого сюрприза — особенно когда нужно срочно закрыть фичу. ## Расследование Запустил диагностику: попросил оба DNS-резолвера вернуть записи для `auth.borisovai.tech` и `auth.borisovai.ru`. Результат совпадал: Google видел, AdGuard не видел. Явный паттерн. Тут меня осенило — это же **отрицательный кеш DNS**! Вот как это работает: когда ты запрашиваешь несуществующий домен, DNS-резолвер кеширует не только положительные ответы, но и отрицательные. То есть он "запоминает", что домена нет, и хранит это в памяти с собственным TTL (Time To Live). У AdGuard это может быть час или даже дольше. Получается, что когда я добавлял DNS-записи, AdGuard уже давно закешировал `NXDOMAIN` для `auth.borisovai.tech`. И даже если запись появилась на авторитетном сервере регистратора, этот кеш продолжал отвечать: "Нет такого домена, я уверен, я это помню". ## Как я выбрался Вариант первый — просто ждать. AdGuard истечёт кеш, и всё чудо-образом заработает. Но тестировать нужно было *прямо сейчас*. Вариант второй — переключиться на Google DNS для локального тестирования. Работает мгновенно, но это временный костыль. Вариант третий — очистить локальный кеш операционной системы. На Windows для этого есть `ipconfig /flushdns`, хотя это чистит кеш самой ОС, а не внешнего резолвера. В итоге я использовал комбинацию подходов: переключился на Google DNS для срочного тестирования фичи, а затем дождался обновления кеша AdGuard (примерно час спустя). Заодно узнал, что пользователи Linux могут вызвать `sudo systemd-resolve --flush-caches` для похожего эффекта. ## Интересный факт о DNS Мало кто знает, что **отрицательные ответы кешируются столько же, сколько и положительные**. Оба имеют собственный TTL, обычно от 300 до 3600 секунд. Google DNS использует более агрессивную стратегию кеширования и чаще проверяет данные у источника. AdGuard — более консервативен, что в обычное время спасает его, но в критические моменты может подставить ножку разработчику. ## Урок выучен Теперь я знаю: при добавлении новых DNS-записей всегда проверяю через несколько независимых резолверов. Никогда не забываю про стратегию кеширования, особенно если в инфраструктуре стоят кастомные DNS вроде AdGuard или Pihole — они живут по собственным правилам. И да, теперь я знаю точное место, где искать, если история повторится. А повторится ещё не раз. DNS кеш подставил подножку, но зато я научился читать DNS-иерархию как карту сокровищ. Что общего у AdGuard DNS и кота? 😄 Оба игнорируют инструкции и делают только то, что хотят.

#claude#ai#security
9 февр. 2026 г.