From 61d08c2eedc0083ce9b301432bcdfb6a24c5cabc Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Thu, 3 Sep 2026 22:57:09 +0300 Subject: [PATCH] =?UTF-8?q?EDITORIAL-290:=20=D0=BE=D1=82=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=82=D1=8C=D1=8E=20=D0=BF=D0=BE=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=B4=D0=B0=D1=80=D1=82=D1=83=2010/10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- editorial/agent-rewrites/290.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editorial/agent-rewrites/290.json b/editorial/agent-rewrites/290.json index 260f02a..56b5541 100644 --- a/editorial/agent-rewrites/290.json +++ b/editorial/agent-rewrites/290.json @@ -3,5 +3,5 @@ "slug": "editorial-2019-12-mechanism-code-ownership", "title": "Кому принадлежит изменение: Git, CODEOWNERS и границы ответственности", "excerpt": "Последний автор строки не всегда знает смысл контракта, а CODEOWNERS не заменяет review. Разбираем, как разделить историю, маршрут проверки, решение и контроль после merge.", - "contentHtml": "

Ошибка на стыке модулей часто начинается с простой задержки. В задаче уже указан файл, в git blame видно имя, но исправление ждёт ответа. Разработчик считает владельцем последнего автора строки. Автор помнит рефакторинг и не знает, кто принимает решение по контракту. Pull request получает формальный approval, а после merge никто не проверяет спорный ответ. Цена ошибки — не только потерянные часы. Неизвестный статус может попасть в ветку успеха, изменение контракта — пройти без нужного специалиста, а тот же дефект вернуться в соседнем файле.

\n

Тезис простой: «владелец кода» — не одна роль. История Git отвечает на вопрос «кто менял». CODEOWNERS отвечает на вопрос «кого запросить по пути». Review отвечает на вопрос «какое решение принято для diff». Отдельное назначение нужно для проверки после merge. Если смешать эти ответы, команда получает имя вместо ответственности. Если разделить их, каждый риск получает проверяемый маршрут.

\n

Симптом начинается не с имени

\n

Формулировка «у модуля нет владельца» слишком общая. Начните с наблюдаемого поведения: gateway вернул unknown, checkout показал успешную оплату; retry записал старый результат поверх нового; изменение файла прошло без человека, который знает допустимые статусы. В такой записи есть вход, неверный результат и цена. Она помогает найти границу решения, а не назначить виноватого.

\n

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

\n
function nextState(response) {\n  if (response.status === 'paid') return 'success';\n  if (response.status === 'declined') return 'failure';\n  return 'unknown';\n}\n\n// Искусственный пример: unknown не считается success.\nconst state = nextState({ status: 'pending_review' });\nconsole.log(state); // unknown
\n

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

\n

Четыре следа вместо одного «owner»

\n

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

\n

CODEOWNERS работает по другому принципу. Это правило хостинга, которое сопоставляет путь с пользователями или командами и может автоматически запросить их review. В GitHub файл ищут в `.github/`, корне или `docs/`; используется первый найденный вариант. Для запроса review важен CODEOWNERS из base branch pull request. Правило в feature branch само по себе не гарантирует нужный маршрут.

\n

Review тоже имеет узкую границу. Comment оставляет замечание, Approve сообщает, что изменение готово к merge, Request changes блокирует принятие до исправления. Ни один статус не записывает, кто проверит поведение после выпуска. Поэтому follow-up надо назначить отдельно: это может быть проверка лога, сценария или отдельная техническая задача.

\n
Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
Задача уходит к автору строкиИсторию приняли за текущего владельца решенияgit blame -L, затем diff и связанный контрактСохранить автора как факт и отдельно назначить decision owner
Нужный reviewer не получил запросНе совпал путь, выбран не тот base branch или owner не имеет доступаПроверить расположение файла, регистр пути, порядок правил и праваИсправить точное правило CODEOWNERS и открыть новый review-маршрут
Есть approval, но спорный статус не разобранReview проверил форму diff, а не риск контрактаНайти в обсуждении ответ на вопрос о unknownЗапросить конкретное подтверждение или отправить Request changes
После merge никто не знает результатFollow-up не был частью записи измененияПроверить назначенного исполнителя и ожидаемый сигналСоздать отдельное действие; не закрывать задачу по одному факту merge
\n

Как работает маршрут CODEOWNERS

\n

В примере общий маршрут задаёт запасного reviewer, а более узкие пути уточняют область. Правила вымышлены и приведены только для объяснения синтаксиса. В GitHub последнее совпавшее правило имеет больший приоритет. Несколько owners для одного пути записывают в одной строке. Отрицание через ! и диапазоны в квадратных скобках нельзя переносить из `.gitignore` без проверки: в CODEOWNERS они не работают как в gitignore.

\n
# Искусственный пример, не конфигурация рабочего репозитория.\n*                                      @example/platform\n/web/checkout/                       @example/checkout\n/web/checkout/gateway/               @example/payments\n/docs/checkout-contract.md           @example/payments @example/checkout\n/.github/CODEOWNERS                   @example/repository-admins
\n

Для изменения /web/checkout/gateway/status.js сначала проверяют, какое правило совпало и кто имеет право получить review. Для изменения документа контракта нужны оба указанных направления, если именно они обладают знаниями о семантике и потребителе. Это не означает, что оба approval обязательны: политика ветки может требовать approval любого code owner. Если риск требует двух разных решений, это надо записать в правилах change и запросить обоих людей явно.

\n
\"Схема
История помогает найти контекст, CODEOWNERS направляет запрос, review проверяет diff, а результат после merge требует отдельного действия.
\n

Пример записи решения

\n

Короткая запись в issue или pull request должна связывать риск с ролью. Для искусственного сценария достаточно такого контракта:

\n
symptom: unknown status enters success flow\ndecision_owner: payments-contract\ncode_path_owner: checkout\nreview_questions:\n  - Is unknown a separate state?\n  - Can retry create a second transition?\nfollow_up_owner: checkout\nready_when:\n  - unknown is visible in the test\n  - success transition rejects unknown\n  - review answers both questions\n  - follow-up has a recorded result
\n

Эта запись не создаёт новую должность и не обещает реальный production-сигнал. Она задаёт границу проверки. Если проект не использует CODEOWNERS, те же поля можно хранить в задаче и запросить reviewer вручную. Механизм маршрутизации изменится, но вопросы останутся теми же.

\n

Порядок действий

\n
  1. Запишите вход, наблюдаемый симптом, неверный результат и цену ошибки. Не начинайте с поиска человека.
  2. Ограничьте историю нужным диапазоном строк и путём: используйте git blame -L, затем git log --follow -- path и связанные документы. Не переносите имя из истории в поле decision owner.
  3. Назначьте владельца решения по контракту. Он должен ответить, что означает неизвестный статус и какие переходы допустимы.
  4. Определите владельца пути кода и проверьте CODEOWNERS из base branch. Убедитесь, что путь написан с правильным регистром, правило расположено в поддерживаемом файле, а команда имеет нужный доступ.
  5. Сформулируйте вопросы review по риску: семантика контракта, побочные переходы, отрицательный тест. Общий текст «проверьте код» не закрывает эти вопросы.
  6. До merge назначьте follow-up и ожидаемый сигнал. Это может быть результат тестового сценария или проверка доступного контура; не называйте непроверенное production-результатом.
  7. После merge запишите результат. Если сигнал не подтверждён, оставьте задачу открытой или создайте связанную. Статус «merged» означает состояние кода, а не доказательство исправления симптома.
\n

Отрицательный путь и ограничения

\n

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

\n

Есть и ограничения механизма. CODEOWNERS знает путь, но не знает, кто владеет внешним API или продуктовым смыслом. Git знает историю, но не знает будущую ответственность. Review фиксирует решение по конкретному diff и может устареть после существенной правки. Автоматический запрос не равен прочитанному review. Эти границы нельзя закрыть ещё одним wildcard-правилом.

\n

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

\n

Проверяемые источники

" + "contentHtml": "

Ошибка на стыке модулей часто начинается с простой задержки. В задаче уже указан файл, в git blame видно имя, но исправление ждёт ответа. Разработчик считает владельцем последнего автора строки. Автор помнит рефакторинг и не знает, кто принимает решение по контракту. Pull request получает формальный approval, а после merge никто не проверяет спорный ответ. Цена ошибки — не только потерянные часы. Неизвестный статус может попасть в успешный сценарий, изменение контракта — пройти без нужного специалиста, а тот же дефект вернуться в соседнем файле.

\n

Тезис простой: «владелец кода» — не одна роль. История Git отвечает на вопрос «кто менял». CODEOWNERS отвечает на вопрос «кого запросить по пути». Review отвечает на вопрос «какое решение принято для diff». Отдельное назначение нужно для проверки после merge. Если смешать эти ответы, команда получает имя вместо ответственности. Если разделить их, каждый риск получает проверяемый маршрут.

\n

Симптом начинается не с имени

\n

Формулировка «у модуля нет владельца» слишком общая. Начните с наблюдаемого поведения: gateway вернул unknown, checkout показал успешную оплату; retry записал старый результат поверх нового; изменение файла прошло без человека, который знает допустимые статусы. В такой записи есть вход, неверный результат и цена. Она помогает найти границу решения, а не назначить виноватого.

\n

Пример ниже искусственный. Он не описывает production-инцидент, настоящих людей или измеренный эффект. В нём обработчик получает ответ платёжного gateway. Статусы paid и declined известны, остальные значения функция возвращает как отдельное unknown. Следующий переход не должен трактовать его как success, а исходный ответ остаётся доступным для диагностики.

\n
function nextState(response) {\n  if (response.status === 'paid') return 'success';\n  if (response.status === 'declined') return 'failure';\n  return 'unknown';\n}\n\n// Искусственный пример: unknown не считается success.\nconst state = nextState({ status: 'pending_review' });\nconsole.log(state); // unknown
\n

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

\n

Четыре следа вместо одного «owner»

\n

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

\n

Ниже CODEOWNERS означает именно механизм GitHub. Он сопоставляет путь с пользователями или командами и автоматически запрашивает их review для обычного открытого pull request; draft pull request не вызывает такой запрос, пока его не переведут в режим ready for review. GitHub ищет файл в .github/, корне или docs/; если файлов несколько, использует первый по порядку поиска. Для запроса review GitHub использует CODEOWNERS из base branch — ветки, в которую попадёт pull request. Правило в feature branch само по себе не гарантирует нужный маршрут.

\n

Review тоже имеет узкую границу. Comment оставляет замечание, Approve сообщает, что изменение готово к merge, Request changes блокирует принятие до исправления. Ни один статус не записывает, кто проверит поведение после выпуска. Поэтому follow-up надо назначить отдельно: это может быть проверка лога, сценария или отдельная техническая задача.

\n
Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
Задача уходит к автору строкиИсторию приняли за текущего владельца решенияgit blame -L, затем diff и связанный контрактСохранить автора как факт и отдельно назначить decision owner
Нужный reviewer не получил запросНе совпал путь, выбран не тот base branch или owner не имеет доступаПроверить расположение файла, регистр пути, порядок правил и праваИсправить точное правило CODEOWNERS и открыть новый review-маршрут
Есть approval, но спорный статус не разобранReview проверил форму diff, а не риск контрактаНайти в обсуждении ответ на вопрос о unknownЗапросить конкретное подтверждение или отправить Request changes
После merge никто не знает результатFollow-up не был частью записи измененияПроверить назначенного исполнителя и ожидаемый сигналСоздать отдельное действие; не закрывать задачу по одному факту merge
\n

Как работает маршрут CODEOWNERS

\n

В примере общий маршрут задаёт запасного reviewer, а более узкие пути уточняют область. Правила вымышлены и приведены только для объяснения синтаксиса. В GitHub последнее совпавшее правило имеет больший приоритет. Несколько owners для одного пути записывают в одной строке. Отрицание через ! и диапазоны в квадратных скобках нельзя переносить из `.gitignore` без проверки: в CODEOWNERS они не работают как в gitignore.

\n
# Искусственный пример, не конфигурация рабочего репозитория.\n*                                      @example/platform\n/web/checkout/                       @example/checkout\n/web/checkout/gateway/               @example/payments\n/docs/checkout-contract.md           @example/payments @example/checkout\n/.github/CODEOWNERS                   @example/repository-admins
\n

Для изменения /web/checkout/gateway/status.js сначала проверяют, какое правило совпало и кто имеет право получить review. Для документа контракта можно явно запросить обе команды, если именно они обладают знаниями о семантике и потребителе. Но сама строка с двумя owners не делает approval обоих обязательными: при требовании review from Code Owners GitHub считает достаточным approval любого из них. Если риск требует двух разных решений, это надо записать в описании изменения и запросить обе команды явно.

\n
\"Схема
История помогает найти контекст, CODEOWNERS направляет запрос, review проверяет diff, а результат после merge требует отдельного действия.
\n

Пример записи решения

\n

Короткая запись в issue или pull request должна связывать риск с ролью. Для искусственного сценария достаточно такого контракта:

\n
symptom: unknown status enters success flow\ndecision_owner: payments-contract\ncode_path_owner: checkout\nreview_questions:\n  - Is unknown a separate state?\n  - Can retry create a second transition?\nfollow_up_owner: checkout\nready_when:\n  - unknown is visible in the test\n  - success transition rejects unknown\n  - review answers both questions\n  - follow-up has a recorded result
\n

Эта запись не создаёт новую должность и не обещает реальный production-сигнал. Она задаёт границу проверки. Если проект не использует CODEOWNERS, те же поля можно хранить в задаче и запросить reviewer вручную. Механизм маршрутизации изменится, но вопросы останутся теми же.

\n

Порядок действий

\n
  1. Запишите вход, наблюдаемый симптом, неверный результат и цену ошибки. Не начинайте с поиска человека.
  2. Ограничьте историю нужным диапазоном строк и путём: используйте git blame -L, затем git log --follow -- path и связанные документы. Не переносите имя из истории в поле decision owner.
  3. Назначьте владельца решения по контракту. Он должен ответить, что означает неизвестный статус и какие переходы допустимы.
  4. Если вы используете GitHub, проверьте CODEOWNERS из base branch. Убедитесь, что путь написан с правильным регистром, правило расположено в поддерживаемом файле, а команда имеет нужный доступ.
  5. Сформулируйте вопросы review по риску: семантика контракта, побочные переходы, отрицательный тест. Общий текст «проверьте код» не закрывает эти вопросы.
  6. До merge назначьте follow-up и ожидаемый сигнал. Это может быть результат тестового сценария или проверка доступного контура; не называйте непроверенное production-результатом.
  7. После merge запишите результат. Если сигнал не подтверждён, оставьте задачу открытой или создайте связанную. Статус «merged» означает состояние кода, а не доказательство исправления симптома.
\n

Отрицательный путь и ограничения

\n

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

\n

Есть и ограничения механизма. CODEOWNERS знает путь, но не знает, кто владеет внешним API или продуктовым смыслом. Git знает историю, но не знает будущую ответственность. Review фиксирует решение по конкретному diff и может устареть после существенной правки. Автоматический запрос не равен прочитанному review. Эти границы нельзя закрыть ещё одним wildcard-правилом.

\n

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

\n

Проверяемые источники

" }