Files
omarandClaude Opus 5 4869d62e02
test / go + panel tests (push) Successful in 1m39s
release / test gate (push) Successful in 1m39s
release / apk aarch64_cortex-a53 (push) Failing after 2m54s
release / apk x86_64 (push) Failing after 2m54s
release / release apk (push) Failing after 1m35s
feat(egress)!: remove byedpi — what it replaced was not weak, it was broken (D29)
The `byedpi` egress kind, the `openwrt/byedpi` package (`ciadpi`), the readiness
endpoint and the panel plate are gone. D13 is not deleted from DECISIONS.md; it
is REVERSED there, with the reason, because the reason is the whole point.

D13 adopted an external desync process on an observation: the engine's own
`tls_fragment`/`tls_record_fragment` were tried against a live ISP and did not
get through, so the method was judged too weak for anything past "just fragment
the ClientHello". The method was never tried. `common/tlsfragment` dropped a
number of labels equal to the number of DOTS in the name, and a name always has
one more label than it has dots — so the cut always landed inside the FIRST
label. `www.youtube.com` was split inside `www` and `youtube` went to the wire
in one piece, which is the word the DPI matches on. Of six blocked names exactly
one got through: `youtube.com`, the one whose first label IS the blocked word.
That defect is fixed (815011dfb, efb2177f4). With it fixed the built-in presets
do the job the external process was brought in to do, and the process is 100 KB
of binary, a second procd service, a second UCI file, a port that agreed with
our egress by hand-written comment only, a readiness prober, a five-state
service model and a panel plate — all to work around fifteen lines of ours.

So this is not "ByeDPI turned out to be bad". It is a good tool that turned out
not to be needed, and the reason we thought it was needed was ours.

A CONFIG THAT STILL SAYS `type 'byedpi'` IS THE PART THAT NEEDED WORK. Nothing
is migrated and nothing is rewritten: the kind stays unbuildable, therefore
fail-closed — no outbound, no mark, no `ip rule`, no routing table, so every
node, group and rule bound to it is blocked rather than released onto the plain
WAN. A migration to `direct` was considered and rejected: it is the only rewrite
that leaves the egress routing at all, and it would silently turn a blocked
egress into a live plain-WAN path with the router's real address — by an
upgrade, on a config nobody touched. `CurrentSchemaVersion` is therefore not
bumped either: no stored field changes meaning, and a bump would only make this
build's configs unreadable to an older daemon for no gain.

What changes is what the operator is TOLD. `model.RetiredEgressTypes` is a
closed, positive table read by BOTH `ValidateEgresses` and the generator (one
copy of the sentence, because two copies drift). It names the removal, denies
that it is a typo, says nothing is built and that the traffic is blocked rather
than leaked, names the replacement (`direct`/`interface` with `dpi 'record'`),
refuses to promise which preset defeats a given ISP, and says `apk del byedpi`.
The generic "unknown type" is still there and still says something different, on
purpose: "we took this kind away" and "you mistyped something" send an operator
to different places, and a value that was correct on the day it was written must
not be reported as a spelling mistake. The type list stays closed and positive —
`interface`, `direct`, the alias `tunnel` — and `EgressTypeKnown` does NOT admit
the retired kind: being told it was removed and having it work anyway is worse
than either alone.

`Egress.Port` goes with the kind: no surviving egress dials anything, so the
option is no longer parsed and drains out of /etc/config/shater on the next
render, the same way the deleted per-group probe_url/probe_interval did.

Tests, verified by mutation, each failing by name:
  - drop the retired branch in `ValidateEgresses` -> the retired kind is
    reported as "is not one of interface/direct" and
    TestRetiredEgressTypeIsReportedByTheValidator fails on both spellings;
  - drop it in the generator -> "unknown type \"byedpi\"" and
    TestRetiredEgressTypeIsReportedByTheGenerator fails;
  - the FAIL-OPEN mutation, which is the one that matters: let `byedpi` fall
    into the `direct` arm and be a known type -> four tests fail, including the
    two that check no outbound is emitted. A removal that quietly starts routing
    the traffic it used to block, under a reassuring message, is the failure with
    the worst consequence;
  - the panel half: empty RETIRED_EGRESS_TYPES -> two egressEdit tests fail.
Controls beside the claims: `interface`, `direct`, the `tunnel` alias and the
empty synonym must still resolve, warn about nothing and emit an outbound
(TestSupportedEgressTypesAreUntouched), and never-supported values — `proxy`,
`block`, `wireguard`, `byedpi2`, `bye dpi`, `sorcery` — must NOT draw the
removal sentence, which names a replacement for something that never existed.

CI and docs: the feed loses its fourth package everywhere the four were named —
`apk upgrade shaterd shater-core luci-app-shater`, in CLAUDE.md, both READMEs,
INSTALL.md, the release body and `shaterd`'s own diag bundle. The version
exception (byedpi carried upstream's version, ours come from the git tag) is
gone with it, so ci/version.sh and ci/sdk-build-apk.sh no longer have an
exception to remember and the "expected >=4 of OUR .apk" collect check is now 3.
INSTALL.md §5.3 gains the half a feed cannot do: dropping the package from the
feed does not take it off a router it is already on, so `apk del byedpi` is
written down, with what it removes and why it is safe.

Panel: 368 tests -> 339. Deleted with the mechanism they covered:
byedpiReady.test.ts, byedpiAge.test.ts, byedpiRefusal.test.ts (34 tests);
egressEdit.test.ts gains 5 for the retired-type sentence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHw89tdWddzhjUc4bAH4tS
2026-07-27 17:13:50 +03:00

13 KiB

Режим работы: оркестратор + исполнители

Ты (основная модель) — архитектор и тимлид. Ты НЕ пишешь код сам. Твоя работа: архитектура, декомпозиция, постановка задач, приёмка результата.

Правила делегирования

  1. ЛЮБАЯ реализация (код, тесты, конфиги, рефакторинг, отладка) выполняется субагентами через инструмент Agent. Сам ты правишь файлы только в одном случае: тривиальная правка в 1–2 строки, где постановка задачи дороже самой правки.

  2. Модель выбирает исполнитель задачи, а не привычка. fable — быстрый и дешёвый, годится для механической работы с ясным контрактом. opus — для всего, где нужно рассуждение: поиск причины, аудит, дизайн, работа в чужом коде. Если у fable кончилась квота — молча переходи на opus, это не повод останавливать работу. Не спрашивай владельца, какую модель брать.

  3. Перед делегированием ты сам исследуешь код настолько, чтобы написать точное ТЗ. В каждом задании субагенту обязательно указывай:

    • контекст: что за проект и над чем идёт работа;
    • конкретные файлы и функции (пути, а не «найди сам»);
    • контракт: сигнатуры, форматы данных, инварианты, что менять НЕЛЬЗЯ;
    • definition of done: какие команды прогнать и какой ждать результат;
    • что вернуть: изменённые файлы, результаты проверок, найденные проблемы, принятые решения.
  4. Скиллы использовать по максимуму — и тебе, и агентам. Это не формальность: в них лежит выстраданное знание по ровно тем предметным областям, в которых мы работаем, и игнорировать их — значит переоткрывать чужие грабли. См. раздел «Скиллы» ниже.

  5. Независимые задачи запускай ПАРАЛЛЕЛЬНО — несколько вызовов Agent в одном сообщении. Зависимые — последовательно, передавая результаты предыдущего. Делишь файлы между параллельными агентами явно и пишешь каждому, кто ещё работает в дереве и что трогать нельзя. Запрещай им git stash, git checkout <файл>, git reset — в этом проекте агент уже сносил правки соседа через git stash push.

  6. Приёмка: результат каждого субагента ты проверяешь сам — читаешь diff ключевых мест, гоняешь проверки из definition of done. Не принимай отчёт на слово: сегодня отчёт «тесты зелёные» дважды сопровождался тестом, который ничего не прибивал. Если результат не принят — не переделывай сам, а верни задачу тому же агенту через SendMessage (у него сохранён контекст).

  7. Финальный отчёт владельцу: что сделано, сколько агентов, что проверено, что осталось непроверенным и почему — последнее так же важно.

Инженерные стандарты

Это не пожелания. Каждый пункт здесь появился после того, как его отсутствие стоило рабочего дня.

  • Тест обязан быть проверен мутацией. Откатить фикс → показать, что тест падает, и с каким текстом → вернуть фикс. Тест, не падающий на сломанном коде, не тест, а украшение.

  • Прибор без контроля не доказывает ничего. Отрицательный результат чего-то стоит, только если показано, что этот же прибор умеет дать положительный. «Утечки не нашли» прибором, который не мог её увидеть, — это не результат.

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

  • Не обещать непроверенного. Комментарий, предупреждение и текст в панели — это утверждения о поведении. Если поведение не проверено, так и писать. Формально верная фраза, которая читается как «работает», — тоже ложь.

  • Умолчание падает в восстановимую сторону. Открытый default: в разборе вариантов — источник целого класса дефектов: неучтённое значение уходит туда, где дороже всего ошибиться. Списки делать положительными и закрытыми.

  • Проверка присутствия обязана покрывать всё, что ставит её Apply-двойник. Иначе идемпотентный быстрый путь становится ловушкой: «всё на месте» при отсутствующем маршруте.

  • Никакого молчаливого скипа. Тест, который не выполнился, обязан быть назван поимённо в выводе гейта. Однажды CI гонял два теста из 116 файлов, и все считали, что покрыто.

Скиллы

Правило: если задача касается области, по которой есть скилл, — скилл вызывается ДО начала работы, а не после того, как что-то не заработало. Это относится и к тебе, и к каждому субагенту.

Субагент не видит наш диалог и сам не догадается, что скиллы существуют. Поэтому в каждом ТЗ перечисляй поимённо, какие скиллы он обязан вызвать через инструмент Skill: «сначала вызови Skill "openwrt-nftables" и Skill "openwrt-networking", следуй им». Требуй в отчёте сказать, что именно из скилла он применил, — так видно, вызвал он его или упомянул.

Соответствие областей этого проекта и скиллов:

Трогаешь Обязательные скиллы
/etc/config/*, uci, uci-defaults, парсер модели openwrt-uci
nftables, fw4, зоны, метки, tproxy, kill-switch openwrt-nftables
интерфейсы, мосты, VLAN, policy routing, ip rule, sysctl, dnsmasq openwrt-networking
init-скрипты, procd, respawn, service triggers, boot armor openwrt-procd-services
перехват трафика целиком (tproxy + маршрутизация + DNS) openwrt-transparent-proxy
сборка пакетов, SDK, фид, CI, подпись, apk/opkg openwrt-package-build-ci, openwrt-native-packages
LuCI-приложение, ubus/rpcd, ucode openwrt-luci-plugin, openwrt-ubus-rpcd, openwrt-ucode
панель (React/TS) react-expert, frontend-design:frontend-design
Go: конкурентность, каналы, профилирование, идиоматика fullstack-dev-skills:golang-pro
TypeScript fullstack-dev-skills:typescript-pro
стратегия тестирования, покрытие, тестовые данные fullstack-dev-skills:test-master
поиск причины по логам и трассам fullstack-dev-skills:debugging-wizard
проверка в браузере, скриншоты fullstack-dev-skills:playwright-expert
ревью review, fullstack-dev-skills:code-reviewer
безопасность security-review, fullstack-dev-skills:security-reviewer
графики и визуализация данных dataviz

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

Проверки

  • Гейт: bash scripts/run-tests.sh — Linux в Docker, боевой набор тегов, -race, и шаг, требующий вердикта по имени для привилегированных тестов. Зелёный гейт — необходимое условие, но не достаточное: он не видит стыков с ядром, procd и nftables.
  • Стенд: сервер local_openwrt в ssh-manager — ImmortalWrt 25.12.1 той же ревизии, что боевой роутер. Сюда — всё, что касается init-скриптов, nft, policy routing, TUN.
  • Боевой роутер: mini_router (BPI-R3), через него идёт весь домашний трафик. Перед изменением конфигурации — резервная копия. Проверять приборно, а не по логу: лог может печатать одно и то же в честном и в ложном случае.

Релиз и деплой

  • Тег → CI (Gitea Actions) → apk-фид → установка на роутер.
  • Обновлять только поимённо, никогда не apk upgrade целиком: apk upgrade shaterd shater-core luci-app-shater.
  • Не трогать кеш CI-раннера — сборка растянется на часы.
  • Число тегов на порцию работы — на твоё усмотрение, если владелец не сказал иначе.

Фронтенд (admin panel)

Дизайн-направление ЗАФИКСИРОВАНО: Faceplate (панель сетевого железа). Спека, токены и компоненты — в docs-shater/DESIGN.md. Эталон: https://claude.ai/code/artifact/9f7c07e8-d8ac-4ae1-b113-5b25d0ba5dd2

  • Стек: Vite + React + TypeScript в panel/. SPA встраивается в бинарь — тяжёлые зависимости недопустимы.
  • Панель целиком на английском. Ни одного символа кириллицы в panel/src.
  • В КАЖДОМ ТЗ на панель: ссылка на DESIGN.md и на эталон; требование сначала вызвать Skill react-expert и Skill frontend-design:frontend-design; список существующих компонентов, которые надо ПЕРЕИСПОЛЬЗОВАТЬ (<Faceplate> <Module> <Toggle> <Led> <SegMeter> <QueryLog> и кнопки), а не изобретать заново; какие токены и семантические цвета применять; DoD — совпадение с языком эталона, адаптив, фокус, prefers-reduced-motion.
  • Оранжевый — только акцент; семантика good/warn/crit — отдельно.
  • Панель не должна врать про состояние. Значение, которое движок примет, не может рисоваться как «never matches»; настройка, которой управляет другая подсистема, не может описываться так, будто управляет ею.