8 мин

Как выбрать подход, когда нужна сложная бизнес-логика

Сложная бизнес-логика требует ясной границы между работой AI-агентов, ручной реализацией правил, транзакций и обязательной проверкой человеком.

Как выбрать подход, когда нужна сложная бизнес-логика

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

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

Сначала отделите объем функций от сложности правил

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

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

Затем для каждого инварианта задайте четыре вопроса:

  1. Где хранится источник истины для входных данных?
  2. Кто и при каком состоянии имеет право менять результат?
  3. Что произойдет при двух одновременных запросах?
  4. Как оператор поймет причину отказа и восстановит процесс?

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

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

Расчеты генерируйте только после таблицы решений

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

Начните с таблицы решений. Она короче технического задания и лучше обнаруживает противоречия:

Базовые варианты выглядят так:

  • обычный клиент с суммой до 100 000 не получает скидку;
  • обычный клиент с суммой от 100 000 и сроком от 12 месяцев получает 3%;
  • приоритетный клиент с суммой до 100 000 и сроком от 12 месяцев получает 4%;
  • приоритетный клиент с суммой от 100 000 и сроком от 12 месяцев получает 7%.

Эта таблица все еще неполна. Что происходит ровно на 100 000? Достаточно ли одного дня до полного года? Применяется ли скидка до налога? Можно ли совместить ее с промокодом? Такие вопросы должен закрыть владелец процесса. Агент полезен тем, что превращает готовую таблицу в функцию и предлагает граничные тесты, но он не имеет права выбирать экономическую политику.

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

Хороший тест расчетного модуля читается как договор, а не как копия реализации:

func TestDiscountAtBoundary(t *testing.T) {
    got := Discount(Input{
        Customer: Priority,
        NetKopecks: 10_000_000,
        ContractDays: 365,
    })
    if got.Percent != 7 || got.AmountKopecks != 700_000 {
        t.Fatalf("got percent=%d amount=%d", got.Percent, got.AmountKopecks)
    }
}

Сначала человек проверяет смысл примера и ожидаемую сумму. Затем агент может расширить набор: значение на копейку ниже порога, нулевую сумму, отрицательный ввод, високосный год, несовместимую скидку. Генерировать десятки тестов до согласования одного эталонного примера бессмысленно. Они лишь закрепят случайную трактовку.

Согласование должно быть машиной состояний

Цепочка согласований надежна, когда допустимые состояния и переходы перечислены явно. Набор логических полей isApproved, isRejected, isPaid быстро создает невозможные комбинации: заявка одновременно одобрена и отклонена, платеж проведен до последнего согласования, отмененный документ снова меняется обычным редактированием.

Для заявки достаточно начать с состояний draft, submitted, under_review, approved, rejected, cancelled, а затем описать переходы. Важна не красота диаграммы, а единая функция, которая принимает текущее состояние, команду и контекст пользователя. Контроллер, фоновая задача и административный скрипт должны вызывать один и тот же код. Если проверка существует только в интерфейсе, прямой запрос обойдет ее.

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

Не просите агента «добавить workflow». Передайте ему разрешенную карту переходов и ожидаемые ошибки. Полезный контракт команды выглядит так:

{
  "request_id": "8d6f2a31",
  "command": "approve",
  "application_id": "A-1842",
  "expected_version": 6,
  "reason": "Лимит подтвержден"
}

Успешный ответ возвращает новое состояние и версию. Конфликт версий должен давать отдельную ошибку, например 409 state_conflict, а запрещенный переход при актуальной версии, 422 transition_not_allowed. Это не косметика API. Оператору нужны разные действия: при конфликте загрузить свежие данные, при запрете исправить состояние или маршрут.

Версия защищает от тихого перетирания. Два согласующих могут открыть одну заявку, после чего первый одобрит ее, а второй попытается отклонить старую версию. Условное обновление WHERE id = ? AND version = ? позволит изменить ровно одну актуальную строку. Если число измененных строк равно нулю, сервис обязан перечитать состояние, а не объявлять успех.

Роли не заменяют проверку конкретного объекта

Роль отвечает на общий вопрос «что пользователь обычно может делать», но бизнес-операция требует ответа «что этот пользователь может сделать с этим объектом сейчас». Менеджер может видеть заявки своего подразделения, финансовый контролер может утверждать суммы в пределах назначенного лимита, автор не может дать последнее согласование собственной заявке. Простая проверка role == admin не выражает эти ограничения.

OWASP Authorization Cheat Sheet советует запрещать доступ по умолчанию и проверять разрешение в каждом запросе. Я согласен с обоими правилами, но для бизнес-систем добавляю еще одно: решение о доступе должно использовать данные объекта и текущее состояние, а не только роль из токена. Токен подтверждает контекст пользователя, однако не знает, что заявку перевели в другое подразделение минуту назад.

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

Политику удобно покрывать матрицей тестов. Не пытайтесь проверить все сочетания вручную в интерфейсе:

Матрица должна закреплять как разрешения, так и отказы:

  • автор может изменить собственный черновик;
  • автор не может изменить собственную заявку на проверке;
  • контролер отдела A может согласовать заявку отдела A;
  • контролер отдела A не может согласовать заявку отдела B;
  • администратор справочников не может согласовать ни одну заявку.

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

Агент хорошо генерирует middleware, тестовые заготовки и одинаковые вызовы политики во всех обработчиках. Человек проверяет полноту матрицы и ищет обходные пути: массовую операцию, экспорт, фоновую задачу, старую версию API, прямой вызов внутреннего метода. Авторизация, выполненная «почти везде», равна отсутствующей авторизации в одном самом неудобном месте.

Транзакция защищает инвариант, а не последовательность строк кода

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

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

Документация PostgreSQL определяет уровень SERIALIZABLE через эффект, эквивалентный некоторому последовательному выполнению транзакций. Это сильная гарантия, но она не превращает любой код в готовое решение. PostgreSQL может остановить одну транзакцию с SQLSTATE 40001, а приложение обязано повторить всю транзакцию, включая чтения и решения, которые определили последующие запросы. Повтор только последнего UPDATE использует устаревший контекст.

Для локального инварианта часто достаточно блокировки конкретной строки и проверки внутри одной транзакции:

BEGIN;

SELECT available_kopecks
FROM budgets
WHERE id = $1
FOR UPDATE;

UPDATE budgets
SET available_kopecks = available_kopecks - $2
WHERE id = $1
  AND available_kopecks >= $2;

/* приложение проверяет, что UPDATE изменил одну строку */
INSERT INTO ledger_entries(request_id, budget_id, amount_kopecks)
VALUES ($3, $1, $2);

COMMIT;

Этот фрагмент не надо копировать бездумно. Уникальное ограничение на ledger_entries.request_id должно не допустить вторую проводку при повторе команды. Если UPDATE не изменил строку, сервис возвращает ошибку недостаточного лимита и не выполняет вставку. Если в операции участвуют несколько бюджетов, строки нужно блокировать в стабильном порядке, иначе параллельные транзакции могут образовать взаимную блокировку.

Документация PostgreSQL о явных блокировках прямо указывает, что SELECT ... FOR UPDATE получает блокировки выбранных строк. Это инструмент для защиты конкретной записи, а не общий ответ на конкуренцию. Проверка суммарного ограничения по множеству строк может потребовать SERIALIZABLE, отдельной агрегирующей строки или другой модели данных. Выбор зависит от того, где физически можно закрепить инвариант.

Нельзя удерживать транзакцию базы во время ожидания ответа человека или внешнего сервиса. Запишите намерение и состояние, завершите транзакцию, затем выполните внешний вызов. Его результат обработайте новой командой. Для связи записи в базе и публикации события подходит таблица outbox: бизнес-изменение и запись события фиксируются вместе, а отдельный процесс доставляет событие с повторами.

Повторы запросов надо проектировать до первого сбоя

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

Паттерн Idempotent Receiver в каталоге Patterns of Distributed Systems предлагает уникально идентифицировать запрос и при повторе вернуть сохраненный ответ вместо повторного выполнения. Это точная основа для платежей, заявок и выдачи лимита. Я бы уточнил практическую деталь: идентификатор должен обозначать бизнес-команду, а сервер должен связать его с пользователем, типом операции и хешем существенных параметров.

Иначе клиент случайно отправит тот же request_id с другой суммой, а сервер молча вернет старый успех. Корректное поведение такое:

  1. Первый запрос резервирует идентификатор и выполняет команду в транзакции.
  2. Точный повтор получает прежний статус и тело ответа.
  3. Повтор с измененными существенными полями получает 409 idempotency_conflict.
  4. Параллельный повтор ждет результат или получает понятный временный статус.

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

Агент способен сгенерировать таблицу, индекс, обработчик и тест параллельных запросов. Ручная проверка нужна в трех местах: состав идентичности команды, атомарность записи результата и поведение незавершенного первого запроса. Особенно опасен код, который сначала проверяет отсутствие ключа, затем выполняет действие и лишь потом сохраняет ключ. Два процесса успеют пройти проверку одновременно. Уникальное ограничение и бизнес-изменение должны работать как единая схема защиты.

Агенту отдавайте задачи с дешевым доказательством результата

Сохраните точку перед правками
Снимки и откат позволяют вернуться к сохраненному состоянию после изменения бизнес-правил.

Полезность агента определяется не сложностью написания кода, а стоимостью проверки. Создание React-формы по готовому контракту, миграции с очевидными ограничениями, клиента API, табличных тестов и административного списка обычно легко проверить. Разработчик видит структуру, запускает тесты, сравнивает схему и быстро замечает отклонение.

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

Перед задачей агенту подготовьте пакет контекста:

  • словарь терминов с одним смыслом для каждого слова;
  • инварианты и запрещенные состояния;
  • контракты команд и ошибок;
  • таблицы решений и примеры на границах;
  • перечень частей, которые агент не должен менять.

Последний пункт нужен обязательно. Если агенту поручили добавить возврат, он не должен заодно «упростить» политику доступа или заменить тип денежного поля. Ограничьте область изменения файлами или модулями, затем отдельно просмотрите diff. Большой сгенерированный diff нельзя честно проверить беглым взглядом. Делите работу так, чтобы один набор изменений отвечал на один вопрос.

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

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

Один сквозной сбой показывает слабые места границы

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

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

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

Исправление начинается не с добавления еще одного условного оператора. Нужно определить точку, после которой обязательство считается созданным. Если источник истины находится во внутреннем реестре, одна транзакция блокирует актуальную заявку, повторно проверяет полномочия контролера, сверяет версию, резервирует ключ идемпотентности, создает проводку, уменьшает лимит и переводит процесс в состояние payment_pending. Запись в outbox фиксируется в той же транзакции. Только после COMMIT отдельный исполнитель обращается к платежному сервису.

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

Команда отмены со старой вкладки передает expected_version. Сервер загружает текущую строку, видит несовпадение версии и возвращает конфликт. Он не пытается угадать намерение бухгалтера и не применяет отмену к уже проведенной операции. Интерфейс показывает свежее состояние и предлагает допустимое действие, если оно существует. Так оптимистическая проверка версии защищает смысл процесса, а не только данные от технической гонки.

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

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

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

Обязательный контроль должен проверять последствия

Отделите интерфейс от правил
Поручите платформе каркас приложения, а критические инварианты задайте в плане явно.

Проверка сгенерированного кода начинается не со стиля. Сначала рецензент ищет путь, который нарушает деньги, права или состояние: отрицательную сумму, повтор команды, смену роли между чтением и записью, параллельное согласование, сбой после внешнего вызова. Форматирование и названия важны, но они не компенсируют пропущенный инвариант.

У каждого опасного изменения должен быть названный владелец проверки. Фраза «команда посмотрела» не оставляет ответственности. Для денежного расчета смысл примеров подтверждает владелец процесса, реализацию проверяет разработчик, а миграцию и запросы оценивает человек, который понимает поведение PostgreSQL под конкуренцией. В маленькой команде это могут быть два человека, но роли проверки все равно стоит назвать.

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

Логи не должны заменять журнал бизнес-решений. Технический лог помогает найти ошибку процесса, но его формат и срок хранения обычно меняются. Журнал решения хранит стабильные поля: кто действовал, над каким объектом, из какого состояния, в какое, по какой причине, с какой версией правил и каким идентификатором команды. Секреты и лишние персональные данные туда не кладут.

Контроль после выпуска тоже нужен, однако не в форме абстрактного наблюдения «ошибок стало больше». Считайте отклоненные переходы, конфликты версий, повторы идемпотентных команд, ошибки 40001, взаимные блокировки и ручные исправления состояния. Резкий рост одного типа события часто показывает неверный контракт или новую гонку раньше, чем пользователи составят точное описание.

Если тест нельзя написать, это не всегда повод отказаться от функции. Но тогда нужен другой способ проверки: модель состояний, парное рецензирование, журнал воспроизведения, ограниченный запуск или сверка со старой системой. Заявление агента о корректности не считается доказательством, как не считается доказательством уверенность автора ручного кода.

Границу фиксируйте по риску изменения

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

Показательный пример среднего риска, внутреннее согласование закупки. Агент может собрать React-интерфейс, Go-обработчики по контракту, миграции PostgreSQL, уведомления и журнал событий. Разработчик вручную задает карту состояний, проверку полномочий на объекте, версионирование и поведение повтора. В TakProsto.AI такой процесс разумно начинать в режиме планирования, затем генерировать отдельными частями и сохранять снимки перед изменением правил.

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

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

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

FAQ

Можно ли доверить AI-агенту расчеты денег?

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

Когда обычная разработка лучше AI-агентов?

Ручная разработка предпочтительна, когда ошибка меняет обязательства, права доступа или необратимое состояние, а корректность трудно проверить автоматически. Агент при этом все еще полезен для тестовых данных, каркаса и повторяемого кода.

Нужен ли разработчик, если агент генерирует работающий код?

Да, потому что компиляция и счастливый сценарий не доказывают выполнение бизнес-инвариантов. Разработчик проверяет конкуренцию, повторы, права, ошибки и восстановление после частичного сбоя.

Как описать сложную бизнес-логику для AI?

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

Может ли AI спроектировать процесс согласования?

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

Как предотвратить двойное выполнение операции?

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

Всегда ли нужен уровень SERIALIZABLE в PostgreSQL?

Нет, для инварианта на одной строке часто хватает условного обновления или SELECT ... FOR UPDATE внутри транзакции. SERIALIZABLE нужен, когда более простая схема не защищает правило, и приложение обязано повторять всю отклоненную транзакцию.

Чем роль отличается от разрешения на действие?

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

Как проверять код, который написал AI-агент?

Сначала проверяйте последствия: деньги, права, переходы состояния, конкурирующие запросы и повторы после сбоя. Затем смотрите архитектуру, тесты, миграции и только после этого стиль.

Стоит ли переписывать весь старый процесс с помощью AI сразу?

Нет, большой объем изменений скрывает случайные решения и делает рецензирование формальным. Разделите процесс по командам и инвариантам, сначала зафиксируйте правила, затем генерируйте небольшие проверяемые части.

Похожие статьи