Действующий контракт doc_translate, doc_verify и doc_continue. Цель — простой конвейер перевода, проверки и исправления. Другие документы не добавляют требований. Этот документ описывает ожидаемое поведение, а не подтверждает его реализацию.
Для открытого PR фиксируем SHA его исходной ветки (head), читаем из него исходные файлы и зависимости. Наличие этих файлов в main не требуется. Для слитого PR используем зафиксированный снимок текущей базовой ветки. Весь запуск работает с выбранным неизменяемым снимком.
doc_translate до создания клиента модели определяет изменения по парам RU/EN: только RU → перевод на EN; только EN → перевод на RU. Если исходный PR уже содержит соответствующие изменения обеих версий страницы, пара пропускается без перевода и критика. Старый перевод в базе сам по себе не означает пропуск. В смешанном PR обрабатываются оставшиеся одноязычные пары. Если работы нет — «Перевод не требуется», без модели, изменений файлов и нового PR. Ошибка чтения списка файлов не означает отсутствие работы.
Изменения зеркальны: добавление → добавление перевода, изменение → обновление перевода, удаление → удаление перевода, переименование → переименование перевода. Чистые удаления и переименования выполняются без модели, но публикуются; изменения содержимого при переименовании переводятся. Если обе языковые версии уже удалены или переименованы самим PR, повторять действие не нужно. Удалённый файл читать для перевода не требуется. Если существующий выбранный исходник невозможно прочитать из снимка — RED до модели с путём и причиной.
До перевода составляем план. Если статья A ссылается внутренней Markdown-ссылкой на статью B того же языка, а перевода B на целевой язык нет, добавляем B в план: ссылка из перевода A должна вести на существующую статью. Для добавленных статей повторяем проверку. Каждый файл учитывается один раз, циклы не вызывают повторов. Пропущенные двуязычные пары не возвращаются в план через зависимости. Это правило действует для RU→EN и EN→RU.
Настройки берутся из Actions variables репозитория ydb-platform/ydb:
| Переменная | Назначение |
|---|---|
YDBDOC_MAX_DEPENDENCY_FILES_PER_ARTICLE |
Максимум дополнительных зависимых статей. Несмотря на имя, лимит общий для всего запуска и обоих направлений; исходные файлы PR не входят. Согласованное значение — 20 |
YDBDOC_MAX_SOURCE_CHARACTERS |
Общий максимум необработанных символов исходных Markdown-файлов плана после раннего отбора и добавления зависимостей, до извлечения сегментов. Согласованное значение — 250000 |
YDBDOC_ALLOWED_ACTORS |
Разрешённые GitHub-логины для всех трёх режимов, через запятую; окружающие пробелы удаляются |
YDBDOC_DAILY_BUDGET_RUB |
Дневной бюджет всех трёх режимов в рублях (§7) |
Значения лимитов читаются из переменных, не фиксируются в коде. Превышение лимита зависимостей или символов даёт RED до создания клиента модели, без изменений файлов и переводного PR. План нельзя молча обрезать. Сообщение содержит фактический объём, лимит, имя переменной и понятное действие:
- Зависимости: «Перевод не запущен: требуется N зависимых статей, лимит — L (
YDBDOC_MAX_DEPENDENCY_FILES_PER_ARTICLE). Сначала переведите часть зависимых статей отдельным PR или увеличьте переменную в настройках Actions репозитория ydb-platform/ydb». Далее — обнаруженные зависимые пути, включая превысившие лимит. - Символы: «Перевод не запущен: объём исходных текстов — N символов, лимит — L (
YDBDOC_MAX_SOURCE_CHARACTERS). Разделите изменения на несколько PR или увеличьте переменную в настройках Actions репозитория ydb-platform/ydb».
Доступ проверяется до изменений и модели. Пользователь вне YDBDOC_ALLOWED_ACTORS получает понятный отказ. На момент согласования список: sintjuri,SixOnMyface, nataliaboldyreva,ayakivosklznak; источником настройки остаётся переменная.
Каждый файл переводится независимо. До обращения к модели строим простой упорядоченный план файла, а не общий граф зависимостей: узлы идут в исходном порядке и имеют диапазон строк, тип и признак «переводить». План нужен только для того, чтобы после ответа безопасно вернуть технические части исходника. При первичном переводе старый текст целевого языка не используется как шаблон или источник результата; это не ограничивает проверку и исправление существующего перевода в doc_verify и doc_continue.
Для Markdown поддерживаем небольшой набор типов: заголовок, абзац, пункт списка, текст ячейки таблицы, label ссылки и alt-текст изображения — переводимые; fenced code, include-директива, TOC/YAML-конфиг, Mermaid, URL, путь файла, идентификатор, шаблон YFM и техническая разметка — неизменяемые. Неизвестный тип оставляем исходным и отмечаем в отчёте, но сам по себе он не делает файл RED. Ссылке разрешено локально менять URL только при установленном соответствии и проверенной цели по §5; исходный и новый адрес с основанием замены сохраняются. Остальные адреса неизменны.
В fenced code-блоке с известным языком простыми регулярными правилами выделяем только комментарии: # для Python, Bash и YAML; // и /* ... */ для C++, Java и JavaScript. Каждый уверенно выделенный комментарий является переводимым дочерним узлом, остальной код — неизменяемый. Если язык не поддержан, комментарий не удаётся уверенно выделить или структура code-блока изменилась, сохраняем весь исходный code-блок. Строки, похожие на конфигурацию или текст внутри кода, не переводим. Mermaid целиком остаётся исходным до отдельного согласованного расширения.
План обязан без перевода собрать исходный файл байт-в-байт. Он хранит исходные байты всех неизменяемых узлов и границы переводимых полей. Модель получает исходный Markdown/YFM чанка целиком, включая настоящий код, конфиги и ссылки как контекст, и возвращает цельный переведённый Markdown/YFM без внешней JSON-обёртки, служебных маркеров или пояснений. Она не является источником технических частей результата.
Если файл не помещается в один запрос, делим его только между целыми разделами или блоками. Не делим внутри предложения, таблицы, fenced code-блока, include или другого технического узла. Для каждого чанка передаём заголовок родительского раздела как read-only context, если он не входит в чанк. Невозможность безопасно разделить слишком большой файл означает RED для этого файла с путём и причиной, без угадывания границ.
После ответа строим такой же план кандидата и однозначно сопоставляем его с исходным по типу узла, родителю и порядку. Не применяем поиск по смысловому сходству. Из кандидата берём только значения переводимых узлов; все неизменяемые узлы в окончательном результате подставляем из исходника байт-в-байт. Если переводимое поле невозможно безопасно экранировать, соответствующий узел остаётся исходным, причина записывается в отчёт, а файл получает RED для ручной правки. Если план кандидата нельзя прочитать или однозначно сопоставить с исходным, в том числе из-за добавленного, удалённого или сломанного узла, весь затронутый чанк остаётся исходным, а его автоматическая обработка в текущем запуске останавливается. Читаемый результат с другими пригодно собранными изменениями публикуется только как draft с RED по §6 и не считается завершённым. После сборки снова разбираем итог и проверяем сохранность неизменяемых узлов на исходных структурных местах. Критик отдельно проверяет смысл, полноту и связь объяснений с примерами: равенство структуры этого не гарантирует.
Для каждого обращения после основного провайдера разрешён максимум один вызов одной настроенной альтернативы — только при timeout, сетевой ошибке, HTTP 5xx или подтверждённой недоступности провайдера/модели. Других технических повторов нет. Повреждённый ответ, внешняя code fence-обёртка ответа, усечение или несопоставимая структура не запускают fallback. Полные запросы и ответы остаются в YDB для разбора. Ошибка ответа критика не считается успешной проверкой.
Размер запроса оценивается с учётом токенизации выбранной модели, промпта, полного текста чанка, глоссария и резерва для ответа. Число UTF-8 байт не принимается за число токенов без обоснования для модели. Сначала пытаемся передать файл одним пакетом; число пакетов определяется вместимостью, а не фиксированным числом частей.
Каждый файл, чанк и узел плана имеют устойчивые идентификаторы, границы исходного текста, собственный результат и статус. Эти идентификаторы не являются частью ответа модели. Соответствие сохраняется при сборке, проверке, исправлении и продолжении. Отсутствующий ответ остаётся явно отсутствующей частью: последующие части не сдвигают соответствие исходника и перевода. Сопоставление нельзя заново угадывать по сходству склеенного неполного текста.
Промпт перевода объясняет: это техническая документация YDB; нужно перевести документ целиком, сохранить смысл и полноту, не добавлять утверждений, сохранить структуру Markdown/YFM и вернуть только переведённый Markdown/YFM. Код, конфиги, ссылки и технические конструкции даны как контекст и не должны намеренно изменяться. При заданном глоссарии передаются необходимые термины и правила из него; одна ссылка не заменяет содержимое, если модель не умеет её читать. Источник глоссария должен быть явно установлен, выдумывать его нельзя.
Каждое обращение к модели, включая перевод, критика, исправление и технический fallback, отправляется с отключёнными рассуждениями. Для OpenAI-совместимого API в корневой JSON запроса обязательно добавляется \"thinking\": {\"type\": \"disabled\"}. Модель, не поддерживающая этот обязательный параметр или не возвращающая пригодный ответ при отключённых рассуждениях, не используется без отдельно согласованного изменения требований; скрытой замены модели нет. Диагностика различает пустой ответ, исчерпание выходного лимита и некорректный формат. Ответ с finish_reason=length или эквивалентным признаком усечения, а также ответ без подтверждённого штатного завершения генерации, непригоден целиком, даже если содержит синтаксически правильный JSON. Для потокового ответа требуется подтверждённое завершение потока и генерации. Рассуждения не принимаются за перевод. Это не расширяет условия fallback из §3.
doc_translateвыполняет первичный перевод и общий цикл проверки (§5). Для открытого исходного PR новый переводной PR направляется в его исходную ветку; для слитого — в базовую ветку (main). Так новые статьи и зависимости исходного PR доступны при проверке перевода.doc_verifyпроверяет существующие пары в PR строго в направлении RU→EN. Первичного перевода нет; сразу запускается тот же цикл критика и исправлений, что послеdoc_translate. Исправляется EN, RU служит оригиналом. Изменения и вердикт публикуются в проверяемом PR, отдельный переводной PR не создаётся. Пропуск двуязычных пар из §1 относится кdoc_translate, а не кdoc_verify.doc_continueпродолжает предыдущую работу с инструкцией техписа из комментария/ydbdoc continue <текст>и запускается меткойdoc_continue. Использует сохранённые переводы и замечания, исправляет только проблемные или явно указанные техписом файлы. Успешные неизменённые файлы повторно не переводятся. Исправления и новый вердикт идут в существующий PR; ветка и PR не удаляются. Для затронутых файлов действует общий цикл; ссылки и сборка проверяются для итогового результата.
Для простого продолжения сохраняем SHA исходного и проверяемого PR по завершении запуска. Если перед doc_continue хотя бы один SHA изменился, отказываем до модели: «После предыдущего запуска появились новые коммиты. Запустите doc_verify для проверки текущего перевода или doc_translate для нового перевода». Даже посторонний коммит блокирует продолжение; слияние старого состояния с новыми правками не требуется.
Максимум три продолжения на исходный PR. Полные запросы и ответы моделей, результаты, замечания и SHA сохраняются в YDB на 14 дней. Модели передаётся только контекст, необходимый для текущего исправления. Если контекст отсутствует или истёк, продолжение не запускается; сообщение объясняет срок хранения и предлагает новый запуск. Недоступность YDB сообщается как ошибка хранилища, а не как истечение срока.
Общий алгоритм: проверка → исправление → проверка → исправление → финальная проверка. Максимум три проверки и два исправления, без дополнительных циклов для отдельных эвристик. Если ошибок нет, завершаемся раньше. После финальной проверки исправлений больше нет; оставшиеся ошибки или незавершённая проверка дают RED.
Критик получает исходный файл и собранный перевод целиком. Проверяет сохранение смысла, полноту переводимых узлов, отсутствие добавленных утверждений, связь объяснений с примерами и качество языка. Возвращает чистый JSON без code fence: «правильно» либо проблемы с файлом, строками перевода, цитатой, сутью и ожидаемым исправлением. Критик сам текст не меняет; отдельный шаг исправления получает найденные проблемы. Длинные документы проверяются соответствующими частями исходника и перевода; проверка должна охватить весь текст.
Дополнительно проверяем:
- Остаточную кириллицу в EN, включая заголовки, таблицы и подписи. Кириллица в защищённом коде — предупреждение, не повод автоматически менять код и не самостоятельная причина RED.
- Полноту и порядок узлов плана, неизменность технических частей в собранном документе, безопасность экранирования и сохранность структуры по §3. Сбой технической сборки сохраняет исходный затронутый чанк и делает результат RED.
- Корректность Markdown/YFM и сборку документации.
- Существование внутренних ссылок, якорей и assets в итоговом результате.
- Соблюдение явно заданного глоссария, если он есть.
- Полноту структуры: парсером сравниваем число блоков по типам — заголовки, абзацы, пункты списков, таблицы и их строки, блоки кода и контейнеры YFM. Перенос строки внутри абзаца не создаёт новый блок. Расхождения передаём критику: он пытается найти пропущенный, лишний или ошибочно объединённый блок, указать раздел, строки и цитату исходника, место исправления в переводе. Если локализация не удалась, показываем само расхождение без выдуманных координат. Для абзацев разница количества сама по себе не доказывает потерю текста; подтверждённые пропуски и повреждения структуры требуют исправления. Равенство количества не заменяет проверку смысла.
Область автоматических проверок ограничена файлами плана перевода и явно добавленными для них зависимостями. Проверка не обходит всё дерево документации или репозиторий, если это прямо не предписано отдельным пунктом этого контракта. «Итоговый результат» означает опубликованный кандидат для этой области, а не все существующие файлы кандидата. Ошибка вне области перевода не считается ошибкой перевода и не получает координаты «места исправления». Сравнение с исходным снимком допускается только для определения, внесён ли дефект работой; оно не расширяет область проверки. Общая ошибка сборки, которая мешает проверить выбранные файлы, даёт RED как внешний блокер с причиной, но не превращает посторонние файлы в задачи перевода.
В английской статье ссылка на русскоязычную страницу требует соответствующей английской цели. Если адрес не найден — RED с конкретной ссылкой и просьбой указать замену. В русской статье английские ссылки допустимы. Выдумывать URL или заменять /ru/ на /en/ без проверки цели нельзя.
GREEN возможен только после завершённой проверки критиком без ошибок и успешных обязательных автоматических проверок. Проверяем готовый неизменяемый кандидат; вердикт относится к его SHA. Любые последующие изменения требуют новой проверки.
После первичного перевода берём из ответа только переводимые узлы плана, а технические узлы возвращаем из исходника, затем фиксируем собранный результат; критик проверяет именно этот кандидат. Непригодный первичный ответ оставляет затронутый чанк исходным и останавливает его автоматическую обработку в текущем запуске. Продолжение при существующем PR может повторить только явно указанный проблемный чанк с исходным текстом и предыдущим переводом.
При исправлении передаём проблемный чанк целиком, его исходный текст, предыдущий собранный перевод, относящиеся к нему замечания и необходимый глоссарий. Ответ исправления подчиняется тому же контракту цельного Markdown/YFM и правилам сборки, что и первичный перевод. Успешные незатронутые чанки повторно не переводятся. Изменения файла публикуются атомарно после сборки всех исправляемых чанков. При непригодном ответе исправления или ошибке сборки исправленная версия не записывается, предыдущая опубликованная версия сохраняется, обработка файла останавливается с RED. Пригодные результаты и статусы сохраняются для продолжения. Успешное исправление фиксируется в ветке до следующей проверки.
Для doc_verify без сохранённого плана разбираем RU и существующий EN, сохраняем границы и устойчивые соответствия исходных и целевых узлов. Соответствие должно быть однозначно установлено до автоматического исправления; равенства числа блоков или сходства текста недостаточно. Если требуется исправление, но соответствие неоднозначно, в том числе из-за объединённых или разделённых абзацев, проверка и диагностика продолжаются, а автоматическое исправление затронутого файла не выполняется: RED с причиной и просьбой исправить соответствие вручную либо запустить новый перевод. Технические узлы и структуру при таком исправлении сравниваем с проверяемым EN до исправления; полноту и смысл сравниваем с RU. Для продолжения сохраняем построенный план.
Критик проверяет существенные смысловые, технические и языковые ошибки; стилистические предпочтения и мелкие придирки сами по себе не блокируют слияние. Для длинных документов используются сохранённые соответствия частей. Ошибка подготовки запроса не считается выполненной проверкой или исправлением. Если исправление невозможно или кандидат остался прежним, цикл останавливается с объяснением RED; повторять тот же цикл и сборку на неизменённом SHA без нового основания нельзя. Максимум остаётся прежним: три проверки, два исправления.
Сбой сборки показывается как первичная ошибка с доступным местом и причиной. Невозможность проверить якоря из-за этого сбоя — одна зависимая неполная проверка, а не тысячи якобы сломанных ссылок. Подтверждённые ошибки ссылок отделяются от непроверенных; без сравнения с исходным состоянием нельзя утверждать, что все ошибки дерева внесены переводом. Неполнота обязательной проверки сохраняет RED, даже если диагностика сгруппирована.
Полностью собранные переводы и связанные assets сохраняются в ветке и переводном PR даже при смысловых замечаниях, ошибках ссылок или внешнем блокере сборки и RED: результат должен быть доступен для ручного исправления. Если отдельный чанк или узел нельзя безопасно сопоставить, он остаётся исходным, а отчёт перечисляет путь и причину; файл и переводной PR публикуются как RED, если в них есть иные пригодно собранные изменения. Не публикуем только нечитаемый кандидат, который нельзя собрать по плану вовсе. Это не удаляет ранее опубликованную версию файла. При RED переводной PR остаётся draft. При прерывании перечисляем незавершённые файлы. Если работы нет или запуск остановлен на предварительных проверках до модели, нового переводного PR нет; чистые удаления и переименования из §1 публикуются без модели. Если из-за непригодных результатов ни одного изменения для публикации нет, пустой PR не создаётся: публикуем RED в исходном PR, сохраняем доступный контекст и стоимость. Без существующего PR doc_continue недоступен; отчёт предлагает новый doc_translate.
В исходном PR: краткий понятный итог — что переведено, что не удалось, статус, ссылка на переводной PR и стоимость. Если запуск не состоялся — причина и необходимые действия.
В переводном PR (для doc_verify — в проверяемом): вердикт критика, SHA последнего проверенного коммита и стоимость. GREEN — перевод прошёл проверки и готов к слиянию; RED — мержить нельзя, нужны исправления. Для каждой конкретной проблемы в подробной диагностике указываем путь, строку или диапазон строк опубликованной версии, суть и что исправить руками. Для отсутствующего блока добавляем строки и цитату исходника, место вставки в переводе, если оно установлено. Если проблема не привязана к строке, например отсутствует файл, пишем это прямо.
В переводном PR отчёт публикуется ровно одним comment. Описание PR не создаёт и не обновляет отчёт. Этот единственный comment содержит текущий или итоговый статус, причины, необходимые действия, проверенный SHA и стоимость. Если проверки ещё не завершены, нельзя объявлять GREEN.
Оба отчёта краткие. Повторяющиеся и зависимые ошибки группируются; полный журнал и диагностические артефакты в репозиторий, PR или отдельное хранилище не публикуются. Полные запросы и ответы хранятся только в YDB по §8. Отчёт не копирует полный журнал в комментарий. До отправки проверяется допустимый размер тела GitHub; сокращение деталей не удаляет вердикт, причины, действия, SHA и стоимость.
Публикация понятного состояния и стоимости не должна ожидать записи раздутого контекста. Сбои хранилища и публикации отражаются отдельно; ошибка одного отчёта не отменяет попытку опубликовать другой и не запускает повторные модельные обращения или полную перезапись неизменного контекста. Ошибка сохранения контекста явно объясняет недоступность продолжения. Учёт всех платных попыток сохраняется независимо от успеха отчёта.
В обоих отчётах показываем стоимость текущего запуска в рублях: перевод, проверки критиком, исправления и общую сумму. Учитываются все платные обращения, включая fallback и неудачные попытки. Стоимости всех запусков трёх режимов, в том числе RED и завершившихся ошибкой, сохраняются в той же YDB и суммируются за календарный день по Москве.
Один раз перед первым обращением к модели сравниваем накопленные расходы за сегодня с YDBDOC_DAILY_BUDGET_RUB. Если расходы достигли или превысили лимит, модель не запускаем: «Запуск отложен: сегодня потрачено X ₽, дневной лимит — Y ₽ (YDBDOC_DAILY_BUDGET_RUB). Повторите запуск завтра или увеличьте лимит».
Если расходы на старте ниже лимита, выполняем весь запуск, включая проверки и исправления, даже если его стоимость превысит бюджет. Превышение блокирует последующие запуски. Резервирование бюджета не требуется; параллельные запуски могут одновременно пройти проверку и суммарно превысить лимит.
Используем одну базу для учёта запусков и стоимости (runs) и контекста (run_objects):
YDBDOC_YDB_ENDPOINT:grpcs://ydb.serverless.yandexcloud.net:2135.YDBDOC_YDB_DATABASE:/ru-central1/b1g7gqj2vnq67gjseuva/etns0641qf73btm7j21k.- Доступ сервисного аккаунта — существующий secret
YDB_SA_KEY. - Контекст в
run_objectsхранится 14 дней (TTL поcreated_at).
Сохраняем явную структуру данных для продолжения: выбранные исходники и переводы, план файлов с типами и границами узлов, идентификаторы и границы чанков, их статусы, замечания, SHA, ссылки на обращения и стоимость. Полное дерево или индекс репозитория, временные рабочие объекты и повторные копии всего результата в контекст не входят.
Каждый полный запрос и ответ модели хранится один раз; остальные записи ссылаются на него по идентификатору. Изменение итогового статуса, ошибки публикации или метаданных не переписывает неизменённые тексты и ответы. Размер контекста растёт с объёмом выбранных документов и фактических обращений, а не с числом посторонних файлов репозитория. Примеры «два файла, восемь запросов, 300–400 КБ» не являются фиксированными лимитами или обещанием стоимости.
Крупные объекты читаются ограниченными страницами/частями с учётом лимита результата YDB, без запроса всего объекта одной выдачей. Сохраняются TTL 14 дней, полные запросы и ответы (включая неудачные платные попытки), корректность учёта и возможность продолжения. Удаление производственной базы не является способом исправления этого дефекта.
Весь описанный функционал покрывается автоматическими тестами: успешные сценарии, ошибки и граничные случаи. Для всех трёх режимов нужны сквозные тесты полного процесса, включая публикацию, отчёты, сохранение контекста и учёт стоимости. Тесты запускаются в CI; изменение принимается, когда они проходят. Исправление бага сопровождается воспроизводящим его тестом. В обычном CI ответы модели имитируются, чтобы тесты были воспроизводимыми и не требовали платных вызовов.
Проверяем ответы с finish_reason=length, пустым переводом и непустыми рассуждениями; сохранение цельного Markdown/YFM без внешней JSON-обёртки; исправление только проблемных частей; отсутствие повторных проверок неизменённого кандидата; группировку последствий неуспешной сборки; большой набор диагностик без превышения лимита GitHub; стоимость и понятный комментарий при сбое отчёта/хранилища; постраничное чтение объекта больше лимита одной выдачи YDB; отсутствие индекса всего репозитория и копий запросов/ответов в контексте. Сбой сохранения или публикации не приводит к повторным платным обращениям и двойному учёту.
Набор быстрых локальных фикстур покрывает именно поддерживаемые типы плана: проза и списки; таблицы; links и images; include и YFM; TOC/YAML; Mermaid; fenced Python, Bash, YAML, C++, Java и JavaScript с комментариями и без них; восстановление исходных байтов; разбиение между целыми блоками; ответ с удалённым или сломанным техническим блоком. Не нужно создавать сотни комбинаций: достаточно 15–20 маленьких фикстур и параметризованных проверок, которые вместе выполняются за секунды, без сети и модели.
Тесты проверяют, что переводимые узлы берутся из ответа, неизменяемые всегда из исходника, а несопоставимый чанк остаётся исходным и даёт RED с понятной причиной. Отдельно проверяем допустимую локальную замену URL и якоря. Тесты отменённого поведения удаляются либо заменяются проверками этих требований, а не сохраняются ради зелёного CI. Автотесты не заменяют отдельной проверки совместимости реальных параметров провайдера; ограничения фактической приёмки перечисляются явно.