chore(scratch): очищен рабочий каталог перед слиянием

- Зачем:
  - рабочие задачи и handoff не должны попадать в итоговое состояние master.
- Что:
  - удалены временные материалы разработки proxy-client.
  - добавлен .scratch/.gitkeep для сохранения пустого рабочего каталога.
- Проверка:
  - git diff --cached --check.
  - find .scratch -mindepth 1 -print.
This commit is contained in:
ddadmin committed 2026-07-21 11:21:37 -04:00
1 parent 9c2e134fc1
commit fb655f0dc8
9 files changed
-942

No files matched your search

View File
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. Сомнения, если есть.
-99
View File
@@ -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).