chore: sync hardening — cp -P, честные ошибки PR, точный парс managed, reuse lint #2

Merged
iuriishikov merged 4 commits from task/sync-hardening into main 2026-07-20 22:35:49 +00:00

task/sync-hardening — отчёт

Три точечных укрепления sync/template-sync.sh + шаг reuse lint в smoke.yml. Семантика синка не менялась: managed-список читается из шаблона, .syncignore детей уважается, PR-ы открываются как раньше. Бриф удалён последним коммитом — контракт отработан.

Что легло и почему

  1. cpcp -P — симлинки едут симлинками. Проверено локально: GNU cp -P симлинки поверх существующего обычного файла корректно заменяет файл симлинкой; [ -f … ] разыменовывает, симлинка с живой целью проходит фильтр. Известная особенность cmp -s (разыменовывает обе стороны) оставлена как есть — по брифу.
  2. Честный код ответа на создании PR. || true убран; код HTTP захватывается через -w '%{http_code}', тело — в файл. Разбор: 201 → «PR → repo»; 409 с телом pull request already exists → «PR уже открыт → repo»; всё остальное (включая транспортные ошибки curl, код 000) → явная ошибка в stderr с кодом и телом, FAILED=1. Выход — накопленный: в конце прогона exit 1, если были отказы. Довод (утверждён на шаге 0): прогон обходит всех детей, отказ API на одном не должен лишать волны остальных здоровых; красный прогон в конце всё равно сигналит. Токен в вывод не попадает: он в заголовке, URL чистый.
  3. Точный парс блока managed: — awk вместо жадного grep по всему файлу: блок начинается строкой managed: (допущен хвостовой комментарий), кончается на следующем ключе верхнего уровня или EOF; строки-комментарии внутри блока пропускаются. Инлайн-комментарии у элементов не обрабатываются — их нет, и старый grep их тоже не резал: менялись только границы блока, паритет семантики. Новых зависимостей нет (bash+awk).
  4. Шаг reuse lint в smoke.yml — на каждый пуш. Установка шагом workflow: apt-get install python3-venv → venv → pip install reuse==6.2.0 в venv. Venv вместо системного pip — из-за PEP 668 на bookworm; пин виден прямо в шаге. Образ shikos/ci не предполагается: шаг работает на дефолтном образе раннера (data.forgejo.org/oci/node:lts, Debian 12 bookworm — выяснено по логу smoke-прогона #28).
  5. Форс-пуш в template-sync — не тронут (решение основателя).

Сверка шага 0 — как разрешились

  • «PR уже существует»: Forgejo 15.0.5, по исходникам v15.0.5 (routers/api/v1/repo/pull.go) — единственный 409-случай в CreatePullRequest; тело {"message":"pull request already exists for these targets [...]"}. Различение: код 409 + подстрока в теле. Расхождений с брифом нет.
  • Образ раннера: runs-on: docker без container: = data.forgejo.org/oci/node:lts — подтверждено логом живого прогона; отсюда способ установки reuse.
  • cp -P поверх обычного файла — проверено локально до кода.

Приёмка (механическая)

  • bash -n sync/template-sync.sh — чисто.
  • Парс managed: проверен исполнением фрагмента, выдернутого из самого скрипта: на текущем .sync.yml шаблона — байт-в-байт тот же набор из 17 путей, что у старого grep; с подмешанным посторонним списком (other:\n - foreign/path) — набор не меняется (старый grep постороннее подхватывал).
  • reuse lint на HEAD — зелёный. Примечание: на промежуточных коммитах ветки lint красный из-за самого брифа docs/tasks/sync-hardening.md (одноразовый файл без SPDX-шапки, добавлен до этой задачи); после его удаления последним коммитом дерево зелёное — CI гоняет HEAD пуша, так что smoke зелёный.
  • Дифф: sync/template-sync.sh + .forgejo/workflows/smoke.yml + удаление брифа. Ничего больше.

Адверсарное ревью диффа (находки построчного ревью)

До коммита дифф прошёл многоагентное ревью (3 линзы: bash-корректность под set -euo pipefail, соответствие брифу, реалии CI; каждая находка — через трёх независимых опровергателей с исполнением фрагментов).

  • Подтверждено и исправлено: первая редакция awk-парсера завершала блок managed: на любой строке с нулевым отступом — комментарий с нулевым отступом внутри блока молча отрезал хвост списка, а элементы с нулевым отступом (managed: + - a.txt — валидный YAML) давали пустой набор: прогон печатал managed: , рапортовал всем репо clean и выходил с 0 — тихое подавление дрейфа, вразрез с темой честных ошибок. Латентно (текущий манифест парсится верно), но исправлено в рамках п. 3: правило элемента перенесено раньше терминатора, комментарии/пустые строки любого отступа — пропуск. Регрессионные случаи добавлены в локальную проверку, все зелёные; критерий «посторонний список не меняет набор» сохранён.
  • Отбито (не дефекты): «хвостовой комментарий на строке элемента приклеивается к пути» — байт-в-байт паритет со старым grep-парсером, в реальном манифесте таких строк нет, обработка не требовалась брифом; «пустой набор при flush-left списке» — та же механика, что в подтверждённой находке, закрыта тем же исправлением.
  • Линза CI (yaml, bookworm/PEP 668, пин, независимость от shikos/ci): находок нет.

Наблюдения

  • Логи Actions и репозиторий читаются анонимно (без токена) — так и был выяснен образ раннера. Если инстанс смотрит в интернет и это не задумано — на усмотрение основателя, к задаче не относится.
  • Боевой прогон синка в задачу не входит: натурной проверкой станет первая волна после следующей задачи (внесение симлинки LICENSES/LicenseRef-PolyForm-Strict-1.0.0.txt в манифест) — cp -P там предусловие.

Отклонения от брифа

Нет.

# task/sync-hardening — отчёт Три точечных укрепления `sync/template-sync.sh` + шаг `reuse lint` в `smoke.yml`. Семантика синка не менялась: managed-список читается из шаблона, `.syncignore` детей уважается, PR-ы открываются как раньше. Бриф удалён последним коммитом — контракт отработан. ## Что легло и почему 1. **`cp` → `cp -P`** — симлинки едут симлинками. Проверено локально: GNU `cp -P` симлинки поверх существующего обычного файла корректно заменяет файл симлинкой; `[ -f … ]` разыменовывает, симлинка с живой целью проходит фильтр. Известная особенность `cmp -s` (разыменовывает обе стороны) оставлена как есть — по брифу. 2. **Честный код ответа на создании PR.** `|| true` убран; код HTTP захватывается через `-w '%{http_code}'`, тело — в файл. Разбор: `201` → «PR → repo»; `409` с телом `pull request already exists` → «PR уже открыт → repo»; всё остальное (включая транспортные ошибки curl, код `000`) → явная ошибка в stderr с кодом и телом, `FAILED=1`. Выход — **накопленный**: в конце прогона `exit 1`, если были отказы. Довод (утверждён на шаге 0): прогон обходит всех детей, отказ API на одном не должен лишать волны остальных здоровых; красный прогон в конце всё равно сигналит. Токен в вывод не попадает: он в заголовке, URL чистый. 3. **Точный парс блока `managed:`** — awk вместо жадного grep по всему файлу: блок начинается строкой `managed:` (допущен хвостовой комментарий), кончается на следующем ключе верхнего уровня или EOF; строки-комментарии внутри блока пропускаются. Инлайн-комментарии у элементов не обрабатываются — их нет, и старый grep их тоже не резал: менялись только границы блока, паритет семантики. Новых зависимостей нет (bash+awk). 4. **Шаг `reuse lint` в `smoke.yml`** — на каждый пуш. Установка шагом workflow: `apt-get install python3-venv` → venv → `pip install reuse==6.2.0` в venv. Venv вместо системного pip — из-за PEP 668 на bookworm; пин виден прямо в шаге. Образ `shikos/ci` не предполагается: шаг работает на дефолтном образе раннера (`data.forgejo.org/oci/node:lts`, Debian 12 bookworm — выяснено по логу smoke-прогона #28). 5. **Форс-пуш в `template-sync`** — не тронут (решение основателя). ## Сверка шага 0 — как разрешились - **«PR уже существует»**: Forgejo 15.0.5, по исходникам v15.0.5 (`routers/api/v1/repo/pull.go`) — единственный 409-случай в `CreatePullRequest`; тело `{"message":"pull request already exists for these targets [...]"}`. Различение: код 409 + подстрока в теле. Расхождений с брифом нет. - **Образ раннера**: `runs-on: docker` без `container:` = `data.forgejo.org/oci/node:lts` — подтверждено логом живого прогона; отсюда способ установки reuse. - **`cp -P` поверх обычного файла** — проверено локально до кода. ## Приёмка (механическая) - `bash -n sync/template-sync.sh` — чисто. - Парс `managed:` проверен исполнением фрагмента, выдернутого из самого скрипта: на текущем `.sync.yml` шаблона — байт-в-байт тот же набор из 17 путей, что у старого grep; с подмешанным посторонним списком (`other:\n - foreign/path`) — набор не меняется (старый grep постороннее подхватывал). - `reuse lint` на HEAD — зелёный. Примечание: на промежуточных коммитах ветки lint красный из-за самого брифа `docs/tasks/sync-hardening.md` (одноразовый файл без SPDX-шапки, добавлен до этой задачи); после его удаления последним коммитом дерево зелёное — CI гоняет HEAD пуша, так что smoke зелёный. - Дифф: `sync/template-sync.sh` + `.forgejo/workflows/smoke.yml` + удаление брифа. Ничего больше. ## Адверсарное ревью диффа (находки построчного ревью) До коммита дифф прошёл многоагентное ревью (3 линзы: bash-корректность под `set -euo pipefail`, соответствие брифу, реалии CI; каждая находка — через трёх независимых опровергателей с исполнением фрагментов). - **Подтверждено и исправлено:** первая редакция awk-парсера завершала блок `managed:` на любой строке с нулевым отступом — комментарий с нулевым отступом внутри блока молча отрезал хвост списка, а элементы с нулевым отступом (`managed:` + `- a.txt` — валидный YAML) давали пустой набор: прогон печатал `managed: `, рапортовал всем репо `clean` и выходил с 0 — тихое подавление дрейфа, вразрез с темой честных ошибок. Латентно (текущий манифест парсится верно), но исправлено в рамках п. 3: правило элемента перенесено раньше терминатора, комментарии/пустые строки любого отступа — пропуск. Регрессионные случаи добавлены в локальную проверку, все зелёные; критерий «посторонний список не меняет набор» сохранён. - **Отбито (не дефекты):** «хвостовой комментарий на строке элемента приклеивается к пути» — байт-в-байт паритет со старым grep-парсером, в реальном манифесте таких строк нет, обработка не требовалась брифом; «пустой набор при flush-left списке» — та же механика, что в подтверждённой находке, закрыта тем же исправлением. - Линза CI (yaml, bookworm/PEP 668, пин, независимость от `shikos/ci`): находок нет. ## Наблюдения - Логи Actions и репозиторий читаются **анонимно** (без токена) — так и был выяснен образ раннера. Если инстанс смотрит в интернет и это не задумано — на усмотрение основателя, к задаче не относится. - Боевой прогон синка в задачу не входит: натурной проверкой станет первая волна после следующей задачи (внесение симлинки `LICENSES/LicenseRef-PolyForm-Strict-1.0.0.txt` в манифест) — `cp -P` там предусловие. ## Отклонения от брифа Нет.
docs: add task/sync-hardening brief
All checks were successful
smoke / smoke (push) Successful in 6s
c8b45c7a3e
docs: drop LICENSE hash guard from sync-hardening brief (founder ruling)
All checks were successful
smoke / smoke (push) Successful in 3s
caf0562599
- cp -P: симлинки managed-файлов едут симлинками (предусловие следующей
  волны с LICENSES/LicenseRef-PolyForm-Strict-1.0.0.txt)
- создание PR: честный разбор ответа вместо '|| true' — 201 как раньше,
  409 'already exists' отдельной строкой, настоящий отказ — ошибка в лог
  и накопленный ненулевой выход в конце прогона
- managed: точный парс блока awk-ом вместо жадного grep по всему файлу;
  посторонний yaml-список в .sync.yml набор не меняет
- smoke.yml: шаг reuse lint на каждый пуш, пин reuse==6.2.0 в venv
  (PEP 668 на bookworm), без опоры на образ shikos/ci

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs: drop sync-hardening task brief
All checks were successful
smoke / smoke (push) Successful in 24s
c4102dafb5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
iuriishikov left a comment

Сверено против брифа (docs/tasks/sync-hardening.md @ caf0562) и вольта. APPROVED.

Scope — все четыре пункта на месте, механика проверена по диффу:

  • cp -P — точечно, одна строка; разыменование фильтра [ -f ] учтено в отчёте, особенность cmp -s сохранена по брифу.
  • Ответ API: 201 / 409 + «pull request already exists» / прочее (включая транспортный 000) — тело в stderr, накопленный exit 1 в конце прогона. Токен в вывод не попадает. Дизайн накопленного выхода — по слову-го шага 0.
  • awk-парс managed:: правило элемента стоит раньше терминатора — flush-left элементы не рвут блок и не дают пустой набор; комментарии и пустые строки любого отступа — пропуск; посторонний топ-уровневый ключ закрывает блок. Критерий брифа («посторонний список не меняет набор») выполняется. Паритет со старым grep по хвостовым комментариям элементов принят как задокументированный.
  • smoke.yml: строго добавка шага; пин reuse==6.2.0, venv против PEP 668, ноль опоры на образ shikos/ci — независимость от параллельной задачи соблюдена. Зелёный smoke #186 на HEAD подтверждён вызовом.

Non-goals соблюдены: форс-пуш нетронут, стражи содержимого не введены, rust-ci.yml/template-sync.yml не тронуты, исключение .forgejo на месте. Дифф — ровно два файла; бриф схлопнулся добавлением+удалением и в полёте не правился: буква заморозки соблюдена — чище прецедента ci-image#9, красный lint промежуточных коммитов принят вместо правки контракта.

Для основателя, не блокирует: (1) отчёт ссылается на го шага 0 (накопленный выход вместо немедленного) — подтверждение, что стоп и го были, за тобой; (2) наблюдение кодера про анонимное чтение репо и логов Actions — вне задачи, маршрутизируется отдельно.

Мерж — за основателем.

Сверено против брифа (`docs/tasks/sync-hardening.md` @ caf0562) и вольта. APPROVED. **Scope — все четыре пункта на месте, механика проверена по диффу:** - `cp -P` — точечно, одна строка; разыменование фильтра `[ -f ]` учтено в отчёте, особенность `cmp -s` сохранена по брифу. - Ответ API: `201` / `409` + «pull request already exists» / прочее (включая транспортный `000`) — тело в stderr, накопленный `exit 1` в конце прогона. Токен в вывод не попадает. Дизайн накопленного выхода — по слову-го шага 0. - awk-парс `managed:`: правило элемента стоит раньше терминатора — flush-left элементы не рвут блок и не дают пустой набор; комментарии и пустые строки любого отступа — пропуск; посторонний топ-уровневый ключ закрывает блок. Критерий брифа («посторонний список не меняет набор») выполняется. Паритет со старым grep по хвостовым комментариям элементов принят как задокументированный. - `smoke.yml`: строго добавка шага; пин `reuse==6.2.0`, venv против PEP 668, ноль опоры на образ `shikos/ci` — независимость от параллельной задачи соблюдена. Зелёный smoke #186 на HEAD подтверждён вызовом. **Non-goals соблюдены:** форс-пуш нетронут, стражи содержимого не введены, `rust-ci.yml`/`template-sync.yml` не тронуты, исключение `.forgejo` на месте. Дифф — ровно два файла; бриф схлопнулся добавлением+удалением и в полёте не правился: буква заморозки соблюдена — чище прецедента ci-image#9, красный lint промежуточных коммитов принят вместо правки контракта. **Для основателя, не блокирует:** (1) отчёт ссылается на го шага 0 (накопленный выход вместо немедленного) — подтверждение, что стоп и го были, за тобой; (2) наблюдение кодера про анонимное чтение репо и логов Actions — вне задачи, маршрутизируется отдельно. Мерж — за основателем.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
shikos/.forgejo!2
No description provided.