chore(scratch): очищен рабочий каталог перед слиянием
- Зачем: - рабочие задачи и handoff не должны попадать в итоговое состояние master. - Что: - удалены временные материалы разработки proxy-client. - добавлен .scratch/.gitkeep для сохранения пустого рабочего каталога. - Проверка: - git diff --cached --check. - find .scratch -mindepth 1 -print.
This commit is contained in:
1 parent
9c2e134fc1
commit
fb655f0dc8
9 files changed
-942
No files matched your search
Whitespace-only changes.
@@ -1,100 +0,0 @@
|
|||||||
# Handoff: `proxy-client` переименован, Ubuntu-приёмка пройдена
|
|
||||||
|
|
||||||
Обновлён: 2026-07-21 11:13 EDT.
|
|
||||||
|
|
||||||
## Состояние задачи 05
|
|
||||||
|
|
||||||
Автоматическая часть готова. Публичное имя изменено с `xray-proxy` на
|
|
||||||
`proxy-client`. Ручная приёмка на Ubuntu 26.04 с системным Python 3.14 пройдена
|
|
||||||
на подтверждённо рабочем профиле XHTTP/TLS с ALPN `h3`. Статус тикета остаётся
|
|
||||||
`ready-for-human`, пока не решено, нужна ли проверка на РЕД ОС, Astra Linux или
|
|
||||||
Alt Linux.
|
|
||||||
|
|
||||||
Тикет: `.scratch/proxy-client/issues/05-package-delivery-and-acceptance.md`.
|
|
||||||
|
|
||||||
## Переименование
|
|
||||||
|
|
||||||
- stow-пакет: `proxy-client/`;
|
|
||||||
- CLI: `~/.local/bin/proxy-client`;
|
|
||||||
- единый пользовательский каталог: `~/.config/proxy-client/`;
|
|
||||||
- служебный каталог: `~/.local/state/proxy-client/`;
|
|
||||||
- машинный конфиг и закрытый профиль теперь лежат рядом как `config` и
|
|
||||||
`default.url`;
|
|
||||||
- примеры называются `config.external` и `config.xray`;
|
|
||||||
- имена тестов, спецификации, задач, README и Bash-модуля приведены к
|
|
||||||
`proxy-client`;
|
|
||||||
- `Xray`, бинарник `xray` и значение `type = xray` сохранены как названия
|
|
||||||
конкретного движка.
|
|
||||||
|
|
||||||
Поддержка старой раскладки в `install.sh` намеренно не добавлена: публичное имя
|
|
||||||
изменено до завершения разработки и выпуска. Текущий HOME уже приведён к новой
|
|
||||||
структуре. Свежая установка заранее создаёт настоящий
|
|
||||||
`~/.config/proxy-client`, поэтому Stow не может свернуть его в ссылку на
|
|
||||||
репозиторий.
|
|
||||||
|
|
||||||
## Автоматическая проверка
|
|
||||||
|
|
||||||
`bash tests/test_proxy_client_full.sh` завершился с кодом 0 после
|
|
||||||
переименования:
|
|
||||||
|
|
||||||
- системный Python 3.14: 79 тестов, `OK`, 61,034 с;
|
|
||||||
- Bash-интеграция: 8 проверок, `PASS`; `shellcheck` не установлен;
|
|
||||||
- установка и миграция: `All install tests passed`;
|
|
||||||
- CPython 3.8 через `uv`: 79 тестов, `OK`, 52,446 с.
|
|
||||||
|
|
||||||
Дополнительно прошли синтаксическая проверка shell-файлов и `git diff --check`.
|
|
||||||
|
|
||||||
## Текущий HOME
|
|
||||||
|
|
||||||
Во время разработки текущий HOME приведён к новой структуре:
|
|
||||||
|
|
||||||
- `~/.config/proxy-client` — настоящий каталог;
|
|
||||||
- `config` и `default.url` — обычные файлы с правами `600`;
|
|
||||||
- профиль содержит одну непустую строку, его содержимое не выводилось;
|
|
||||||
- `shell.bash`, `examples` и CLI указывают в новый stow-пакет;
|
|
||||||
- старые `~/.config/xray-proxy`, `~/.config/proxy/config`,
|
|
||||||
`~/.local/bin/xray-proxy` и `~/.local/state/xray-proxy` отсутствуют.
|
|
||||||
|
|
||||||
## Ручная приёмка
|
|
||||||
|
|
||||||
Подтверждено:
|
|
||||||
|
|
||||||
- установлен Xray 26.3.27;
|
|
||||||
- `proxy-client start` успешно проверяет версию, строит JSON и запускает Xray;
|
|
||||||
- `proxy-client status` видит управляемый процесс на `127.0.0.1:12334`;
|
|
||||||
- `xray run -test` сообщает `Configuration OK`;
|
|
||||||
- встроенное определение внешнего IP работает;
|
|
||||||
- HTTP CONNECT и SOCKS5h через `127.0.0.1:12334` получают HTTP 200;
|
|
||||||
- после проверки процесс штатно остановлен командой `proxy-client stop`.
|
|
||||||
|
|
||||||
Первый профиль не работал и в исходном клиенте. Его дополнительная диагностика
|
|
||||||
на Xray 26.3.27 и 25.12.8 не выявила расхождений преобразования; оба клиента
|
|
||||||
получали от удалённой стороны неверный VLESS-ответ. Понижение версии не помогло.
|
|
||||||
После замены строки на рабочую H3-профиль сразу прошёл полный сценарий на
|
|
||||||
26.3.27. Содержимое профилей в Git не попадало, временные копии удалены.
|
|
||||||
|
|
||||||
Короткая проверка стабильности H3 также прошла: 120 циклов дали 240 новых HTTP
|
|
||||||
CONNECT- и SOCKS5h-соединений без ошибок; две одновременные загрузки по 10 МБ
|
|
||||||
завершились полностью; после 45 секунд простоя оба способа снова получили HTTP
|
|
||||||
200. Известный сбой первых соединений из XTLS/Xray-core#3673 не воспроизвёлся.
|
|
||||||
Это не заменяет длительное наблюдение под обычной нагрузкой.
|
|
||||||
|
|
||||||
## Документация и установка
|
|
||||||
|
|
||||||
Корневой README сокращён до обзора, а `proxy-client/README.md` перестроен по
|
|
||||||
пути читателя: установка, выбор режима, настройка, ежедневная работа,
|
|
||||||
диагностика и справочник. Установщик заранее создаёт настоящие `~/.local/bin` и
|
|
||||||
`~/.config/proxy-client`, чтобы Stow не сворачивал изменяемые каталоги в ссылки
|
|
||||||
на репозиторий. Это покрыто свежим HOME и посторонней ссылкой `~/.local`.
|
|
||||||
|
|
||||||
## Следующий шаг
|
|
||||||
|
|
||||||
Решить, обязательна ли ручная проверка на доступной системе из семейства РЕД ОС,
|
|
||||||
Astra Linux или Alt Linux. Если нет, привести статус исходного дизайна и
|
|
||||||
заявления о поддержке в соответствие с фактически пройденной Ubuntu-приёмкой.
|
|
||||||
|
|
||||||
## Git
|
|
||||||
|
|
||||||
Переименование зафиксировано коммитом `6e431d8`, успешная H3-приёмка —
|
|
||||||
`bca90c1`. Не включать посторонние неотслеживаемые
|
|
||||||
`agents/.agents/.skill-lock.json` и `scripts/setup-xray-proxy.sh`.
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
# 01 — Внешний прокси и переносимый Bash-интерфейс
|
|
||||||
|
|
||||||
**What to build:** Самостоятельный пользовательский пакет прокси, через который
|
|
||||||
можно безопасно запускать команды с уже работающим внешним прокси, например
|
|
||||||
Hiddify. Привычные Bash-команды переходят на единый CLI-контракт, но прежний
|
|
||||||
сценарий без машинного конфига продолжает работать.
|
|
||||||
|
|
||||||
**Blocked by:** None — can start immediately.
|
|
||||||
|
|
||||||
**Status:** done
|
|
||||||
|
|
||||||
Часть требований пересмотрена в 06 — см. 06-simplification-pass.md.
|
|
||||||
|
|
||||||
- [x] CLI работает на Python 3.8+ без сторонних библиотек, ничего не устанавливает
|
|
||||||
и не запускает без явной подкоманды и выдаёт понятную ошибку на более старом
|
|
||||||
Python.
|
|
||||||
- [x] Машинный INI явно различает типы `external` и `xray`, проверяет имя, адрес,
|
|
||||||
порт и добавку к `NO_PROXY`; отсутствие файла сохраняет внешний прокси
|
|
||||||
`hproxy.local:12334` без неявного переключения на другой тип.
|
|
||||||
- [x] Канонический машинный INI содержит единственную секцию `[proxy]` и ровно
|
|
||||||
по одному ключу `type`, `name`, `address`, `port`, `no_proxy_extra`:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[proxy]
|
|
||||||
type = external
|
|
||||||
name = Hiddify
|
|
||||||
address = hproxy.local
|
|
||||||
port = 12334
|
|
||||||
no_proxy_extra = .example.internal,git.example.internal
|
|
||||||
```
|
|
||||||
|
|
||||||
Все ключи обязательны при наличии файла; `no_proxy_extra` может быть пустым.
|
|
||||||
Неизвестные секции и ключи, повторы, пропуски, многострочные или содержащие
|
|
||||||
NUL значения, пустые имя или адрес и порт вне диапазона 1–65535 отклоняются.
|
|
||||||
Добавки к `NO_PROXY` задаются через запятую, окружающие пробелы удаляются,
|
|
||||||
пустые элементы отклоняются.
|
|
||||||
- [x] `address` принимает IPv4, IPv6 без квадратных скобок либо ASCII DNS-имя
|
|
||||||
из корректных непустых меток; схема URL, встроенный порт, скобки, zone ID,
|
|
||||||
пробелы, `/`, `@` и другие URL-разделители отклоняются. При формировании
|
|
||||||
proxy-URL CLI сам заключает IPv6-литерал в квадратные скобки.
|
|
||||||
- [x] Для типа `external` команды `status`, `env` и `run -- COMMAND ARG...`
|
|
||||||
проверяют только настроенный адрес и порт; недоступный прокси запрещает запуск
|
|
||||||
дочерней команды с понятным сообщением.
|
|
||||||
- [x] До реализации управляемого процесса конфиг с `type=xray` обрабатывается
|
|
||||||
безопасным отказом: `status`, `env`, `run` и `start` не считают один открытый
|
|
||||||
порт доказательством управляемого Xray и не запускают дочернюю команду.
|
|
||||||
- [x] `env` при успехе печатает только восемь разрешённых proxy-переменных в двух
|
|
||||||
регистрах, а при ошибке не печатает частичный результат; базовый `NO_PROXY`
|
|
||||||
включает loopback и узел прокси, затем дополняется машинной настройкой.
|
|
||||||
- [x] Документированный `PROXY_PREFLIGHT_TIMEOUT` сохраняется как положительное
|
|
||||||
число секунд с прежним значением по умолчанию `2` для проверок доступности из
|
|
||||||
`status`, `env` и `run`; ошибочное значение даёт понятный отказ.
|
|
||||||
- [x] `run` без искажений сохраняет аргументы и код возврата команды, передаёт
|
|
||||||
proxy-переменные только дочернему процессу и возвращает отдельную понятную
|
|
||||||
ошибку, если команда отсутствует в `PATH`.
|
|
||||||
- [x] Переносимый Bash-модуль становится единственным владельцем `proxy_on`,
|
|
||||||
`proxy_off`, `pclaude`, `pcodex`, `myip` и внутренних proxy-помощников;
|
|
||||||
WSL-функция обновления `hproxy.local` остаётся независимой.
|
|
||||||
- [x] Обычный запуск dotfiles подключает общий Bash-файл и переносимый
|
|
||||||
proxy-модуль независимо: после установки доступны все пять proxy-команд и
|
|
||||||
`update_hproxy`; подключение одного переносимого модуля без остальных
|
|
||||||
dotfiles также предоставляет все пять proxy-команд.
|
|
||||||
- [x] Bash-модуль принимает вывод `env` атомарно, проверяет белый список и
|
|
||||||
наличие каждого из восьми ключей ровно один раз, не использует `eval` и при
|
|
||||||
любом неверном выводе оставляет прежние восемь переменных без изменений;
|
|
||||||
`proxy_on` и `proxy_off` больше не делают автоматический запрос внешнего IP.
|
|
||||||
- [x] Автоматические тесты подтверждают прежний внешний Hiddify-сценарий,
|
|
||||||
fail-fast, оба регистра переменных, настраиваемый `NO_PROXY`, отсутствие
|
|
||||||
протечки окружения, обычное и самостоятельное подключение Bash-модуля,
|
|
||||||
безопасный отказ для `type=xray` и атомарный разбор полного вывода `env`.
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# 02 — Управляемый Xray: полный штатный путь
|
|
||||||
|
|
||||||
**What to build:** Пользователь с уже установленным поддерживаемым Xray кладёт
|
|
||||||
одну VLESS-ссылку в закрытый файл и получает полный штатный сценарий локального
|
|
||||||
прокси: проверку профиля, запуск на переднем плане, диагностику, запуск команд
|
|
||||||
через прокси и штатную остановку.
|
|
||||||
|
|
||||||
**Blocked by:** 01 — Внешний прокси и переносимый Bash-интерфейс.
|
|
||||||
|
|
||||||
**Status:** done
|
|
||||||
|
|
||||||
Часть требований пересмотрена в 06 — см. 06-simplification-pass.md.
|
|
||||||
|
|
||||||
- [x] `start` разрешён только при явном типе `xray`, требует loopback-адрес и
|
|
||||||
согласованный локальный порт, никогда не скачивает бинарник и выбирает первый
|
|
||||||
`xray` из `PATH`. Один разрешённый путь используется для `version`,
|
|
||||||
`run -test` и основного процесса и показывается перед запуском.
|
|
||||||
- [x] Успешный `xray version` разбирается только по первой строке в форме
|
|
||||||
`Xray VERSION` с концом строки либо ASCII-пробелом после полного токена
|
|
||||||
версии. Принимаются только `25.12.8` и `26.3.27`; ведущий мусор, похожий
|
|
||||||
префикс вроде `26.3.270`, пустой, неверный или завершившийся ошибкой вывод
|
|
||||||
запрещает преобразование и запуск. Запрошенная или указанная в профиле версия
|
|
||||||
не подменяет фактическую версию выбранного бинарника.
|
|
||||||
- [x] Профиль берётся из предусмотренного файла по умолчанию либо из явно
|
|
||||||
переданного `-f`, содержит ровно одну VLESS-ссылку, а конечный файл после
|
|
||||||
разрешения симлинка недоступен группе и остальным пользователям.
|
|
||||||
- [x] Строгий преобразователь поддерживает TCP/RAW с REALITY и XHTTP с TLS,
|
|
||||||
сохраняет все предусмотренные спецификацией поля, отклоняет повторы,
|
|
||||||
любой неизвестный query-параметр, отсутствующие обязательные поля и
|
|
||||||
неподдерживаемые сочетания до запуска Xray.
|
|
||||||
- [x] Для Xray 26.3.27 корректно обрабатываются `pcs`/`vcn`, а устаревшие
|
|
||||||
`allowInsecure`/`insecure` не переносятся и дают предупреждение; для явно
|
|
||||||
выбранного Xray 25.12.8 сохраняется совместимое insecure-поведение, а новые
|
|
||||||
поля отклоняются без автоматического отката версии.
|
|
||||||
- [x] `allowInsecure` и `insecure` принимают только точные значения `true` или
|
|
||||||
`false`; при одновременном присутствии значения должны совпадать. Для
|
|
||||||
25.12.8 только согласованное истинное значение включает `allowInsecure` с
|
|
||||||
предупреждением, а для 26.3.27 поле никогда не попадает в JSON и истинное
|
|
||||||
значение предупреждает об обычной проверке сертификата.
|
|
||||||
- [x] Созданный клиентский JSON слушает только loopback, предоставляет HTTP и
|
|
||||||
SOCKS на одном порту, отключает журнал доступа и проходит `xray run -test`
|
|
||||||
до запуска основного процесса.
|
|
||||||
- [x] `start` остаётся на переднем плане как супервизор одного дочернего Xray;
|
|
||||||
после запуска процесс жив и порт принимает соединения, а сбой отдельной
|
|
||||||
проверки внешнего IP остаётся предупреждением.
|
|
||||||
- [x] Штатный `start` создаёт закрытое состояние с PID супервизора и дочернего
|
|
||||||
Xray, разрешённым путём к бинарнику, портом и безопасным отпечатком профиля.
|
|
||||||
`status`, `env`, `run` и `stop` требуют это состояние, живой супервизор,
|
|
||||||
ожидаемый дочерний процесс и настроенный порт; один чужой listener или PID
|
|
||||||
никогда не считается управляемым Xray и не затрагивается.
|
|
||||||
- [x] `status`, `env`, `run` и `stop` обеспечивают штатный путь управляемого
|
|
||||||
Xray; `run` никогда не запускает его скрыто, а `stop` может завершить
|
|
||||||
управляемый процесс после переключения машинного типа на `external`.
|
|
||||||
Успешный `stop` посылает сигнал супервизору, ограниченно ждёт завершения
|
|
||||||
супервизора и Xray, очищает штатное состояние и возвращает исходный
|
|
||||||
foreground-вызов `start` в терминал.
|
|
||||||
- [x] Диагностика внешнего IP действительно делает запрос через настроенный
|
|
||||||
локальный HTTP-прокси. Её сбой явно показан как предупреждение и не отменяет
|
|
||||||
уже успешные проверки процесса и порта.
|
|
||||||
- [x] Безопасная сводка показывает только полезные несекретные свойства
|
|
||||||
профиля; XHTTP `path` показывается только как скрытое значение с длиной, а
|
|
||||||
UUID, ключи, short ID и другие секреты — без фрагментов исходного значения.
|
|
||||||
Полная VLESS-ссылка и уникальные секреты отсутствуют в stdout, stderr и argv
|
|
||||||
дочерних процессов при успехе, любом отказе разбора и сбое `run -test`.
|
|
||||||
- [x] Модульные и интеграционные тесты покрывают обе семьи ссылок, варианты
|
|
||||||
ALPN, типичную ссылку Hiddify XHTTP/TLS, обе поддержанные версии Xray и
|
|
||||||
понятные отказы на неподдерживаемом вводе.
|
|
||||||
- [x] Чёрный ящик для каждой поддержанной версии доказывает полный порядок:
|
|
||||||
профиль по умолчанию и `-f`, `version`, успешный `run -test` до основного
|
|
||||||
запуска, живое управляемое состояние и порт, `status`, `env`, дочернюю
|
|
||||||
команду через `run`, проксированную IP-диагностику, `stop` и ограниченное
|
|
||||||
завершение исходного `start`. Через один настроенный локальный порт реально
|
|
||||||
завершаются отдельный HTTP-proxy-запрос и отдельный SOCKS-proxy-запрос.
|
|
||||||
Подмена порта или состояния чужим процессом даёт безопасный отказ.
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
# 03 — Безопасный жизненный цикл управляемого процесса
|
|
||||||
|
|
||||||
**What to build:** Управляемый Xray предсказуемо переживает повторные команды,
|
|
||||||
сигналы, устаревшее состояние и аварийное исчезновение супервизора, не
|
|
||||||
затрагивая чужие процессы и не оставляя секретных данных вне закрытого
|
|
||||||
служебного каталога.
|
|
||||||
|
|
||||||
**Blocked by:** 02 — Управляемый Xray: полный штатный путь; 06 — Упрощение.
|
|
||||||
|
|
||||||
**Status:** done
|
|
||||||
|
|
||||||
**Резолюция (2026-07-20):** реализовано и принято. Дельта: CLI 976→1235,
|
|
||||||
тесты 437→521 + 799→1112; сьюты: 49 unittest OK + 8 shell PASS (baseline
|
|
||||||
34/8). Конвейер: spec-ревью постановки (NEEDS_REWORK, 7 MAJOR + 3 MINOR —
|
|
||||||
постановка переписана до реализации) → реализация на medium → саморевью
|
|
||||||
(3 находки, починены) → слепые ревью: TASK 1 MINOR + 2 NIT, CODE 4 MAJOR +
|
|
||||||
1 MINOR (гонки идентичности/очистки) → триаж: FIXED 8, REJECTED 0 → обе
|
|
||||||
перепроверки FIXES_CONFIRMED.
|
|
||||||
|
|
||||||
## Модель идентичности процессов
|
|
||||||
|
|
||||||
Идентичность определяется отдельно для ребёнка-Xray и для супервизора;
|
|
||||||
«обход argv в поисках любого совпадения» (`verify_managed_state`) заменяется
|
|
||||||
этим механизмом.
|
|
||||||
|
|
||||||
- **Общее для обоих:** PID + время старта. Время старта — поле 22
|
|
||||||
(`starttime`) из `/proc/PID/stat`, целое число тиков с момента загрузки;
|
|
||||||
хранится в state как есть и сравнивается на точное равенство. Парсер
|
|
||||||
обязан сначала изолировать поле `comm` по последней `)` (comm может
|
|
||||||
содержать пробелы и скобки) и только потом индексировать поля; любая
|
|
||||||
ошибка чтения/разбора трактуется как несовпадение (fail-closed).
|
|
||||||
- **Ребёнок Xray:** канонический путь `/proc/PID/exe` (realpath) совпадает
|
|
||||||
с сохранённым `xray_path`.
|
|
||||||
- **Супервизор:** его exe — интерпретатор Python, поэтому проверяется
|
|
||||||
иначе: в state сохраняется канонический путь скрипта CLI
|
|
||||||
(`supervisor_path`, realpath на момент старта); при проверке операнд
|
|
||||||
скрипта из `/proc/PID/cmdline` резолвится (относительные пути — через
|
|
||||||
`/proc/PID/cwd`) и сравнивается с `supervisor_path`. Поддерживаемые формы
|
|
||||||
запуска (относительный путь, PATH, симлинк) не ломаются — сравнение
|
|
||||||
идёт по realpath.
|
|
||||||
- **Границы загрузки:** в state сохраняется `boot_id`
|
|
||||||
(`/proc/sys/kernel/random/boot_id`). Несовпадение с текущим означает
|
|
||||||
устаревшее состояние: записанные PID трактуются как мёртвые, сигналы не
|
|
||||||
посылаются.
|
|
||||||
- **Гонка check-then-kill:** непосредственно перед каждым `os.kill`
|
|
||||||
идентичность перечитывается заново; несовпадение — отказ без сигнала.
|
|
||||||
Остаточная гонка «процесс умер между перечиткой и kill» принята как риск
|
|
||||||
модели доверенных пользователей (pidfd не используем: Python 3.8+,
|
|
||||||
переносимость).
|
|
||||||
|
|
||||||
## Чекбоксы
|
|
||||||
|
|
||||||
- [x] Взаимное исключение: супервизор на всё время жизни держит
|
|
||||||
неблокирующий эксклюзивный `fcntl.flock` на lock-файле в служебном
|
|
||||||
каталоге. Второй одновременный `start` завершается понятным отказом, не
|
|
||||||
запуская процессов и не трогая чужие state/конфиг. `state.json` остаётся
|
|
||||||
данными для восстановления после сбоя, а не механизмом блокировки.
|
|
||||||
Последовательные повторный `start` и смена профиля при живом экземпляре
|
|
||||||
по-прежнему отвергаются.
|
|
||||||
- [x] Схема state (права 0600, каталоги 0700) — ровно эти ключи:
|
|
||||||
`supervisor_pid`, `xray_pid`, `xray_path`, `config_path`, `address`,
|
|
||||||
`port` (существующие) + `supervisor_path`, `supervisor_starttime`,
|
|
||||||
`xray_starttime`, `boot_id` (новые). Ссылка VLESS и её секреты в state
|
|
||||||
не попадают.
|
|
||||||
- [x] `status` и `stop` применяют модель идентичности выше; чужой,
|
|
||||||
устаревший или переиспользованный PID не признаётся управляемым
|
|
||||||
процессом (в пределах принятой остаточной гонки).
|
|
||||||
- [x] Три исхода проверки состояния, у каждого — своё поведение:
|
|
||||||
1. **Здоровая пара** (супервизор и ребёнок живы, идентичность
|
|
||||||
подтверждена): `stop` сигналит супервизору, как сейчас.
|
|
||||||
2. **Сирота** (супервизор исчез, ребёнок жив и подтверждён по
|
|
||||||
PID + starttime + boot_id + exe): `stop` сигналит ребёнку напрямую и
|
|
||||||
удаляет state и сгенерированный конфиг; доступность порта для этого
|
|
||||||
НЕ требуется. `status` — отдельная ненулевая диагностика «сирота»;
|
|
||||||
`env` и `run` — отказ.
|
|
||||||
3. **Непроверяемое/устаревшее состояние:** если записанный PID жив, но
|
|
||||||
идентичность не подтверждена — никаких сигналов, state сохраняется,
|
|
||||||
диагностика с инструкцией ручного удаления; если записанные PID
|
|
||||||
мертвы (или boot_id не совпал) — безопасная очистка устаревших
|
|
||||||
файлов разрешена.
|
|
||||||
- [x] Конфликт локального порта: `start` при уже занятом порте завершается
|
|
||||||
ненулевым кодом, диагностика называет порт занятым/недоступным, чужой
|
|
||||||
слушатель не затронут, свои сгенерированные файлы убраны. Инспекция
|
|
||||||
владения слушателем (/proc/net/tcp) запрещена.
|
|
||||||
- [x] Секреты: полная VLESS-ссылка никуда не персистится; UUID, ключи,
|
|
||||||
секретоподобные значения отсутствуют в stdout, stderr, argv,
|
|
||||||
`state.json` и lock-файле. Единственное допустимое место — `client.json`
|
|
||||||
с правами 0600 внутри каталога 0700; он удаляется при штатном завершении
|
|
||||||
и при `stop` сироты.
|
|
||||||
- [x] Автоматические тесты (без root, на обычной dev-машине):
|
|
||||||
- блокировка: удержанный извне flock → `start` отказывает, ничего не
|
|
||||||
запустив и не изменив;
|
|
||||||
- переиспользованный PID моделируется правкой сохранённого `starttime`
|
|
||||||
у живого процесса (реальную гонку PID не воспроизводим); отдельно —
|
|
||||||
мёртвый PID, неверный исполняемый файл, несовпавший `boot_id`;
|
|
||||||
- SIGKILL супервизора → `stop` добивает подтверждённого сироту и чистит
|
|
||||||
state/конфиг;
|
|
||||||
- занятый порт (заранее открытый сокет) → контракт из чекбокса выше;
|
|
||||||
- юнит-тест парсера `/proc/PID/stat` на синтетических `comm` с
|
|
||||||
пробелами и `)`;
|
|
||||||
- фикстура фейкового Xray адаптирована под проверку `/proc/PID/exe`
|
|
||||||
(ребёнок должен быть настоящим исполняемым файлом — например,
|
|
||||||
`sys.executable` как «бинарник xray» со скриптами-операндами; детали
|
|
||||||
на усмотрение исполнителя);
|
|
||||||
- все сохраняемые ниже поведения остаются зелёными.
|
|
||||||
|
|
||||||
## Сохранить, не переписывать
|
|
||||||
|
|
||||||
Уже реализовано и покрыто тестами в 02/06 — не переделывать, только не
|
|
||||||
сломать: проброс SIGINT/SIGTERM/SIGHUP ребёнку; очистка state и конфига при
|
|
||||||
штатном завершении; права 0600/0700; последовательные отказы (повторный
|
|
||||||
start, смена профиля, несовпадение машинного адреса/порта со state);
|
|
||||||
loopback-only; секреты вне stdout/stderr/argv; foreground-супервизор;
|
|
||||||
fail-fast `pclaude`/`pcodex`; внешний путь без машинного конфига.
|
|
||||||
|
|
||||||
Запрещено восстанавливать срезанное в 06: владение слушателем через
|
|
||||||
`/proc/net/tcp`, `profile_fingerprint`, инспекцию zombie-состояний,
|
|
||||||
SIGALRM-машинерию, поддержку второй версии Xray.
|
|
||||||
|
|
||||||
## Не трогать
|
|
||||||
|
|
||||||
- `bash/.bash_functions`, установщик, README — вне объёма.
|
|
||||||
- Никаких коммитов.
|
|
||||||
|
|
||||||
## Отчёт исполнителя
|
|
||||||
|
|
||||||
1. Изменённые файлы со строкой «зачем» на каждый.
|
|
||||||
2. Команды тестов и счётчики (`python3 -m unittest discover -s tests`,
|
|
||||||
`bash tests/test_proxy_launchers.sh`; baseline: 34 OK / 8 PASS — новых
|
|
||||||
станет больше, указать числа).
|
|
||||||
3. Чекбоксы: закрыт / не закрыт и почему.
|
|
||||||
4. Принятые по ходу решения и сомнения, если есть.
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
# 04 — Проверяемая пользовательская установка Xray
|
|
||||||
|
|
||||||
**What to build:** Пользователь без root устанавливает официальный Xray
|
|
||||||
одной явной командой `install` и получает проверенный бинарник
|
|
||||||
зафиксированной версии, без скрытой замены существующей установки, без
|
|
||||||
записи за пределами пользовательских каталогов и без риска для живого
|
|
||||||
managed-экземпляра.
|
|
||||||
|
|
||||||
**Prerequisites:** 01, 03, 06 — все done. Наследуемые решения: одна
|
|
||||||
поддержанная версия Xray 26.3.27 (06); идентичность managed-процессов по
|
|
||||||
`/proc/PID/exe` и start-lock (03); Python 3.8+ stdlib-only, переносимость
|
|
||||||
Ubuntu / РЕД ОС / Astra / Alt.
|
|
||||||
|
|
||||||
**Status:** done
|
|
||||||
|
|
||||||
## Зафиксированный протокол установки
|
|
||||||
|
|
||||||
- **Только версия 26.3.27** — та же, что принимает `VERSION_LINE`.
|
|
||||||
Никакого параметра версии: будущий апгрейд — отдельный тикет, меняющий
|
|
||||||
пин скачивания, ворота рантайма, фикстуры и документацию вместе.
|
|
||||||
- **Источник:** `https://github.com/XTLS/Xray-core/releases/download/v26.3.27/`.
|
|
||||||
Маппинг архитектур (чистая функция, по `platform.machine()`):
|
|
||||||
`x86_64`/`amd64` → `Xray-linux-64.zip`; `aarch64`/`arm64` →
|
|
||||||
`Xray-linux-arm64-v8a.zip`; прочее — понятный отказ.
|
|
||||||
- **Проверка:** скачивается `<архив>.dgst`. Фактический формат официальных
|
|
||||||
файлов — четыре записи `MD5=`, `SHA1=`, `SHA2-256=`, `SHA2-512=`
|
|
||||||
(исправлено 2026-07-20 по находке CODE-ревью: однострочного `SHA256=`
|
|
||||||
в релизах нет). Принимается ровно одна запись `SHA2-256= <64 hex в
|
|
||||||
нижнем регистре>`; дубликаты, uppercase, отсутствие записи или мусор —
|
|
||||||
отказ. Хэш архива считается `hashlib.sha256`; несовпадение или
|
|
||||||
неожиданный формат — отказ до распаковки, установленный бинарник не
|
|
||||||
тронут.
|
|
||||||
- **Границы размеров:** скачивание и распаковка ограничены
|
|
||||||
зафиксированными лимитами (архив, `.dgst`, извлекаемый бинарник);
|
|
||||||
фактически прочитанные байты считаются с limit+1; несоответствие
|
|
||||||
объявленному `Content-Length` или превышение лимита — отказ без
|
|
||||||
исчерпания диска/памяти.
|
|
||||||
- **Инструменты:** `urllib.request` для скачивания, `zipfile` для
|
|
||||||
извлечения члена `xray`. Явно исключены: tar, GPG-подписи, curl, wget,
|
|
||||||
пакетные менеджеры, sudo, systemd, системные каталоги.
|
|
||||||
- **Атомарность:** приватный staging-каталог создаётся ВНУТРИ
|
|
||||||
`~/.local/bin` (одна файловая система — `os.replace` без EXDEV);
|
|
||||||
скачивание и распаковка туда; кандидату ставится режим 0755; кандидат
|
|
||||||
проходит проверку `version` (те же ворота 26.3.27); затем один
|
|
||||||
`os.replace` в `~/.local/bin/xray`. До этого вызова существующий файл
|
|
||||||
назначения не изменяется. Staging убирается при любой ошибке и
|
|
||||||
прерывании (best-effort); fsync/power-loss-гарантии не требуются.
|
|
||||||
|
|
||||||
## Чекбоксы
|
|
||||||
|
|
||||||
- [x] Команда `install [--replace]` добавлена в парсер и диспетчеризуется
|
|
||||||
ДО `load_config()`: установка не требует машинного конфига и
|
|
||||||
VLESS-профиля. `start` и запуск без подкоманды по-прежнему никогда не
|
|
||||||
начинают скачивание.
|
|
||||||
- [x] Защита managed-режима: до любого скачивания и изменения назначения
|
|
||||||
`install` берёт существующий start-lock и держит его до конца
|
|
||||||
`os.replace`. Lock занят — немедленный отказ с подсказкой «сначала
|
|
||||||
stop». При взятом lock допускаются только отсутствующий state или
|
|
||||||
доказуемо устаревший (по классификации 03 — stale); живой, сиротский
|
|
||||||
или непроверяемый state — отказ.
|
|
||||||
- [x] Поведение при существующем назначении `~/.local/bin/xray`:
|
|
||||||
- отсутствует → установка;
|
|
||||||
- обычный исполняемый файл, отвечающий `26.3.27` → no-op без сети
|
|
||||||
(успех, сообщение «уже установлено»);
|
|
||||||
- `--replace` → принудительная переустановка с полной проверкой;
|
|
||||||
- иной файл (не отвечает 26.3.27, неисполняемый, битый) или симлинк →
|
|
||||||
отказ без `--replace`; с `--replace` заменяется сам путь, симлинк не
|
|
||||||
разыменовывается;
|
|
||||||
- каталог → всегда понятный отказ.
|
|
||||||
- [x] После установки: проверка `shutil.which("xray")` — резолвится ли в
|
|
||||||
установленный `~/.local/bin/xray`. Нет — ненулевой код с подсказкой
|
|
||||||
добавить `~/.local/bin` в начало `PATH`; готовая установка при этом НЕ
|
|
||||||
откатывается.
|
|
||||||
- [x] Тестовый шов: чистая функция «архитектура → имя ассета» и
|
|
||||||
подменяемый загрузчик (callable), который тесты заменяют in-process.
|
|
||||||
Пользовательского оверрайда базового URL нет.
|
|
||||||
- [x] Автоматические тесты (без root, без сети; временные HOME/PATH/state):
|
|
||||||
- маппинг обеих архитектур (patch значения архитектуры) и отказ на
|
|
||||||
неподдержанной;
|
|
||||||
- корректный `.dgst` → установка; повреждённый хэш и неожиданный формат
|
|
||||||
`.dgst` → отказ до распаковки, назначение не тронуто;
|
|
||||||
- фикстуры: локальный ZIP с фейковым исполняемым, печатающим
|
|
||||||
`Xray 26.3.27 ...`, и парные валидный/битый `.dgst`;
|
|
||||||
- существующая корректная установка → no-op без обращения к загрузчику;
|
|
||||||
`--replace` → переустановка; симлинк/каталог/битый файл — по таблице
|
|
||||||
выше;
|
|
||||||
- атомарность: инъецированная ошибка после скачивания / при распаковке /
|
|
||||||
при проверке `version` → назначение не изменено, staging убран;
|
|
||||||
- запись только в пределах staging/назначения и lock-файла: сверка
|
|
||||||
полного дерева временной ФС до/после;
|
|
||||||
- защита managed: при живом managed-экземпляре (реальная фикстура 03)
|
|
||||||
`install --replace` отказывает с подсказкой «сначала stop», бинарник
|
|
||||||
и state не тронуты;
|
|
||||||
- `shutil.which`-проверка: отсутствие каталога в PATH и конкурирующий
|
|
||||||
более ранний `xray` в PATH.
|
|
||||||
- [x] `proxy-client/README.md` обновлён: одна поддержанная версия 26.3.27
|
|
||||||
(упоминания 25.12.8 удалены), использование `install [--replace]`,
|
|
||||||
маппинг x86_64/aarch64, SHA-256-проверка, поведение PATH, отказ при
|
|
||||||
живом managed-экземпляре.
|
|
||||||
|
|
||||||
## Сохранить, не трогать
|
|
||||||
|
|
||||||
- Существующее поведение `resolve_xray`, ворот `VERSION_LINE`,
|
|
||||||
managed-режима и всех 49 зелёных тестов + 8 shell-проверок.
|
|
||||||
- Секреты: install не касается профилей и не выводит их; общее правило
|
|
||||||
«VLESS/UUID/ключи никогда в stdout/stderr/argv» остаётся.
|
|
||||||
- `bash/.bash_functions`, установщик dotfiles — вне объёма.
|
|
||||||
- Никаких коммитов.
|
|
||||||
|
|
||||||
## Отчёт исполнителя
|
|
||||||
|
|
||||||
1. Изменённые файлы со строкой «зачем» на каждый.
|
|
||||||
2. Команды тестов и счётчики (baseline: 49 OK / 8 PASS — новых станет
|
|
||||||
больше, указать числа).
|
|
||||||
3. Чекбоксы: закрыт / не закрыт и почему.
|
|
||||||
4. Принятые по ходу решения и сомнения, если есть.
|
|
||||||
|
|
||||||
## Резолюция (2026-07-20)
|
|
||||||
|
|
||||||
Конвейер пройден целиком: постановка → Codex spec-review (high, 5 MAJOR +
|
|
||||||
3 MINOR + 1 NIT, переписана) → реализация (Codex, medium — эксперимент) →
|
|
||||||
саморевью (found 6, fixed 6) → два слепых ревью (CODE: 2 MAJOR + 2 MINOR;
|
|
||||||
TASK: 3 NIT) → триаж (FIXED 7, REJECTED 0) → узкие перепроверки
|
|
||||||
(оба FIXES_CONFIRMED) → приёмка оркестратором.
|
|
||||||
|
|
||||||
Ключевое:
|
|
||||||
|
|
||||||
- Команда `install [--replace]`: пин 26.3.27, маппинг архитектур чистой
|
|
||||||
функцией, `urllib.request` + `zipfile`, staging внутри `~/.local/bin`,
|
|
||||||
один `os.replace`, `version`-ворота кандидата, guard по start-lock.
|
|
||||||
- CODE-1 (MAJOR) оказался дефектом постановки: spec-review зафиксировал
|
|
||||||
несуществующий однострочный формат `SHA256=`; реальные `.dgst` — четыре
|
|
||||||
записи. Протокол в тикете исправлен (см. выше), парсер строгий: ровно
|
|
||||||
одна `SHA2-256= <64 hex в нижнем регистре>`.
|
|
||||||
- Границы размеров: 64 MiB архив/бинарник, 4 KiB `.dgst`, счёт limit+1,
|
|
||||||
сверка Content-Length; offline-покрытие реального `download_file` через
|
|
||||||
подмену `urllib.request.urlopen`.
|
|
||||||
- `StartLockBusyError` вместо парсинга текста ошибки; снапшоты дерева ФС
|
|
||||||
в тестах отказных путей; PATH-тест конкурирующего бинарника.
|
|
||||||
- README дополнён (по просьбе пользователя): последняя версия с
|
|
||||||
`allowInsecure` — 25.12.8, опция удалена в 26.2.6; CLI их не ставит.
|
|
||||||
|
|
||||||
Тесты: 79 OK (baseline 49, +30) + 8 PASS shell-проверок. Затраты — в
|
|
||||||
`.scratch/proxy-client/token-ledger.md`.
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
# 05 — Доставка пакета и итоговая приёмка
|
|
||||||
|
|
||||||
**What to build:** Поставка `proxy-client` проверена целиком: корневой обзор
|
|
||||||
соответствует готовым возможностям, автоматические проверки воспроизводимы, а
|
|
||||||
ручная приёмка на доступных системах подготовлена для пользователя.
|
|
||||||
|
|
||||||
**Blocked by:** 03 — безопасный жизненный цикл; 04 — проверяемая установка.
|
|
||||||
|
|
||||||
**Status:** ready-for-human
|
|
||||||
|
|
||||||
## Границы задачи
|
|
||||||
|
|
||||||
Тикеты 03/04/06 уже доставили CLI, Bash-модуль, установку и жизненный цикл.
|
|
||||||
Задача 05 их проверяет, но не переписывает. Источники истины:
|
|
||||||
|
|
||||||
- [`proxy-client/README.md`](../../../proxy-client/README.md) — пользовательские
|
|
||||||
команды, настройка и ограничения;
|
|
||||||
- [исходный дизайн](../../../docs/specs/2026-07-15-proxy-client-design.md)
|
|
||||||
— границы и принятые решения.
|
|
||||||
|
|
||||||
- [x] Корневой `README.md` кратко описывает внешний Hiddify и управляемый Xray,
|
|
||||||
показывает `stow proxy-client` и ссылается на полную инструкцию.
|
|
||||||
- [x] Пакет подключается через stow, ручные ссылки или копирование; общий
|
|
||||||
установщик использует тот же пакет.
|
|
||||||
- [x] Инструкция покрывает две семьи VLESS, Xray 26.3.27, отсутствие systemd,
|
|
||||||
автозапуска и автоматического перехода между прокси.
|
|
||||||
- [x] Тест
|
|
||||||
`ManagedXrayCliTest.test_exact_boolean_version_alpn_and_extra_branches`
|
|
||||||
подтверждает, что устаревшие `allowInsecure`/`insecure` не попадают в
|
|
||||||
TLS-конфиг, а истинное значение даёт предупреждение.
|
|
||||||
|
|
||||||
## Секретные данные
|
|
||||||
|
|
||||||
- [x] В отслеживаемых файлах, тестовых данных, аргументах, выводе и отчёте нет
|
|
||||||
настоящих VLESS-ссылок, UUID, ключей, `shortId` и `path`. Автоматические тесты
|
|
||||||
используют только вымышленные контрольные значения. Настоящий профиль и
|
|
||||||
сгенерированный JSON остаются в закрытых пользовательских файлах и не
|
|
||||||
копируются в отчёт.
|
|
||||||
|
|
||||||
## Автоматическая проверка
|
|
||||||
|
|
||||||
- [x] `tests/test_proxy_client_full.sh` запускает весь Python-набор под системным
|
|
||||||
`python3` и CPython 3.8 через `uv`, затем Bash-тесты и проверку установки.
|
|
||||||
- [x] Тестовый Xray остаётся отдельным исполняемым файлом и получает доступ к
|
|
||||||
библиотекам исходного Python. Это устраняет ложные падения переносимой сборки
|
|
||||||
`uv`, не ослабляя проверки личности процесса.
|
|
||||||
- [x] `tests/test_install.sh` собирает небольшие тестовые деревья только из
|
|
||||||
отслеживаемых Git-файлов и удаляет общий временный каталог при выходе.
|
|
||||||
|
|
||||||
## Приёмка пользователем
|
|
||||||
|
|
||||||
Исполнитель готовит команды и останавливается. Результат отмечает пользователь;
|
|
||||||
недоступная система остаётся неподтверждённой.
|
|
||||||
|
|
||||||
- [x] Ubuntu 26.04 с системным Python 3.14: установка, жизненный цикл,
|
|
||||||
`xray run -test`, HTTP и SOCKS через один локальный вход Xray 26.3.27.
|
|
||||||
Минимальный CPython 3.8 отдельно покрыт автоматическим прогоном через `uv`.
|
|
||||||
- [ ] РЕД ОС, Astra Linux или Alt Linux по доступности: тот же сценарий после
|
|
||||||
проверки фактической версии Python.
|
|
||||||
|
|
||||||
## После приёмки
|
|
||||||
|
|
||||||
- [ ] Статус исходного дизайна и заявления о поддержке приведены в соответствие
|
|
||||||
только с фактически выполненными прогонами.
|
|
||||||
|
|
||||||
## Comments
|
|
||||||
|
|
||||||
### 2026-07-21 — автоматическая проверка
|
|
||||||
|
|
||||||
`bash tests/test_proxy_client_full.sh` завершился с кодом 0:
|
|
||||||
|
|
||||||
- системный Python 3.14: 79 тестов, `OK`, 61,034 с;
|
|
||||||
- Bash-интеграция: 8 проверок, `PASS`; `shellcheck` отсутствует и явно пропущен;
|
|
||||||
- установка: `All install tests passed`; в `/tmp` не осталось каталогов
|
|
||||||
`dotfiles-install-test.*`;
|
|
||||||
- CPython 3.8 через `uv`: 79 тестов, `OK`, 52,446 с.
|
|
||||||
|
|
||||||
### 2026-07-21 — команды ручной приёмки
|
|
||||||
|
|
||||||
В первой панели tmux, после подготовки закрытого `default.url` и машинного
|
|
||||||
конфига с `type = xray`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 --version
|
|
||||||
cd ~/dotfiles
|
|
||||||
stow proxy-client
|
|
||||||
source ~/.config/proxy-client/shell.bash
|
|
||||||
proxy-client install
|
|
||||||
proxy-client start
|
|
||||||
```
|
|
||||||
|
|
||||||
Во второй панели, пока `start` работает:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
proxy-client status
|
|
||||||
xray run -test -c "${XDG_STATE_HOME:-$HOME/.local/state}/proxy-client/client.json"
|
|
||||||
curl --proxy http://127.0.0.1:12334 https://api.ipify.org
|
|
||||||
curl --proxy socks5h://127.0.0.1:12334 https://api.ipify.org
|
|
||||||
proxy-client stop
|
|
||||||
```
|
|
||||||
|
|
||||||
Если настроен другой локальный адрес или порт, те же значения подставляются в
|
|
||||||
обе команды `curl`. В отчёт записываются только версии, коды возврата и итог
|
|
||||||
проверки HTTP/SOCKS.
|
|
||||||
|
|
||||||
### 2026-07-21 — переименование и частичная ручная приёмка
|
|
||||||
|
|
||||||
Публичный интерфейс переименован в `proxy-client`, а машинный конфиг и закрытый
|
|
||||||
профиль объединены в `~/.config/proxy-client/`. В ходе разработки текущий HOME
|
|
||||||
приведён к новой структуре; старые пользовательские пути отсутствуют, права
|
|
||||||
файлов `600` сохранены. Поддержка прежнего имени в установщик не добавлялась,
|
|
||||||
поскольку выпуск ещё не состоялся.
|
|
||||||
|
|
||||||
Xray 26.3.27 установился и запустился, `status` подтвердил процесс на
|
|
||||||
`127.0.0.1:12334`, а `xray run -test` завершился сообщением
|
|
||||||
`Configuration OK`. HTTP и SOCKS5h через локальный вход не прошли: оба запроса
|
|
||||||
`curl` завершились кодом 35 с неожиданным закрытием TLS-соединения во время
|
|
||||||
рукопожатия. После проверки Xray штатно остановлен. Пункты ручной приёмки пока
|
|
||||||
не отмечены.
|
|
||||||
|
|
||||||
### 2026-07-21 — успешная приёмка на Ubuntu
|
|
||||||
|
|
||||||
Первая строка оказалась нерабочей и в исходном клиенте. После замены на
|
|
||||||
подтверждённо рабочий профиль XHTTP/TLS с ALPN `h3` тот же Xray 26.3.27 прошёл
|
|
||||||
полный сценарий на Ubuntu 26.04 с системным Python 3.14: встроенная проверка
|
|
||||||
внешнего IP, `status`, `xray run -test`, HTTP CONNECT и SOCKS5h. Оба запроса
|
|
||||||
`curl` получили HTTP 200. Содержимое профиля в журнал и Git не попадало.
|
|
||||||
|
|
||||||
Диагностическое понижение до Xray 25.12.8 не исправляло старый профиль, поэтому
|
|
||||||
поддержка второй версии не нужна. Ошибка была связана с прежней строкой или её
|
|
||||||
удалённой стороной, а не с преобразованием `proxy-client`.
|
|
||||||
|
|
||||||
Короткая проверка стабильности H3 на Xray 26.3.27 также прошла:
|
|
||||||
|
|
||||||
- 120 циклов, в каждом новое HTTP CONNECT- и SOCKS5h-соединение: 240 запросов,
|
|
||||||
0 ошибок;
|
|
||||||
- две одновременные загрузки по 10 МБ через HTTP и SOCKS завершились полностью;
|
|
||||||
- после 45 секунд без трафика оба новых запроса получили HTTP 200.
|
|
||||||
|
|
||||||
Таким образом, не воспроизвёлся сбой из
|
|
||||||
[XTLS/Xray-core#3673](https://github.com/XTLS/Xray-core/issues/3673), при котором
|
|
||||||
H3 переставал принимать новые соединения уже после первых запросов. Этот
|
|
||||||
короткий прогон не заменяет длительное наблюдение под обычной нагрузкой.
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
# 06 — Упрощение: срез accidental-сложности спеки и кода
|
|
||||||
|
|
||||||
**What to build:** Один проход упрощения по готовым issue 01–02 и спеке:
|
|
||||||
убрать поддержку второй версии Xray, избыточные защитные проверки вне
|
|
||||||
принятой модели угроз («доверенные пользователи машины», единственный
|
|
||||||
пользователь редактирует свои файлы сам) и церемонии, не нужные личному
|
|
||||||
foreground-инструменту. Поведенческие контракты из раздела «Не трогать»
|
|
||||||
сохраняются без изменений.
|
|
||||||
|
|
||||||
**Blocked by:** 02 — Управляемый Xray: полный штатный путь.
|
|
||||||
|
|
||||||
**Status:** done
|
|
||||||
|
|
||||||
**Резолюция (2026-07-20):** реализовано и принято. Дельта: CLI 1123→976,
|
|
||||||
shell.bash 116→91, тесты 1357→1236 строк; сьюты: 34 unittest OK + 8 shell
|
|
||||||
PASS. Ревью: 1 MAJOR (scoped IPv6 в упрощённом валидаторе — починен с
|
|
||||||
регрессионным тестом под Python 3.8), 1 NIT отклонён аргументированно.
|
|
||||||
Non-gating замечания ревью приняты как риски модели угроз.
|
|
||||||
|
|
||||||
**Сначала прочитать:**
|
|
||||||
|
|
||||||
1. Этот файл целиком, затем `/tmp/xray-simplify-Zeo5/06-simplify/sweep.md` —
|
|
||||||
там точный line-level blast radius каждого пункта (секция 2 — срезы A/B,
|
|
||||||
секция 3 — SIMP-N). Номера строк — ориентир, сверяй с фактическим кодом.
|
|
||||||
2. `docs/specs/2026-07-15-proxy-client-design.md` — исходная спека.
|
|
||||||
3. `proxy-client/.local/bin/proxy-client`, `proxy-client/.config/proxy-client/shell.bash`,
|
|
||||||
`tests/test_proxy_client.py`, `tests/test_proxy_client_managed.py`.
|
|
||||||
|
|
||||||
Команды проверки (обе обязательны):
|
|
||||||
|
|
||||||
- `python3 -m unittest discover -s tests` — baseline до изменений: 38
|
|
||||||
тестов, OK, ~60 с;
|
|
||||||
- `bash tests/test_proxy_launchers.sh` — baseline: 9 проверок PASS.
|
|
||||||
|
|
||||||
После изменений тестов станет меньше (удаляются тесты срезанных фич); все
|
|
||||||
оставшиеся зелёные, новые счётчики указать в отчёте.
|
|
||||||
|
|
||||||
## A. Срез dual-version: остаётся только Xray 26.3.27
|
|
||||||
|
|
||||||
- [x] `VERSION_LINE` принимает только `26.3.27`; параметр `version` убран из
|
|
||||||
сигнатур `parse_vless`, `build_xray_config`, `safe_summary`, `warn_legacy`
|
|
||||||
и их вызовов; `xray_version` остаётся воротами «поддержанная версия или
|
|
||||||
отказ» и по-прежнему показывает фактическую версию в сводке.
|
|
||||||
- [x] Ветка эмиссии `allowInsecure` для 25.12.8 и запрет `pcs`/`vcn` для
|
|
||||||
25.12.8 удалены. `parse_boolean_fields` (валидация точных `true`/`false`,
|
|
||||||
требование согласованности обоих полей, предупреждение при истинном
|
|
||||||
значении) сохраняется; `allowInsecure` никогда не попадает в JSON.
|
|
||||||
- [x] Тестовая ось версий схлопнута: lifecycle-тест гоняет один прогон
|
|
||||||
26.3.27 (обе локации профиля — default и `-f` — сохраняются);
|
|
||||||
`test_old_version_preserves_explicit_legacy_insecure` и все
|
|
||||||
25.12.8-строки табличных тестов удалены.
|
|
||||||
|
|
||||||
## B. Срез проверки владения сокетом через /proc/net/tcp
|
|
||||||
|
|
||||||
- [x] `process_owns_listener`, `process_socket_inodes`, `decode_proc_address`
|
|
||||||
и их вызовы (в `verify_managed_state` и readiness-цикле `command_start`)
|
|
||||||
удалены. Сохраняются: сверка PID/идентичности процесса, сверка
|
|
||||||
адрес/порт машинного конфига со state и `check_endpoint`-проверка
|
|
||||||
доступности порта.
|
|
||||||
- [x] `test_verified_child_must_own_configured_listener` удалён вместе с
|
|
||||||
веткой `FAKE_XRAY_LISTEN_PORT` в фейковом xray;
|
|
||||||
`test_substituted_machine_port_fails_closed...` сохраняется и зелёный.
|
|
||||||
- [x] Диагностические сообщения, упоминающие проверку владения слушателем
|
|
||||||
(в `verify_managed_state` и readiness-цикле `command_start`), обновлены:
|
|
||||||
после среза они описывают фактические проверки (идентичность процесса,
|
|
||||||
сверка адрес/порт, доступность порта), а не владение сокетом.
|
|
||||||
|
|
||||||
## C. Диета входной валидации (файлы пишет сам пользователь)
|
|
||||||
|
|
||||||
- [x] `validate_address`: сначала `ipaddress.ip_address`, иначе минимальная
|
|
||||||
проверка «непустое ASCII-имя без пробелов, схемы `://`, `@`, `/` и
|
|
||||||
квадратных скобок». RFC-строгость по меткам DNS, лимит 253 и
|
|
||||||
сопутствующие тесты удалены. Оборачивание IPv6 в `[]` при сборке URL
|
|
||||||
сохраняется.
|
|
||||||
- [x] `load_config`: сохраняется отказ на неизвестной секции/ключе и на
|
|
||||||
отсутствии обязательного ключа (ловит реальные опечатки) и диапазон
|
|
||||||
порта. Удалены: скан NUL, guard UTF-8-декодирования, отдельный запрет
|
|
||||||
`[DEFAULT]`, запрет CR/многострочных значений, case-sensitive
|
|
||||||
`optionxform`; соответствующие подкейсы тестов удалены.
|
|
||||||
- [x] `read_profile`: сохраняются разрешение симлинка, проверка прав
|
|
||||||
(group/other → ошибка с подсказкой `chmod 600`) и правило «ровно одна
|
|
||||||
непустая строка». Удалены скан NUL и guard UTF-8.
|
|
||||||
- [x] `parse_query`: сохраняются отказ на неизвестном и на повторном
|
|
||||||
query-параметре. Удалены regex на сырые `%`-последовательности и
|
|
||||||
посимвольный запрет управляющих символов.
|
|
||||||
|
|
||||||
## D. Диета церемоний
|
|
||||||
|
|
||||||
- [x] `write_private_json`: простая запись файла с правами 0600 внутри
|
|
||||||
уже-0700 каталога; O_EXCL/fsync/`os.replace`/PID-суффиксы удалены.
|
|
||||||
- [x] Валидация `PROXY_PREFLIGHT_TIMEOUT` и `PROXY_CLIENT_STOP_TIMEOUT` — один
|
|
||||||
guard «`float()` успешен и > 0, иначе понятная ошибка»; `math` не
|
|
||||||
импортируется; тесты экстремальных значений (nan/inf/1e-7/1e308)
|
|
||||||
удалены. Для `PROXY_PREFLIGHT_TIMEOUT` оставить один тест «плохое
|
|
||||||
значение → понятный отказ»; для `PROXY_CLIENT_STOP_TIMEOUT` отдельного
|
|
||||||
теста нет и добавлять его не нужно — достаточно guard'а.
|
|
||||||
- [x] `check_reachable` использует `socket.create_connection(timeout=...)`
|
|
||||||
без SIGALRM/setitimer-машинерии. Принятый риск (записан здесь):
|
|
||||||
зависший DNS-резолв на external-пути не ограничен таймаутом — для
|
|
||||||
личного инструмента пользователь прерывает вручную.
|
|
||||||
- [x] `extra` и `alpn` разбираются один раз в `parse_vless`, результат
|
|
||||||
сохраняется в профиле и переиспользуется в `build_xray_config` без
|
|
||||||
повторного разбора. `parse_json_object` сохраняет оба отказа —
|
|
||||||
на повторах ключей и на JSON-константах (`reject_constant` остаётся):
|
|
||||||
существующий тест требует отказ на уровне разбора с диагностикой про
|
|
||||||
`extra` и без запуска `run -test`, и это дешевле, чем переносить
|
|
||||||
ответственность на сериализацию.
|
|
||||||
- [x] `process_alive` — только `os.kill(pid, 0)`; чтение
|
|
||||||
`/proc/PID/stat` ради zombie-состояния удалено.
|
|
||||||
- [x] Отпечаток профиля (`profile_fingerprint`) удалён из state и кода:
|
|
||||||
он не участвует ни в одном решении. Смена профиля по-прежнему
|
|
||||||
блокируется существованием state. Вместе с ним обновлены `STATE_KEYS` и
|
|
||||||
все тестовые фикстуры state (включая точную сверку набора ключей в
|
|
||||||
managed-тестах): устаревший ключ в фикстуре не должен ронять сверку
|
|
||||||
ключей раньше, чем тест дойдёт до проверяемой идентичности процесса.
|
|
||||||
|
|
||||||
## E. shell.bash: средняя форма приёма `env`
|
|
||||||
|
|
||||||
- [x] `_proxy_apply_env_file`: при ненулевом коде возврата CLI окружение не
|
|
||||||
меняется вовсе; успешный вывод разбирается построчно по первому `=`;
|
|
||||||
применяются (через `printf -v`, без `eval`) только восемь известных
|
|
||||||
ключей, прочие строки — ошибка без частичного экспорта. Удалены сканы
|
|
||||||
NUL/CR, отдельный детектор повторов и проверка «ровно восемь строк».
|
|
||||||
Свойство «нет частичного изменения окружения при ошибке» сохраняется и
|
|
||||||
остаётся покрыто тестом.
|
|
||||||
- [x] `tests/test_proxy_launchers.sh` приведён к средней форме: проверки
|
|
||||||
повторов/CR/NUL/счётчика строк, соответствующие удалённой машинерии,
|
|
||||||
убраны или переписаны; проверки «ненулевой код возврата CLI → окружение
|
|
||||||
не тронуто», «неизвестная строка → ошибка без частичного экспорта» и
|
|
||||||
владения функциями остаются зелёными.
|
|
||||||
|
|
||||||
## F. Спека и соседние issue приведены к новому объёму
|
|
||||||
|
|
||||||
- [x] Из спеки удалены: ветка 25.12.8 (все места из секции 2 sweep-файла),
|
|
||||||
флаги `-v`/`--debug` (не реализованы), упоминание отпечатка профиля в
|
|
||||||
state; описания валидации/атомарности смягчены до фактического
|
|
||||||
поведения после этого issue. Формулировка идентичности процессов
|
|
||||||
(«PID + время старта + исполняемый файл») в спеке не меняется.
|
|
||||||
- [x] Issue 03: добавлен явный мандат «идентичность = PID + время старта
|
|
||||||
(`/proc/PID/stat`) + исполняемый файл ребёнка / путь скрипта
|
|
||||||
супервизора; текущий argv-обход заменить этим механизмом»; убраны
|
|
||||||
унаследованные упоминания срезанных фич, если есть; строка Blocked by
|
|
||||||
дополнена: «06 — Упрощение».
|
|
||||||
- [x] Issue 05: приёмка сведена к одной версии Xray 26.3.27; упоминания
|
|
||||||
25.12.8 удалены. Пункты про РЕД ОС сохранены (решение пользователя
|
|
||||||
2026-07-20: целевые системы шире Ubuntu — РЕД ОС, возможно Astra Linux
|
|
||||||
и Alt Linux; инструмент могут использовать коллеги).
|
|
||||||
- [x] В спеке в раздел ручной проверки добавлены Astra Linux и Alt Linux
|
|
||||||
как кандидаты «по мере доступности» с тем же правилом, что для РЕД ОС:
|
|
||||||
недоступная среда явно записывается как неподтверждённая, поддержка не
|
|
||||||
заявляется без фактической проверки версии Python и прогона.
|
|
||||||
- [x] В issue 01 и 02 добавлена одна строка после Status: «Часть
|
|
||||||
требований пересмотрена в 06 — см. 06-simplification-pass.md»; чекбоксы
|
|
||||||
01 и 02 не редактируются (issue 01 описывает строгую приёмку `env`,
|
|
||||||
которую этот issue смягчает, — без пометки он противоречил бы коду).
|
|
||||||
|
|
||||||
## G. Не трогать (границы)
|
|
||||||
|
|
||||||
- Строгий отказ на неизвестном/повторном query-параметре VLESS.
|
|
||||||
- Точечный `stop` только по проверенному PID; никакого pkill.
|
|
||||||
- Foreground-супервизор, проброс сигналов, очистка state.
|
|
||||||
- Изоляция окружения `run` (proxy-переменные только у ребёнка), сохранение
|
|
||||||
argv и кода возврата, `--`-разделитель.
|
|
||||||
- Fail-fast `pclaude`/`pcodex`, обратная совместимость: без машинного
|
|
||||||
конфига — внешний `hproxy.local:12334`.
|
|
||||||
- Проверка прав файла профиля (chmod 600) и loopback-only вход.
|
|
||||||
- Авто-диагностика внешнего IP на старте (несмертельная) — остаётся.
|
|
||||||
- Секреты: полная VLESS-ссылка, UUID, ключи не появляются в stdout/stderr/
|
|
||||||
argv — все существующие тесты на это остаются зелёными.
|
|
||||||
- `bash/.bash_functions`, установщик, README — не входят в этот issue.
|
|
||||||
- Никаких коммитов.
|
|
||||||
|
|
||||||
## Отчёт исполнителя
|
|
||||||
|
|
||||||
1. Изменённые файлы со строкой «зачем» на каждый.
|
|
||||||
2. Команда тестов и счётчик (все зелёные, указать новое число).
|
|
||||||
3. Чекбоксы: закрыт / не закрыт и почему.
|
|
||||||
4. Итоговая дельта `wc -l` по CLI, shell.bash и обоим тест-файлам
|
|
||||||
против baseline (1123 / 116 / 467 / 890).
|
|
||||||
5. Сомнения, если есть.
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
# Токен-леджер проекта proxy-client
|
|
||||||
|
|
||||||
Затраты по этапам конвейера, для калибровки эффорта и планирования лимитов
|
|
||||||
(5-часовые / недельные окна подписок). Прямое сравнение корректно внутри
|
|
||||||
провайдера; между Claude и Codex метрики разные (у Claude-субагентов
|
|
||||||
харнесс даёт один суммарный счётчик, у Codex — раздельные input/cached/
|
|
||||||
output/reasoning), кросс-сравнение — ориентировочное.
|
|
||||||
|
|
||||||
## Задача 06 — упрощение (baseline: реализация на HIGH)
|
|
||||||
|
|
||||||
| Этап | Агент / модель | Effort | Токены | Прочее |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| KISS-обход спеки и кода | Claude Opus (субагент) | default | 100 025 суммарно | 14 tool uses, 4.5 мин |
|
|
||||||
| Ревью постановки | Codex gpt-5.6-sol | high | in 1 263 557 (кэш 1 174 784 → свежих ~89k), out 16 862 (reasoning 12 817) | 82 команды |
|
|
||||||
| Реализация | Codex gpt-5.6-sol | high | in 6 680 053 (кэш 6 492 928 → свежих ~187k), out 29 064 (reasoning 8 280) | 84 команды, −331 строка |
|
|
||||||
| Саморевью | Codex (resume) | medium | инкремент: свежих in ~155k, out ~4.5k | 8 команд, found 2 fixed 2 |
|
|
||||||
| Слепое ревью «по задаче» | Claude Opus (субагент) | default | 75 216 суммарно | 26 tool uses, 4.3 мин; 1 NIT |
|
|
||||||
| Слепое ревью «по коду» | Codex gpt-5.6-sol (свежий тред) | high | in 2 336 162 (кэш 2 106 112 → свежих ~230k), out 12 434 (reasoning 7 033) | 48 команд; 1 MAJOR |
|
|
||||||
| Триаж/починка | Codex (resume) | medium | инкремент: свежих in ~26k, out ~7.3k | 18 команд; FIXED 1, REJECTED 1 |
|
|
||||||
| Перепроверка CODE-1 | Codex-ревьюер (resume) | medium | инкремент: свежих in ~137k (вкл. оборванный первый заход), out ~1.8k | FIXES_CONFIRMED |
|
|
||||||
|
|
||||||
Итог 06 (high-baseline): свежий input ~735k + output ~97k на весь конвейер
|
|
||||||
(обход, ревью постановки, реализация, саморевью, 2 слепых ревью, триаж,
|
|
||||||
перепроверка); Claude-доля — 175k суммарно (обход 100k + ревью 75k).
|
|
||||||
|
|
||||||
## Задача 03 / 04 (эксперимент: реализация на MEDIUM)
|
|
||||||
|
|
||||||
_Заполняется по ходу; сравнивать построчно с 06._
|
|
||||||
|
|
||||||
| Этап | Агент / модель | Effort | Токены | Прочее |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| 03: ревью постановки | Codex gpt-5.6-sol | high | in 1 126 384 (кэш 1 044 480 → свежих ~82k), out 15 830 (reasoning 9 567) | NEEDS_REWORK: 7 MAJOR + 3 MINOR |
|
|
||||||
| 03: реализация | Codex gpt-5.6-sol | **medium** | in 5 925 714 (кэш 5 678 080 → свежих ~248k), out 29 512 (reasoning 11 251)* | 88+6 команд; +657/−89; 44 OK + 8 PASS; процесс убит рестартом сессии перед отчётом, дописан через resume |
|
|
||||||
|
|
||||||
| 03: саморевью | Codex (resume) | medium | инкремент: свежих in ~53k, out ~11.6k (reasoning ~7k) | 38 команд; found 3 fixed 3; 45 OK + 8 PASS |
|
|
||||||
| 03: слепое ревью «по задаче» | Claude Opus (субагент) | default | 73 131 суммарно | 21 tool uses, 6.3 мин; 1 MINOR + 2 NIT |
|
|
||||||
| 03: слепое ревью «по коду» | Codex gpt-5.6-sol (свежий тред) | high | in 1 103 725 (кэш 1 019 136 → свежих ~85k), out 12 797 (reasoning 8 908) | 28 команд; 4 MAJOR + 1 MINOR |
|
|
||||||
| 03: триаж/починка | Codex (resume) | medium | инкремент: свежих in ~55.5k, out ~16.9k (reasoning ~7.8k) | 46 команд; FIXED 8, REJECTED 0; 49 OK + 8 PASS |
|
|
||||||
| 03: перепроверка TASK-1..3 | Claude Opus (резюм субагента) | default | инкремент ~7.4k (суммарно 80 560) | 8 tool uses, 1.7 мин; FIXES_CONFIRMED |
|
|
||||||
| 03: перепроверка CODE-1..5 | Codex-ревьюер (resume) | medium | инкремент: свежих in ~112k, out ~3.2k | 20 команд; FIXES_CONFIRMED |
|
|
||||||
|
|
||||||
| 04: ревью постановки | Codex gpt-5.6-sol | high | in 2 469 937 (кэш 2 352 896 → свежих ~117k), out 17 051 (reasoning 10 128) | 54 команды; NEEDS_REWORK: 5 MAJOR + 3 MINOR + 1 NIT |
|
|
||||||
| 04: реализация | Codex gpt-5.6-sol | **medium** | in 2 941 682 (кэш 2 828 288 → свежих ~113k), out 18 521 (reasoning 5 278) | 50 команд; +~600 строк; 63 OK (+14) + 8 PASS |
|
|
||||||
| 04: саморевью | Codex (resume) | medium | инкремент: свежих in ~50k, out ~17.2k (reasoning ~9.2k) | 38 команд; found 6 fixed 6; 72 OK + 8 PASS |
|
|
||||||
| 04: слепое ревью «по задаче» | Claude Opus (субагент) | default | 53 463 суммарно | 18 tool uses, 5.7 мин; 3 NIT |
|
|
||||||
| 04: слепое ревью «по коду» | Codex gpt-5.6-sol (свежий тред) | high | in 1 774 005 (кэш 1 676 288 → свежих ~98k), out 10 402 (reasoning 5 481) | 46 команд; 2 MAJOR + 2 MINOR (CODE-1 — дефект постановки: реальный формат .dgst) |
|
|
||||||
| 04: триаж/починка | Codex (resume) | medium | инкремент: свежих in ~237k, out ~58.9k (reasoning ~24k) | FIXED 7, REJECTED 0; 79 OK (+30 к baseline) + 8 PASS |
|
|
||||||
| 04: перепроверка TASK-1..3 | Claude Opus (резюм субагента) | default | инкремент ~7.7k (суммарно 61 135) | 6 tool uses, 1.7 мин; FIXES_CONFIRMED |
|
|
||||||
| 04: перепроверка CODE-1..4 | Codex-ревьюер (resume) | medium | инкремент: свежих in ~207k, out ~12.5k (reasoning ~6.3k) | FIXES_CONFIRMED |
|
|
||||||
|
|
||||||
Итог 03 (реализация на MEDIUM): Codex — свежий input ~636k + output ~77k
|
|
||||||
(ревью постановки 82k/16k, реализация ~248k/29.5k, саморевью 53k/11.6k,
|
|
||||||
CODE-ревью 85k/12.8k, триаж 55.5k/16.9k, перепроверка 112k/3.2k);
|
|
||||||
Claude-доля — 80.5k суммарно (TASK-ревью + перепроверка). Против 06
|
|
||||||
(high-baseline: ~735k + 97k Codex, 175k Claude) — дешевле, но качество
|
|
||||||
ниже: слепые ревью нашли 4 MAJOR (у 06 — 1), все — гонки
|
|
||||||
конкурентности/восстановления; конвейер их выловил и починил.
|
|
||||||
|
|
||||||
\* usage первого (убитого) хода не был зафиксирован (нет `turn.completed`); цифры — из resume-хода, по масштабу покрывают весь тред.
|
|
||||||
|
|
||||||
Итог 04 (реализация на MEDIUM): Codex — свежий input ~822k + output ~135k
|
|
||||||
(ревью постановки 117k/17k, реализация 113k/18.5k, саморевью 50k/17.2k,
|
|
||||||
CODE-ревью 98k/10.4k, триаж 237k/58.9k, перепроверка 207k/12.5k);
|
|
||||||
Claude-доля — 61k суммарно (TASK-ревью + перепроверка). Слепые ревью
|
|
||||||
нашли 2 MAJOR + 2 MINOR + 3 NIT, причём CODE-1 (MAJOR) — дефект
|
|
||||||
постановки (spec-review на high выдумал однострочный `SHA256=`), т.е.
|
|
||||||
на счету реализации-medium фактически 1 MAJOR (границы размеров).
|
|
||||||
Примечательно: 04-medium вышла ДОРОЖЕ 06-high по свежим токенам
|
|
||||||
(~822k+135k против ~735k+97k) — экономию реализации съели дорогой триаж
|
|
||||||
(7 находок, включая переделку под исправленный протокол) и перепроверки.
|
|
||||||
|
|
||||||
Сводка эксперимента (свежий input + output Codex; MAJOR от слепых ревью):
|
|
||||||
|
|
||||||
| Задача | Effort реализации | Codex токены | Claude | MAJOR |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| 06 | high (baseline) | ~735k + 97k | 175k | 1 |
|
|
||||||
| 03 | medium | ~636k + 77k | 80.5k | 4 (гонки) |
|
|
||||||
| 04 | medium | ~822k + 135k | 61k | 2 (1 — вина постановки) |
|
|
||||||
|
|
||||||
## Задача 05 — доставка пакета и приёмка
|
|
||||||
|
|
||||||
| Этап | Агент / модель | Effort | Токены | Прочее |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| 05: ревью постановки, попытки 1–2 (убиты) | Codex gpt-5.6-sol | high | расход токенов потерян (нет `turn.completed`) | обе умерли на `Quota exceeded (os error 122)`: набор `test_install.sh` заполнял `/tmp` тестовыми деревьями по ~1,8 ГБ; потеряно ~2 и ~10 мин работы, сеансы невосстановимы из-за сбоя журнала |
|
|
||||||
|
|
||||||
## Наблюдения
|
|
||||||
|
|
||||||
- Кэш у Codex покрывает ~93% входа ревью-этапа: длинные треды с большим
|
|
||||||
контекстом дешевле по лимитам, чем кажется по сырому input.
|
|
||||||
- Сравнивать этапы честнее по «свежий input + output (включая reasoning)»,
|
|
||||||
а не по валовому input.
|
|
||||||
- Итог эксперимента (решение пользователя 2026-07-21, детали — в
|
|
||||||
claude-subagent-playbook): реализация по отревьюированному тикету →
|
|
||||||
medium допустима, но экономию считать по конвейеру целиком (04-medium
|
|
||||||
дороже 06-high из-за триажа); Claude-субагентам effort задавать явно —
|
|
||||||
xhigh постановки/планы, high слепое ревью (ниже не опускать), medium
|
|
||||||
перепроверки; высокий effort не страхует от выдумки внешних фактов
|
|
||||||
(казус формата `.dgst`); смена effort у Codex при resume кэш не
|
|
||||||
сбрасывает (~93% кэша при high→medium).
|
|
||||||
Reference in new issue
Block a user