# CONSTITUTION — sing-box-lx Неизменяемые принципы проекта. При конфликте с любым SPEC/PLAN — приоритет у этого документа. --- ## 1. Миссия `sing-box-lx` — **тонкий downstream** [SagerNet/sing-box](https://github.com/SagerNet/sing-box). Это **upstream + небольшой набор клиентских фич**, которых в upstream нет и не будет. Сейчас их несколько: XHTTP (клиентский v2ray-транспорт, совместимый с Xray XHTTP); AmneziaWG 2.0 (AWG2) — клиентский endpoint поверх WireGuard, с обфускацией (Jc/Jmin/Jmax, S1/S2, H1–H4, I1–I5); и расширения libbox command-протокола (проброс существующих возможностей ядра в наш CommandClient взамен вырезанных upstream-каналов, §3.6). Набор фич со временем может расти — другие протоколы, новые возможности, — но **философия тонкого форка неизменна**: каждая новая фича обязана целиком укладываться в §2–3, иначе она не принимается. Текущие транспортные фичи **отклонены upstream** ([XHTTP — not planned](https://github.com/SagerNet/sing-box/issues/3550), [AmneziaWG — closed not-planned](https://github.com/SagerNet/sing-box/issues/4045)), поэтому форк постоянный, а «согласованность с upstream» достигается не вливанием в него, а **дешёвым ребейзом на каждый новый тег**. Этот баланс держится на трёх явных принципах, перечисленных в порядке приоритета: (1) **тонкий слой** — мы держим максимально тонкий слой поверх upstream: минимум кода, минимум тронутых upstream-файлов, потому что каждая правка upstream-файла и каждая строка диффа — это стоимость на каждом ручном ребейзе; (2) **идём за upstream** — синхронизация только дешёвым ребейзом на каждый новый тег, и при любом выборе побеждает вариант с самым дешёвым ребейзом, даже ценой удобства реализации; (3) **делаем нужные фичи** — если фича нужна нам или нашим пользователям и её нельзя получить в той поверхности дистрибуции, которую мы реально поставляем (§3.5), мы её делаем, но целиком в рамках §2–3. Принцип (3) подчинён (1)–(2): «нужно» — необходимое, но не достаточное условие; достаточность даёт только прохождение теста §3.1. Фича, которая делает ребейз дороже, переосмысливается, а не принимается. --- ## 2. Приоритеты (в порядке убывания) 1. **Согласованность с upstream / минимальный дифф.** Любое решение выбирается так, чтобы ребейз на следующий тег был максимально дешёвым. 2. **Корректность и совместимость** с реальными серверами Xray-XHTTP и AmneziaWG 2.0. 3. **Ребейзопригодность** изменений (изоляция, атомарность, маркеры). 4. **Сами фичи** (функциональность XHTTP/AWG2 и прочих принятых). Появление принципа «делаем нужные фичи» (§1) НЕ повышает приоритет п.4 (сами фичи) и НЕ понижает п.1 (минимальный дифф / дешёвый ребейз). Желание иметь фичу — необходимое, но не достаточное условие: оно открывает тест §3.1, а не отменяет его. При конфликте «фича удобнее так / ребейз дешевле иначе» всегда выбирается дешёвый ребейз. Для генерируемого кода ребейзопригодность (п.3) означает воспроизводимую регенерацию из помеченного источника, а не ручной мёрж артефакта (§3.6). Если фича требует жертвовать пунктом 1 — она переосмысливается, а не пункт 1. --- ## 3. Жёсткие правила (запреты и инварианты) ### 3.1 Объём - **Только принятые фичи.** Любой код вне фич, оформленных через Spec Kit (`SPECS/NNN-…`), — вне скоупа. Багфиксы upstream не патчим у себя — ждём апстрим-тег. **Исключение из «ждём тег»:** если функциональность недостижима в нашем канале не из-за временной недоделки, а потому что upstream сознательно не выносит её в наш канал (тега, который это исправит, не будет — их клиенту это не нужно), ожидание тега не применяется: фича проходит как новая по §3.1(а). Исключение покрывает ТОЛЬКО восстановление в нашем канале того, что upstream уже реализовал в другом, и ТОЛЬКО для Spec-Kit-фич; новую функциональность, которой нет в upstream ни в одном канале, и точечные upstream-багфиксы оно не легализует (их по-прежнему ждём тегом). - **Критерии новой фичи** (все обязательны): **(а) тест оправданности** — все три под-условия обязательны: (а1) **нужна нам или нашим пользователям** — есть конкретный запрос/задача LxBox, а не «было бы хорошо»; (а2) **её нет в нашем целевом канале дистрибуции** — в той поверхности, которую форк реально поставляет (§3.5: desktop-бинарь `sing-box` и Android `libbox` через нативный `CommandClient`). Если функциональность ЕСТЬ в upstream, но только в канале, который форк осознанно не собирает (например per-node delay и таблица правил доступны в upstream лишь через Clash API REST, вырезанный вместе с `with_clash_api`), — (а2) выполнено. «Нет в нашем канале» — это НЕ «нет в удобной нам форме»: косметическая или дублирующая переупаковка уже доступной у нас возможности (а2) НЕ проходит. Критерий самозакрывающийся: как только канал начинает собираться, дверь захлопывается. (а3) **держать у себя дешевле, чем альтернатива** — свой дифф дешевле по ребейзу, чем (i) ждать/протолкнуть upstream-тег, (ii) вернуть вырезанную подсистему целиком, (iii) решить на стороне потребителя (LxBox). Дешевизна фиксируется в SPEC КОНКРЕТНЫМ перечнем тронутых upstream-файлов и зоной касания ребейза (аудируемое число, а не прозой), эталон — точечный бэкпорт SPEC 013 вместо полной 1.14-миграции из-за дорогого ребейза подмодуля; **(б)** она изолируется по правилам §3.2–3.3 (для расширений command-протокола — §3.6) — новые файлы, свой build-tag, минимальные помеченные швы; **(в)** проходит полный цикл Spec Kit. Фича, требующая размазанных правок upstream-файлов, переосмысливается или отклоняется (см. §2). - **Scope — client-only.** Реализуем outbound/endpoint и клиентскую сторону транспорта. Server/inbound — **отложены** (отдельные будущие задачи), в текущих спеках не реализуются. ### 3.2 Изоляция изменений - **SPEC.md = актуальное состояние сверху, НЕ хронология.** SPEC.md всегда описывает ТЕКУЩУЮ архитектуру фичи; порядок разделов — от актуального к деталям, не по ходу разработки. Дневниковые пометки («исправлено в rc.N», «прежнее утверждение неверно», отвергнутые подходы) в SPEC.md запрещены. При смене архитектуры фичи SPEC.md переписывается под новое состояние, а старое + обоснование смены выносятся в `HISTORY.md` той же папки (см. `SPECS/README.md` → «Структура SPEC.md»). - **Go module path остаётся `github.com/sagernet/sing-box`.** Не переименовывать — это ломает все внутренние импорты и каждый ребейз. - **Новый код — в новых файлах/пакетах.** XHTTP-транспорт — пакет `transport/v2rayxhttp`. AWG — в выделенных файлах рядом с `protocol/wireguard` / `transport/wireguard`. - **Каждая фича — за build-tag:** `with_xhttp`, `with_awg`. Без тега сборка обязана быть **байт-в-байт эквивалентна upstream по поведению** (фича отсутствует). - **Шаблон гейтинга — `include/*_stub.go`** (как у upstream `include/wireguard.go` + `wireguard_stub.go`): реальная регистрация под тегом, заглушка с понятной ошибкой без тега. - **Расширения libbox command-протокола** изолируются не как новый-файл+тег целиком, а по специальному режиму §3.6 (handler'ы за build-tag по образцу `daemon/started_service_usbip{,_stub}.go`; шов в `.proto` под маркером; `.pb.go` — регенерируемый артефакт). Это единственное послабление формы изоляции, и оно компенсируется более строгим режимом, см. §3.6. ### 3.3 Правки upstream-файлов - Допускаются **только** там, где иначе нельзя (диспетчеры, struct опций, списки констант, `go.mod`, а также шов в `daemon/*.proto` для расширений по §3.6). Регенерируемые артефакты (`*.pb.go`, `*_grpc.pb.go`) под этот режим НЕ подпадают: они — машинный вывод protoc, маркеров не несут и руками не правятся; их форма целиком определяется помеченным `.proto`, на ребейзе они перегенерируются, а не мёржатся текстом. - Каждая такая правка **обёрнута маркером**: ```go // lx:begin xhttp ... // lx:end xhttp ``` - Правки upstream-файлов выносятся в **отдельные атомарные коммиты** (см. IMPLEMENTATION_PROMPT). Один коммит = одна логическая правка одной зоны. ### 3.4 Синхронизация - **Только rebase, никогда merge.** Ветка `lx` всегда ребейзится поверх тега upstream (`v1.13.13`, затем следующий стабильный). - `origin` = `Leadaxe/sing-box-lx`, `upstream` = `SagerNet/sing-box`. Теги тянем из `upstream`. ### 3.5 Дистрибуция - **Desktop — бинарь `sing-box`** (drop-in для лаунчера `singbox-launcher`, который ищет `LookPath("sing-box")` → `bin/sing-box`). - **Android — `libbox.aar`** (+ `libbox-legacy.aar`, SDK21): gomobile-сборка `experimental/libbox` через upstream `make lib_android`, с зашитыми `with_xhttp`/`with_awg` (и `with_lx_command` для расширений §3.6) (`cmd/internal/build_libbox`, `// lx:`-блок; tailscale/clash_api выкинуты). Для встраивания в Android-приложение-потребитель. `Libbox.version()` → `1.14.0-lx.N`. - Идентичность сборки — **в версии**: `sing-box version` / `Libbox.version()` → `1.14.0-lx.N`, где источник версии — lx-тег `vX.Y.Z-lx.N` (см. задачу BUILD_CI_RELEASE). ### 3.6 Расширения libbox command-протокола (мост LxBox ↔ ядро) Отдельный, более узкий режим изоляции — **только** для класса «новый RPC в нативном протоколе управления ядром» (`daemon/*.proto` + `experimental/libbox/`), где упаковка «новый файл + свой build-tag» для самого RPC невозможна: RPC объявляется внутри единого upstream-`service StartedService {}`, а `*.pb.go`/`*_grpc.pb.go` регенерируются protoc и стирают любые `// lx:` маркеры. Допускается только под §3.1(а) и при выполнении ВСЕХ условий: - **(1) Потолок — только мост.** Режим разрешён ИСКЛЮЧИТЕЛЬНО для проброса наружу уже существующей в ядре возможности через CommandClient (delay-тест outbound/endpoint, история URLTest, чтение таблицы правил). Новые подсистемы, серверные/inbound-фичи, бизнес-логика в `daemon/` — не сюда. Если RPC тянет за собой новый пакет логики — это уже не «расширение протокола». - **(2) Handler'ы и клиентские методы — в новых `*_lx.go`.** Реализация (`func (s *StartedService) …` и клиентские методы) выносится в новые файлы (`daemon/started_service_*_lx.go`, `experimental/libbox/command_client_*_lx.go`). В upstream-файлах (`started_service.go`, `command_client.go`) — ноль строк сверх диспетчерского шва, и тот за маркером, отдельным атомарным коммитом. - **(3) Build-tag `with_lx_command` ОБЯЗАТЕЛЕН** — по доказанному в репозитории образцу `daemon/started_service_usbip{,_stub}.go`: реальные handler'ы за `//go:build with_lx_command`, файл-близнец `*_stub.go` за `//go:build !with_lx_command` возвращает `codes.Unimplemented`. Без тега сборка по поведению эквивалентна upstream (RPC просто не обслуживается). Тег гейтит написанные руками handler-методы, а не генерируемые типы, поэтому регенерация `.pb.go` гейтингу не мешает; «тег невозможен» как исключение НЕ допускается. - **(4) Шов в `.proto` — за маркером.** Новые `rpc`-строки внутри `service StartedService {}` и новые `message`-типы обёрнуты `// lx:begin lx_command … // lx:end lx_command`. Шов виден глазом и переносится при ребейзе вручную (несколько строк), даже если upstream меняет соседние RPC. - **(5) `.pb.go`/`*_grpc.pb.go` — регенерируемый артефакт.** Не правятся руками, маркеров не несут, на ребейзе пере-генерируются из смерженного `.proto`, а не мёржатся. Сегодня генерация НЕ зафиксирована (`make proto` шеллит системный `protoc` и ставит плагины `@latest`, в `Makefile.lx` proto-таргета нет), поэтому детерминированная регенерация — ОБЯЗАТЕЛЬНЫЙ новый deliverable первого SPEC этого класса: добавить в `Makefile.lx` proto-таргет с зафиксированными версиями `protoc`/плагинов. Это требование, а не существующая гарантия. - **(6) Точка прошивки тега в AAR.** Новый тег добавляется в `sharedTags` в `cmd/internal/build_libbox/main.go` (файл уже несёт `// lx:`-блок) — иначе RPC не попадёт в `libbox.aar`. Этот файл считается третьим тронутым upstream-ish файлом класса. - **(7) CI-инвариант.** CI обязан доказывать обе сборки: без `with_lx_command` (поведенчески эквивалентна upstream, `*_stub.go` отдаёт Unimplemented) и с тегом (RPC обслуживается). Usbip-паттерн делает эту проверку дешёвой. - **(8) Ребейз-цена — в SPEC.** SPEC перечисляет точный список тронутых общих файлов (`.proto` + факт регенерации `.pb.go` + `build_libbox/main.go`) и оценивает стоимость ручного переноса. Выход за «несколько строк в `.proto` + регенерация + строка тега» — фича переосмысливается (§2). **Инвариант §3.6:** это ЕДИНСТВЕННОЕ послабление формы изоляции во всей конституции, ограниченное мостом `daemon/*.proto`+`experimental/libbox/` и пробросом уже существующей возможности ядра. Любая попытка применить §3.6 за этими пределами или для новой подсистемы — нарушение §3.1, отклоняется. Объём кода под §3.6 держится минимальным наравне с приоритетом №1, и §3.6 не создаёт прецедента для будущих послаблений. --- ## 4. Архитектурные ориентиры (факты upstream v1.13.13) - **v2ray-транспорты** диспатчатся `switch` по `options.Type` в `transport/v2ray/transport.go` (`NewClientTransport`/`NewServerTransport`). Константы — `constant/v2ray.go`. Опции — `option/v2ray_transport.go` (`_V2RayTransportOptions`). VLESS/VMess/Trojan ходят через общий транспорт — пер-протокольных правок не требуется. - **WireGuard** — это **endpoint**: `protocol/wireguard/endpoint.go`, регистрация `endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint)`, проводка в `include/wireguard.go` (+ `wireguard_stub.go`). Девайс — через `transport/wireguard`, зависимость `github.com/sagernet/wireguard-go` в `go.mod`. - **libbox command-протокол** — gRPC-сервис `StartedService` в `daemon/started_service.proto` (+ регенерируемые `*.pb.go`/`*_grpc.pb.go`), клиент `experimental/libbox/command_client.go`. Опциональные RPC гейтятся build-tag'ом по парному паттерну `daemon/started_service_usbip{,_stub}.go` (реальные handler'ы / `codes.Unimplemented`-заглушка). Это образец для §3.6. --- ## 5. Референсы (только как образец, код не тянуть «как есть») - **AWG2** — [`hoaxisr/amnezia-box`](https://github.com/hoaxisr/amnezia-box) (submodule + `patches/amneziawg-go`, тег `with_awg`). - **XHTTP** — [`hiddify/hiddify-sing-box`](https://github.com/hiddify/hiddify-sing-box), пакет `transport/v2rayxhttp`. - **Спецификация XHTTP** — Xray-core (актуальная версия параметров `mode`/`path`/`host`/`extra`). - **Clash API как функциональный эталон** для расширений §3.6 — `experimental/clashapi/` (`proxies.go` per-node delay, `rules.go` таблица правил): что именно пробрасываем в CommandClient. Код не тянуть — повторяем семантику через нативный канал. --- ## 6. Лицензия Upstream — GPLv3. Портируемый код из сторонних проектов держать в отдельных файлах с сохранением исходных лицензионных заголовков и указанием происхождения.