diff --git a/.scratch/.gitkeep b/.scratch/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.scratch/handoffs/20260721-0743-proxy-client-05-ready-for-human.md b/.scratch/handoffs/20260721-0743-proxy-client-05-ready-for-human.md deleted file mode 100644 index 5eb67d1..0000000 --- a/.scratch/handoffs/20260721-0743-proxy-client-05-ready-for-human.md +++ /dev/null @@ -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`. diff --git a/.scratch/proxy-client/issues/01-external-proxy-and-portable-shell.md b/.scratch/proxy-client/issues/01-external-proxy-and-portable-shell.md deleted file mode 100644 index a6fd35f..0000000 --- a/.scratch/proxy-client/issues/01-external-proxy-and-portable-shell.md +++ /dev/null @@ -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`. diff --git a/.scratch/proxy-client/issues/02-managed-xray-happy-path.md b/.scratch/proxy-client/issues/02-managed-xray-happy-path.md deleted file mode 100644 index 91b5fb6..0000000 --- a/.scratch/proxy-client/issues/02-managed-xray-happy-path.md +++ /dev/null @@ -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-запрос. - Подмена порта или состояния чужим процессом даёт безопасный отказ. diff --git a/.scratch/proxy-client/issues/03-safe-process-lifecycle.md b/.scratch/proxy-client/issues/03-safe-process-lifecycle.md deleted file mode 100644 index 9433100..0000000 --- a/.scratch/proxy-client/issues/03-safe-process-lifecycle.md +++ /dev/null @@ -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. Принятые по ходу решения и сомнения, если есть. diff --git a/.scratch/proxy-client/issues/04-verified-xray-installation.md b/.scratch/proxy-client/issues/04-verified-xray-installation.md deleted file mode 100644 index 6034a93..0000000 --- a/.scratch/proxy-client/issues/04-verified-xray-installation.md +++ /dev/null @@ -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`. diff --git a/.scratch/proxy-client/issues/05-package-delivery-and-acceptance.md b/.scratch/proxy-client/issues/05-package-delivery-and-acceptance.md deleted file mode 100644 index 207f1cd..0000000 --- a/.scratch/proxy-client/issues/05-package-delivery-and-acceptance.md +++ /dev/null @@ -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 переставал принимать новые соединения уже после первых запросов. Этот -короткий прогон не заменяет длительное наблюдение под обычной нагрузкой. diff --git a/.scratch/proxy-client/issues/06-simplification-pass.md b/.scratch/proxy-client/issues/06-simplification-pass.md deleted file mode 100644 index b13a81d..0000000 --- a/.scratch/proxy-client/issues/06-simplification-pass.md +++ /dev/null @@ -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. Сомнения, если есть. diff --git a/.scratch/proxy-client/token-ledger.md b/.scratch/proxy-client/token-ledger.md deleted file mode 100644 index fd8f688..0000000 --- a/.scratch/proxy-client/token-ledger.md +++ /dev/null @@ -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).