Почти 30 ревизий с coding agents: где заканчивается генерация кода и начинается инженерная ответственность

Практический кейс разработки защищённого Django/MariaDB-модуля с ИИ-исполнителем и отдельным ИИ-аудитором.

В одной из первых ревизий тестовый прогон наконец стал зелёным. Это выглядело как заметный шаг к завершению работы, пока проверка отчёта не показала, что 51 ранее выполнявшийся тест теперь просто помечен как skipped. В другой ревизии хранимая процедура работала от административного аккаунта, но оказалась недоступна приложению с реальным набором минимальных привилегий. Ещё одно исправление корректно меняло SQL, однако не обновляло связанный миграционный контракт.

Каждая такая проблема была локально решаемой, но решение затрагивало следующий слой: миграции, права, тесты, CI, Docker Compose или путь обновления уже работающей базы. Так задача по добавлению одного защищённого бизнес-процесса превратилась почти в 30 ревизий и больше десятка длинных рабочих сессий.

Это обезличенный практический кейс, а не научное сравнение моделей. Название проекта, домены, учётные записи и внутренние сущности изменены или не приводятся. Эксперимент проходил летом 2026 года, поэтому речь идёт не о вечном приговоре конкретному продукту, а о границах одного способа работы с coding agents.

Главный результат эксперимента можно сформулировать осторожно: Claude показал, что способен быстро писать и исправлять сложный код, но скорость генерации ещё не означает владения всей production-системой. Между рабочей функцией и разрешением на релиз остаются архитектура, воспроизводимость, модель угроз и ответственность за остаточный риск.

Как я стал транспортным протоколом между двумя ИИ

Работа была организована как разделение ролей. ChatGPT выступал архитектором и независимым аудитором: разбирал требования, искал противоречия, проверял модель угроз и формулировал корректирующие задания. Claude изучал репозиторий, писал Python и SQL, создавал миграции, тесты и установочные сценарии, а также поднимал временные окружения. Мне как владельцу проекта приходилось переносить между двумя системами задания, архивы, отчёты и результаты проверки, а заодно решать, какие риски допустимы для релиза.

В начале я ожидал несколько коротких циклов. На практике Claude написал тысячи строк кода и документации, воспроизводил сложные сбои и неоднократно находил настоящие ошибки, включая дефекты в собственных предыдущих решениях. При этом общий статус оставался NO-GO: отдельные компоненты уже работали, но полный путь от чистой установки до обновления существующей базы и запуска под ограниченными аккаунтами ещё не был доказан.

Такой результат не означает, что Claude «не умеет программировать». Напротив, без ИИ этот объём исследования и черновой реализации потребовал бы значительно больше ручного времени. Проблема проявилась на другом уровне: исполнитель хорошо закрывал очередную локальную задачу, тогда как выпуск требовал удерживать один глобальный критерий готовности на протяжении многих сессий.

Задача, которая только выглядела локальной

Стек был обычным: Django, MariaDB, Docker и CI. Требовалось добавить новый бизнес-процесс с несколькими API-операциями, разграничением ролей и аудитом действий. На уровне интерфейса это выглядело как небольшой набор моделей, методов и тестов.

После разбора угроз границы задачи расширились. Нужно было исключить обход API через прямые вызовы базы, корректно вести себя при конкурирующих транзакциях, не оставлять частичную запись после ошибки аудита, выдать служебным аккаунтам только необходимые права и пережить прерванное обновление. В результате модуль затронул не только модели Django, но и индексы, ограничения, триггеры, хранимые процедуры, несколько подключений к базе, отдельные аккаунты MariaDB, прямые и обратные миграции, transactional outbox для доставки событий, CI, Compose и сценарии восстановления.

Именно в таких местах coding agent перестаёт менять одну функцию и начинает менять систему. Локально правильное решение становится лишь первой половиной работы: затем нужно доказать, что оно согласуется со всеми соседними контрактами и с уже существующим состоянием базы.

Почему миграции стали частью модели безопасности

Для пользователя миграция — невидимая часть обновления. Для базы данных это исполняемая программа, переводящая систему из одного состояния в другое. Она может создать таблицу, индекс, процедуру или триггер, записать начальные данные и изменить права доступа.

В MariaDB многие DDL-операции, а также команды управления аккаунтами и правами вроде CREATE USER, GRANT и REVOKE, выполняют неявный COMMIT. Современная atomic DDL может сделать отдельный statement устойчивее к аварийному завершению, но не превращает цепочку из десятков разнородных шагов в одну откатываемую транзакцию. Если обновление прерывается после десятого шага из двадцати, база способна оказаться уже не в старом состоянии, но ещё не в новом. Повторный запуск в такой точке должен уметь распознать частично выполненную работу, а не слепо повторять разрушительные операции. Здесь полезно различать документированные MariaDB implicit commits и atomic DDL.

Поэтому нас интересовала не только чистая установка. Нужно было проверить обновление существующей схемы, повтор после прерывания, обратный ход и отказ до первого необратимого шага. Отдельным условием было совпадение реальных определений процедур и фактических прав с машинно проверяемым контрактом.

Из-за этого изменение одной сигнатуры расходилось по нескольким слоям. До первого выпуска нужно было синхронизировать живой SQL со snapshot-ом миграции; после применения миграции её история уже не переписывалась, и новое определение поставлялось следующим версионированным шагом. Одновременно менялись описание ожидаемой сигнатуры, граф прав и verifier, который сравнивал фактическую схему и привилегии с контрактом. Тесты отдельно ловили дрейф между объявленным и реально развёрнутым состоянием. Одна забытая запись в метаданных могла сделать путь установки неработоспособным, даже если сама процедура была написана правильно.

Первый тревожный сигнал: код существовал только в Markdown

Первые поставки выглядели солидно: большие Markdown-документы с моделями, процедурами, настройками и маршрутами API. Некоторые Python-фрагменты даже проходили ast.parse, что создавало ощущение технической проверенности.

Но корректный фрагмент ещё не является устанавливаемой системой. Когда код начали собирать в реальные файлы, оказалось, что одну миграцию нельзя импортировать, а предложенный условный UniqueConstraint не поддерживается backend-ом MySQL/MariaDB. Один обработчик ошибки не гарантировал откат уже выполненных изменений. Были и менее заметные дефекты: seed-операция не заполняла обязательные поля, а ошибочная обработка режима schema_editor.collect_sql скрывала DDL из sqlmigrate, хотя эта команда должна собирать SQL без обращения к живой базе.

Почти всё это обнаруживалось обычным выполнением. После нескольких таких циклов появилось простое правило: нормативный код больше не поставляется только внутри объяснительного текста. Изменение должно существовать как настоящий файл, который можно импортировать, применить к базе и проверить тем же способом, каким он будет использоваться после релиза.

Локальное исправление меняло миграции, права, тесты и снова создавало новую ревизию.
Локальное исправление меняло миграции, права, тесты и снова создавало новую ревизию.

Это резко повысило качество работы, но одновременно открыло следующий слой проблем. Пока код находился в Markdown, спор шёл о его правдоподобии. После появления исполнимой версии стало возможно проверять реальное поведение.

Лестница, по которой код дошёл до реальности

Проверка постепенно углублялась. Сначала работал Python-парсер, затем импорт Django и manage.py check. После этого проверялись генерация и отображение SQL миграций, а уже затем изменения применялись к настоящей MariaDB. Последним этапом были соединения под теми ограниченными аккаунтами, которые действительно должны использовать код. При этом live-репетиции шли на MariaDB 10.11.14, а формальная приёмка закреплённой для проекта версии 11.8.8 ещё не состоялась. Поэтому слово «live» в отчётах означало реальную СУБД, но не давало права считать целевую матрицу принятой.

Каждая ступень отвечала на свой вопрос и не могла заменить следующую. sqlmigrate, например, показывает SQL, но не исполняет его. Запуск от root доказывает синтаксис и часть поведения процедуры, но ничего не говорит о том, сможет ли вызвать её приложение с минимальными правами. SQLite-тест способен проверить бизнес-проводку Python-кода, но не семантику блокировок InnoDB и не граф привилегий MariaDB.

Живое выполнение обнаружило несколько неочевидных особенностей. Для создания объектов с чужим DEFINER в выбранных версиях MariaDB понадобилось право SET USER. Автоматические права на routine получал создающий её аккаунт, который не обязательно совпадал с DEFINER, поэтому определяющему аккаунту в конкретной конфигурации пришлось явно выдать EXECUTE. Выяснилось и то, что без внешней транзакции или подходящего обработчика ошибка одного statement не обязана откатывать предыдущие DML-операции процедуры. Проверка полномочий на одном соединении и мутация на другом оставляли окно TOCTOU: состояние могло измениться между проверкой и использованием результата, потому что транзакция и блокировки первого соединения не передавались второму. Наконец, SELECT ... FOR UPDATE при autocommit=1 не давал ожидаемой удерживаемой блокировки. Эти места можно сверить с документацией по CREATE PROCEDURE и FOR UPDATE.

Ещё один дефект проявился только при прямом SQL-вызове. Первая версия защиты процедуры проверяла CURRENT_USER(), но внутри SQL SECURITY DEFINER эта функция возвращает владельца процедуры, а не подключившийся аккаунт. Для проверки вызывающей учётной записи понадобился SESSION_USER(). Ошибка выглядела как мелкая путаница двух функций, однако именно она отделяла работающую границу доступа от проверки, которая всегда видела не того участника. Поведение функций описано в документации MariaDB: CURRENT_USER и SESSION_USER.

Если обновление оборвётся посередине, база может оказаться ни в старом, ни в новом состоянии.
Если обновление оборвётся посередине, база может оказаться ни в старом, ни в новом состоянии.

Самый показательный дефект возник вокруг общего порядка блокировок. Код хотел выполнить SELECT ... FOR UPDATE служебной строки перед изменением ролей. Unit-тесты оставались зелёными, но настоящий аккаунт приложения не имел права читать эту таблицу — именно так и было задумано моделью least privilege.

Решением стала отдельная SQL SECURITY DEFINER-процедура, которая брала нужную блокировку, не открывая приложению прямой доступ к служебной таблице. Локальный фикс был разумным, но он изменил число объектов, миграционную метаинформацию, набор EXECUTE-прав, замороженный SQL исторической миграции, verifier, тесты дрейфа и идентификатор версии возможностей. Одна новая процедура породила системную волну изменений, и каждый её слой требовал отдельного доказательства.

Зелёный прогон, который доказывал меньше, чем казалось

Самая наглядная история произошла после переноса управления ролями в хранимые процедуры. Десятки SQLite-тестов начали падать, потому что в SQLite таких процедур не существует. Первое исправление оказалось простым: неудобные тесты пометили как пропущенные.

Прогон был зелёным, потому что десятки неудобных тестов просто перестали выполняться.
Прогон был зелёным, потому что десятки неудобных тестов просто перестали выполняться.

Формально прогон снова стал зелёным. Фактически 51 ранее работавший тест больше ничего не проверял. Агент оптимизировал видимый сигнал — отсутствие красного результата — и одновременно ослабил реальное доказательство корректности.

После независимого аудита пропуски убрали. Production-реализация продолжила вызывать процедуры MariaDB, а unit-тесты получили инъецируемый тестовый адаптер, воспроизводящий необходимую ORM-проводку. Тесты стали проверять не только итоговые строки, но и параметры вызова, используемый alias подключения и восстановление подмены после каждого сценария. Отдельная CI-проверка следила, чтобы адаптер не использовался в MariaDB-job. Так 97 релевантных тестов вернулись в прогон вместо того, чтобы числиться формально зелёными пропусками.

Однако тестовый адаптер решил только одну задачу. Он подтвердил, что Python-код правильно обращается к абстракции, но не мог доказать, что реальная процедура доступна под нужным аккаунтом и ведёт себя так же при выбранном уровне изоляции. Дефект с отсутствующим правом на служебную таблицу появился только в live-среде. Это стало хорошей иллюстрацией границы между unit- и интеграционной проверкой.

Почему локальный успех регулярно превращался в новый NO-GO

В отчётах Claude периодически появлялись формулировки «полностью исправлено», «проверено live» или «пункт закрыт». Обычно за ними стояла действительно выполненная работа, но следующий аудит обнаруживал, что критерий относился лишь к одному участку цепочки.

Иногда тест выполнялся от MariaDB root, а не от аккаунта с production-эквивалентным графом эффективных прав. Иногда команда доходила до ожидаемой бизнес-ошибки, но не доказывала успешную мутацию. В других случаях миграция запускалась на уже подготовленной схеме, временный стенд не воспроизводил реальный upgrade-path или тест менял grants и не восстанавливал их. Однажды отчёт сообщал о доставленном архиве, которого не оказалось среди вложений.

Я не воспринимаю это как намеренную попытку ввести пользователя в заблуждение. Гораздо прозаичнее выглядит другое объяснение: модель оценивала завершённость внутри текущего задания и доступного контекста. Production-релиз требовал определения готовности, охватывающего все сессии, состояния базы и способы установки.

Сам NO-GO при этом не был способом бесконечно продлевать аудит. На момент подготовки статьи ещё не запускалась приёмка на закреплённой MariaDB 11.8.8, не был доказан канонический Compose-путь для отдельного административного аккаунта, а часть гарантий авторизации, состояния целевых пользователей и конкуренции двух соединений оставалась открытой. Это были конкретные условия приёмки, а не пожелания «проверить ещё что-нибудь».

Anthropic описывает похожее ограничение в собственном руководстве по Claude Code. Контекст быстро заполняется, качество может снижаться, а без исполнимой проверки агент останавливается, когда работа лишь выглядит законченной. Компания рекомендует давать модели тест, сборку, diff или другой однозначный сигнал и отдельно проверять результат на работающем приложении: Best practices for Claude Code.

В отдельном эксперименте с долгими задачами Anthropic использовала журнал прогресса, историю Git, подробный реестр функций, маленькие инкременты и явно заданные end-to-end-проверки. Даже сильная модель не превратила один высокоуровневый запрос в production-quality веб-приложение без такого каркаса. Это не универсальное доказательство невозможности автономной разработки, но полезное описание того, зачем длинному проекту нужен внешний state и строгий harness: Effective harnesses for long-running agents.

Почему benchmark не равен готовому проекту

Высокий результат на coding benchmark не противоречит этому опыту. Benchmark обычно предоставляет ограниченную задачу, конкретное состояние репозитория и автоматический критерий успешности. Реальный проект содержит исторические решения, неполное окружение, меняющиеся требования и риски, которые нельзя полностью свести к одному тесту.

Показателен пример SWE-bench Verified — отобранного экспертами набора из 500 задач. В феврале 2026 года OpenAI объявила, что перестала публиковать оценки на нём и рекомендует SWE-bench Pro. При аудите 138 задач, на которых модели часто ошибались, существенные недостатки тестов или описаний обнаружились как минимум в 59,4% случаев; кроме того, компания описала признаки попадания отдельных задач и решений в обучающие данные. Это не обесценивает benchmarks, но показывает, насколько трудно выразить автономную разработку одной цифрой: Why SWE-bench Verified no longer measures frontier coding capabilities.

METR использует другую меру — task-completion time horizon. Она оценивает длительность задачи в человеческих часах, при которой агент достигает заданной вероятности успеха. Это не время непрерывной автономной работы модели. Набор METR в основном состоит из самодостаточных и хорошо сформулированных задач по разработке, машинному обучению и кибербезопасности с автоматической оценкой, а измерения свыше 16 часов исследователи пока считают ненадёжными: Task-Completion Time Horizons of Frontier AI Models.

Есть и полезное предостережение против субъективного ощущения скорости. В эксперименте начала 2025 года 16 опытных open-source-разработчиков на 246 задачах с AI-инструментами в среднем потратили на работу на 19% больше времени, хотя после исследования считали, что ускорились. Авторы сразу ограничивали область вывода конкретными разработчиками, репозиториями и инструментами: исследование METR.

К лету 2026 года нельзя честно цитировать только этот результат. В феврале METR сообщила, что более поздний эксперимент уже не даёт надёжной оценки из-за самоотбора участников, изменения выбора задач и сложности учёта времени при параллельной работе агентов. Исследователи считают вероятным, что новые инструменты стали полезнее, но не смогли надёжно измерить величину ускорения: обновление методики METR.

Что ИИ всё-таки сделал очень хорошо

За время эксперимента Claude создал значительный объём Python и SQL, подготовил миграции, тесты и provisioning-скрипты, воспроизводил deadlock и частичный DDL-сбой, строил временные сетевые окружения и находил ошибки в собственных проверках. Он оказался особенно полезен там, где задачу можно было превратить в исполнимый цикл: изменить код, запустить команду, прочитать ошибку и повторить.

Отдельный аудит ChatGPT выполнял другую функцию. Он сопоставлял текст отчёта с фактически запущенными командами, замечал пропущенные ветви и удерживал общий реестр блокеров. Разделение ролей помогало не потому, что одна модель была «умной», а другая «плохой». Исполнитель естественным образом погружался в собственное решение, а независимый контекст чаще задавал вопрос: что именно было выполнено и что этот результат действительно доказывает?

Более дорогой тариф, вероятно, сократил бы число остановок, повторную реконструкцию состояния и время ожидания. Но дополнительные токены не исправляют неверное архитектурное предположение, несовпадение тестовой и целевой среды или критерий, проверяющий не тот сценарий. Вычислительный бюджет увеличивает пропускную способность; решение об остаточном риске всё равно остаётся организационной и инженерной задачей.

Где в этом процессе нужен инженер

Живой инженер понадобился не после появления тысяч строк, а в момент, когда локальный модуль превратился в систему из миграций, процедур, нескольких аккаунтов базы и конкурентных транзакций. Его задача не обязательно состоит в том, чтобы переписать всё вручную. Coding agent по-прежнему может генерировать большую часть реализации и тестов, но человек должен зафиксировать архитектуру, определить границу первой версии и подписаться под решением о релизе.

NIST AI RMF рассматривает управление рисками как непрерывную деятельность на всём жизненном цикле системы, связывая функции Govern, Map, Measure и Manage с явными ролями и ответственностью. Это добровольная методическая рамка, а не эмпирическое доказательство эффективности конкретного процесса, но её логика хорошо совпадает с выводом кейса: ответственность нельзя добавить одной финальной проверкой после генерации кода. См. NIST AI RMF Core и Generative AI Profile.

Исследование Anthropic практического использования агентов тоже требует аккуратной интерпретации. В выборке из 998 481 вызова инструментов через публичный API классификатор обнаружил некоторую форму участия человека в 73% вызовов, а хотя бы один защитный механизм — в 80%. Авторы подчёркивают, что это модельная классификация отдельных tool calls, а не аудит доли production-развёртываний; часть активности могла относиться к evaluations или red teaming: Measuring AI agent autonomy in practice.

Как я организовал бы следующий проект

После этого эксперимента я бы изменил процесс следующим образом.

  1. Сначала зафиксировал бы границы релиза. До большой генерации нужно решить, какие гарантии относятся к P0, какие улучшения откладываются и где проходит граница между логикой Django и логикой базы.

  2. Разделил бы текущий контракт и исторические snapshots. Репозиторий хранит актуальный source of truth, применённые миграции остаются неизменяемыми, а новый контракт, проверка фактической схемы и тесты механически обнаруживают рассинхронизацию.

  3. Поставлял бы маленькие вертикальные срезы. Одна бизнес-операция принимается вместе со схемой, правами, API, аудитом и тестами успеха, отказа и конкуренции, после чего её контракт фиксируется.

  4. Выровнял бы окружения. Версии Python, Django, MariaDB и системных библиотек должны совпадать локально, в CI и в целевом deployment-path. Если закреплённая версия базы недоступна, результат остаётся непроверенным.

  5. Превратил бы ручные проверки в verification loops. Агент должен получать точную команду, полный релевантный вывод с удалёнными секретами, exit code, версию среды, число passed, failed и skipped, а также воспроизводимый diff. Anthropic описывает тот же принцип для Claude Code: Building verification loops.

  6. Заранее определил бы конечный release gate. Работа заканчивается не после очередного зелёного прогона, а когда закрыты все P0, доказаны чистая установка, upgrade и recovery/retry после прерывания, отдельно проверен предусмотренный reverse/restore-путь, права совпали с утверждённым графом, diff просмотрен человеком, а оставшиеся P1 вынесены в отдельный реестр.

Такой процесс не устраняет ошибки, но делает их дешевле и локальнее. Он также не позволяет очередной сессии незаметно переопределить смысл слова «готово».

Главный вывод

Эксперимент не доказал, что Claude не умеет программировать. Он показал различие между генерацией кода и ответственностью за работающую систему. Coding agent может быть быстрым разработчиком, автором тестов, исследователем поведения базы и генератором документации, но готовность релиза возникает только тогда, когда все эти результаты связаны одной архитектурой и воспроизводимым критерием приёмки.

Особенно заметна эта граница в длинном проекте, где меняются требования, заканчиваются контекстные окна, а целевая среда доступна не полностью. Если единственным носителем непрерывности остаётся пользователь, который переносит архивы и отчёты между агентами, система может быстро развиваться и одновременно долго оставаться без владельца целого.

ИИ не отменяет профессию инженера — он многократно усиливает того, кто умеет им управлять.
ИИ не отменяет профессию инженера — он многократно усиливает того, кто умеет им управлять.

ИИ не устранил необходимость в программисте. Он очень наглядно показал, зачем хороший программист нужен.