Files
HY2XS_flamy/docs/acceptance/2026-09-01-v1.0.0-rc1-host-acceptance.md
founder c0a43ae915 fix(admin): закрыть обещания панели, которые продукт не выполнял
Девятый проход, по итогам приёмки v1.0.0-rc1 на живом Debian 13. Общая тема:
интерфейс обещал оператору то, что продукт умел, но до чего не доходило
управление.

Секрет пира. Подпись под полем предлагала оставить его пустым, сервер умел его
сгенерировать, и генерация была недостижима: в go-playground/validator тег
omitempty НЕ пропускает правило, если поле объявлено указателем и указатель не
nil — hasValue считает указатель на пустую строку «значением». Правило min=6
применялось к пустой строке и отказывало. Ловушка закрыта общим шагом
нормализации DTO, а не тегом на одном поле: та же ловушка ломала фильтр списка
пиров, где очищенный крестиком el-input отправляет `?name=`. Граница проходит по
каждому полю отдельно — у remark пустая строка означает «убрать пометку», у
disabled ноль означает «включён».

Отказы. Любая ошибка любого поля превращалась в слово `invalid`, а слой vo
определял код ответа СРАВНЕНИЕМ текста сообщения — тот же антипаттерн, который
запрещён панели, только на сервере. Ответ несёт errors[{code, field, message,
params}]; панель выбирает фразу по коду и подставляет причины под поля.

Сессия. Ветка «войдите заново» была недостижима дважды: сервер отвечает HTTP 200
на любой отказ, поэтому обработчик ошибок axios не вызывался, а условие в нём
проверяло code === "A0230" и поле msg, которых в этом API никогда не было.
Истёкший токен вдобавок уезжал с кодом системной ошибки.

Иконки. Контракт currentColor был объявлен в двух местах и не действовал: восемь
ассетов несли литеральный fill="#000000" на <path>, а атрибут представления
перебивает унаследованное CSS-свойство. Под это попадали все семь иконок
бокового меню на фоне #181818.

Имя пира. Два правила на одном поле противоречили друг другу (min=1 против
6-32), а копия набора символов в слое контроллеров несла неэкранированный дефис
и впускала `, - . / : ; <` — через панель проходило имя peer/name, которое
импорт того же пира отклонял. Набор символов ЛОГИНА сознательно не сужен и
закреплён тестом: он приходит из HY2XS_ADMIN_USER и оркестратором не
ограничивается.

Добавлены подпись «Разработано во Flamy» с адресом, принадлежащим приложению, и
контрактные тесты панели как обязательный шаг сборки. Их исполняет Bun, а не
vitest: jsdom не вычисляет currentColor и визуальной корректности не доказал бы,
зато vitest привёл бы в граф pnpm audit сотню транзитивных зависимостей.

docs/ разложена по слоям, 11-testing-and-acceptance.md (117 КБ) разбит на пять
частей, добавлен docs/acceptance/ с отчётом о прогоне rc1 и перечнем дефектов.
Обход документации в приёмке стал рекурсивным: плоский docs/*.md после
разнесения по каталогам совпадал бы ровно с одним файлом.
2026-09-01 07:27:15 +05:00

20 KiB
Raw Permalink Blame History

HY2XS 1.0.0-rc1 — отчёт build/host acceptance

Дата прогона: 2026-09-01 Вердикт: RC ACCEPTED WITH RELEASE-REQUIRED UX FIXES

Прогон выполнялся не как статический аудит исходного кода, а как фактическая release/host acceptance RC-сборки: сборка артефакта, установка на переустановленный Debian 13 и проверка работающего сервера.

Release candidate:

Version:       1.0.0-rc1
Source commit: a1f0db22c2f6c0b0436789b64e82f53bfa314327
Artifact:      hy2xs-install-1.0.0.tar.gz
SHA-256:       7fd18a34f56ebb7e9cf62f579e857d6ed3f6a9711075a17da22818a4b079683c
Target:        Debian 13 / amd64
Hysteria:      v2.12.2
Default obfs:  gecko
Fallback obfs: salamander

Публичный IPv4 тестового хоста в отчёте заменён на 198.51.100.10 (RFC 5737, документационный диапазон). Доменное имя и номер SSH-порта оставлены: без них шаги прогона невоспроизводимы.


1. Что проверялось

  • воспроизводимость release build;
  • соответствие version contract;
  • тесты оркестратора;
  • typecheck и сборка frontend;
  • тесты Go;
  • гейты уязвимостей зависимостей;
  • разрешение и фиксация актуального stable Hysteria;
  • upstream SHA-256 Hysteria;
  • совместимость сгенерированной production-конфигурации с Gecko и Salamander;
  • содержимое готового release archive;
  • внутренние контрольные суммы пакета;
  • clean-host boundary;
  • read-only PHASE 0;
  • отказ установки поверх HY2XS 0.x;
  • фактическая чистая установка на переустановленный Debian 13;
  • systemd;
  • nftables takeover;
  • firewall rollback guard;
  • ACME;
  • runtime Hysteria;
  • runtime админки;
  • install-state;
  • status;
  • read-only doctor;
  • доступ к admin UI исключительно через SSH local forwarding;
  • базовые функциональные операции admin UI.

Полный внешний Hysteria/Gecko dataplane через пользовательский desktop-клиент сознательно отложен до готовности собственного C#/sing-box клиента HY2XS. Это не подменяется server-side self-test — см. раздел 12.


2. Build acceptance

Финальная release-сборка выполнена из a1f0db22c2f6c0b0436789b64e82f53bfa314327. Рабочее дерево перед сборкой было чистым.

Orchestrator:

399 pass
0 fail
932 expect() calls
18 test files

Hysteria:

Resolved stable: v2.12.2
Tag:             app/v2.12.2
Published:       2026-08-23

Upstream SHA-256:

6493dfffd55b5883f64c76c63880ecc32988f0c568c9ca9014907877b4d55f94

Скачанный бинарь совпал с upstream hashes.txt.

Compatibility gate:

Gecko      PASS
Salamander PASS

Сгенерированная production-конфигурация HY2XS принята Hysteria v2.12.2.

Frontend:

vue-tsc --noEmit    PASS
vite production     PASS

Go:

go test             PASS

Security:

govulncheck v1.7.0      PASS
reachable vulns         0

pnpm audit high+        PASS
high/critical vulns     0

Полный release acceptance дошёл до:

[hy2xs-build] Built dist/hy2xs-install-1.0.0.tar.gz

Проверки release pipeline включают в том числе read-only installer boundary, семантику отката, firewall guard, сериализацию операций, транзакционный импорт пиров, редактирование секретов, гигиену зависимостей и обязательность test/security-гейтов.

2.1. Требование к памяти build-хоста

Первый govulncheck был убит Linux OOM killer на машине с:

RAM:  ~1.9 GiB
Swap: 0

После подключения временного swap 4 GiB полный security gate прошёл.

Это не runtime-дефект HY2XS, но требование к сборочной машине: около 2 GiB RAM без swap может быть недостаточно для govulncheck. 4 GiB swap здесь — не формально доказанный минимум, а подтверждённая рабочая конфигурация данного прогона. См. docs/build/02-build-layer-and-package.md.


3. Исправления release verifier, сделанные во время приёмки

Приёмка выявила несколько ошибок не продукта, а самого release verifier. Они были исправлены до формирования принятого RC.

3.1. Ранний выход matcher'а и pipefail

Обнаружен антипаттерн вида printf … | grep -q … при set -o pipefail. На достаточно большом выводе продюсера раннее завершение grep -q способно привести продюсера к SIGPIPE, и статус всей конструкции становится 141 — ненулевым именно тогда, когда совпадение НАЙДЕНО.

56 проверок переведены на форму без опасного pipeline.

Кроме того, исправлена более существенная проблема: прежнее 2>/dev/null || true превращало ошибку или опечатку в пути файла в пустой ввод, а отрицательная проверка после этого получала ложный PASS. Теперь отсутствие ожидаемого исходного файла — ошибка приёмки.

Примечание: единичное первоначальное падение на LICENSE нельзя доказанно объяснить этим механизмом — размер LICENSE был ниже воспроизведённого порога буфера канала. После исправлений содержимое LICENSE, его копия в архиве и контрольные суммы подтверждены отдельно.

3.2. Проверки кода против комментариев

Выявлены три ложных совпадения: virtual:svg-icons-register, прежние имена раннеров, cancelFirewallRollback. Все они находились в комментариях и прозе, а приёмка трактовала присутствие строки как возвращение исполняемого кода.

Семантика гейтов исправлена: SVG проверяется по реальному runtime/build contract; определение раннера учитывает форму идентификатора; cancelFirewallRollback проверяется как declaration/call form, а не как любое упоминание строки.

Наивный общий разбор /* … */ намеренно не добавлен: неполный лексер может удалить настоящее содержимое внутри строкового или регулярного литерала и создать уже опасный ложный PASS.

3.3. Устаревший gate reconfigure

Приёмка ожидала прежний вызов classifyReconfigureFailure(ownership) после того, как фактический контракт стал classifyReconfigureFailure(ownership, error).

Новая архитектура:

обычные ошибки       -> классификация по ownership
FirewallGuardFired   -> типизированное исключение
текст error.message  -> не участвует

Gate приведён к фактическому контракту.


4. Artifact acceptance

Release archive hy2xs-install-1.0.0.tar.gz, SHA-256:

7fd18a34f56ebb7e9cf62f579e857d6ed3f6a9711075a17da22818a4b079683c

Хеш независимо пересчитан после копирования архива на Windows и совпал с серверным.

Metadata пакета:

name=HY2XS
license=AGPL-3.0-only
version=1.0.0
release_line=1
config_schema_version=2

source_git_commit=a1f0db22c2f6
dirty_tree=false
build_profile=production

dependency_security_gate=true
tests_gate=true

hysteria_source=official-upstream
hysteria_version=v2.12.2
hysteria_sha_source=upstream-hashes
hysteria_channel=stable
hysteria_resolution=latest-stable
hysteria_compat_gate=true

Полный sha256sum -c metadata/checksums.txt для распакованного пакета завершился без ошибок.

RC опубликован отдельным tag/release v1.0.0-rc1.


5. D0 — установка поверх legacy HY2XS

До переустановки ОС RC был запущен на действующем сервере HY2XS 0.x.

Ожидаемое поведение:

PHASE 0
→ обнаружить legacy markers
→ завершиться до первой persistent mutation

Фактический результат: RC=1.

Installer обнаружил старые:

/etc/hy2xs
/etc/hysteria
/var/lib/hy2xs
/var/lib/hy2xs-admin
/var/lib/hysteria
/usr/local/lib/hy2xs
/usr/local/bin/hysteria
/usr/local/bin/hy2xs-orchestrator
/etc/nftables.d/hy2xs.nft
systemd units
admin installation

и сообщил:

HY2XS v1 не поддерживает установку поверх и не мигрирует состояние 0.x.
Ни один файл на сервере не изменён.

Контрольный before/after diff показал только изменение активной базы SQLite legacy-админки. Отдельный idle-тест без installer подтвердил, что h_ui.db сама меняет hash и mtime примерно каждые 20 секунд при работающем legacy hy2xs-admin.

Следовательно:

D0 legacy detection    PASS
D0 fail-before-apply   PASS
D0 zero product mutation PASS

6. Baseline чистого хоста

Для дальнейшей проверки ОС была переустановлена.

Debian GNU/Linux 13
amd64
kernel 6.12.85+deb13-amd64

RAM  ~1.9 GiB
Swap 0

Сеть:

198.51.100.10/24
fi.api.withen.pro -> 198.51.100.10

До установки:

HY2XS paths       absent
Hysteria paths    absent
HY2XS units       absent
Hysteria unit     absent
nft ruleset       empty
UDP 443           free
TCP 80            free
TCP 443           free

Единственный ожидаемый внешний listener — SSH :2323.

Clean-host contract подтверждён фактическим составом хоста.


7. Чистая установка

Установка выполнялась непосредственно из ранее созданного и проверенного RC artifact. Пакет не пересобирался на target-сервере.

Production profile:

schema                     2
domain                     fi.api.withen.pro
public host                fi.api.withen.pro
public port                443
SSH                        2323
firewall mode              takeover
staged firewall            true

admin bind                 127.0.0.1
admin port                 8080
admin public access        false

TLS                        ACME
ACME challenge             HTTP
ACME email                 admin@withen.pro

Hysteria port              443/udp
obfs                       gecko

IPv6                       disabled
DNS AAAA policy            strict
public endpoint policy     strict

Результат: INSTALL_RC=0.

Фактически прошли:

PHASE 0
package checksums
clean-host preflight
operation lock
orchestrator bootstrap
system dependencies
capability preflight
filesystem
runtime env
bundled admin
Hysteria download
Hysteria SHA verification
config generation
systemd installation
firewall staged apply
rollback guard
post-install env
bootstrap admin secret
smoke
firewall guard disarm
durable install commit
rollback cleanup

8. Firewall

До применения firewall создан rollback snapshot. Candidate-конфигурация проверена до активации:

nft -c -f hy2xs.nft.candidate
nft -c -f nftables.conf.candidate

После этого создан transient rollback timer:

deadline:    45 s
AccuracySec: 1 s
RemainAfterElapse=no

Firewall применён только после успешного arm guard.

После smoke:

guard disarmed
installed state durably committed
rollback files removed

Post-install:

rollback_guard_active=false
rollback_guard_state=quiescent

Transient guard units отсутствуют. Candidate-файлы отсутствуют. В /run/hy2xs/rollback остался только пустой родительский каталог.

Действующие правила:

table inet hy2xs

input policy drop

allow loopback
allow established/related
allow TCP/2323 IPv4
allow TCP/80 IPv4
allow UDP/443 IPv4
allow ICMP echo-request

9. Состояние runtime

Hysteria:

v2.12.2
active
enabled
UDP 0.0.0.0:443
trafficStats 127.0.0.1:36712

Админка:

active
enabled
TCP 127.0.0.1:8080

nftables: active, enabled.

Let's Encrypt ACME:

authorization valid
certificate obtained successfully

Install state:

product=hy2xs
release_line=1
config_schema_version=2
product_version=1.0.0

installed=true
phase=installed
last_error=""

Секретные runtime-файлы имеют ограниченные permissions. /usr/local/bin/hy2xs-orchestrator является symlink, конечный исполняемый файл имеет 0755 root:root.


10. status и doctor

С корректным runtime --package-dir:

STATUS_RC=0
DOCTOR_RC=0

status подтвердил:

services            active
firewall            valid
firewall entrypoint hy2xs-managed

install generation  current
operation           none

rollback guard      quiescent
runtime state       running
install state       installed

doctor прошёл preflight и smoke.

Отдельно проверен read-only contract doctor — по фактическим PID процессов, а не только статическим тестом:

до doctor:     hysteria PID = 5041, admin PID = 5042
после doctor:  hysteria PID = 5041, admin PID = 5042

doctor Hysteria restart   NO
doctor admin restart      NO

Post-install состояние systemd/firewall/install-state соответствует ожидаемому контракту.


11. Admin UI — ручная функциональная проверка

Доступ:

SSH local forwarding
127.0.0.1:8080

Публичный admin listener отсутствует.

Проверены: вход, дашборд, список пиров, создание пира, генерация share URI.

Дашборд получает данные CPU/RAM/disk/runtime.

Создание пира фактически работает при ручном указании секрета — см. UX-02 в перечне дефектов.

Share URI формируется в ожидаемом production-формате:

hysteria2://<peer-secret>@<host>:443/
  ?insecure=0
  &obfs=gecko
  &obfs-password=<server-obfs-secret>
  &sni=<host>
  #<peer-name>

Реальный секрет из тестовой URI в документацию не переносится.

Незакрытое действие среды: одна тестовая URI была выведена за пределы admin UI, поэтому соответствующий тестовый пир перед дальнейшим использованием среды следует удалить или пересоздать с новым секретом.


12. Отложенная проверка Gecko E2E

Полноценный внешний client E2E на этом прогоне не выполнялся.

Причина не в обнаруженном server-side дефекте. Целевой пользовательский клиент HY2XS ещё разрабатывается:

C#
sing-box core
HY2XS desktop shell

Практически пригодных сторонних клиентов с необходимой Gecko-поддержкой недостаточно для того, чтобы считать их корректной reference implementation.

При этом уже подтверждено:

Hysteria v2.12.2 Gecko config compatibility PASS
Hysteria v2.12.2 Salamander compatibility  PASS
server startup                              PASS
ACME                                        PASS
UDP/443 listener                            PASS
peer auth/control-plane smoke               PASS
share URI generator                         PASS

Статус внешнего Gecko E2E:

DEFERRED — WAITING FOR HY2XS DESKTOP CLIENT

Он обязателен для окончательной ecosystem acceptance «server + client», но отсутствие стороннего Gecko-клиента не следует трактовать как отказ текущего server/orchestrator/admin RC.


13. Итоговый статус прогона

SOURCE AUDIT                 PASS
BUILD                        PASS
TEST GATE                    PASS
SECURITY GATE                PASS
ARTIFACT INTEGRITY           PASS

LEGACY D0                    PASS
CLEAN HOST                   PASS
FRESH INSTALL                PASS
ACME                         PASS
SYSTEMD                      PASS
NFTABLES                     PASS
FIREWALL ROLLBACK GUARD      PASS
INSTALL STATE                PASS
STATUS                       PASS
DOCTOR                       PASS
DOCTOR READ-ONLY             PASS

ADMIN LOGIN                  PASS
ADMIN DASHBOARD              PASS
PEER CREATE                  PARTIAL / UX DEFECT
SHARE URI GENERATION         PASS

EXTERNAL GECKO CLIENT E2E    DEFERRED
EXTERNAL SALAMANDER E2E      DEFERRED

FINAL v1.0.0                 NOT YET ACCEPTED

v1.0.0-rc1 сохраняется как успешно прошедший server/install RC.

Что требуется до финального v1.0.0 — см. перечень дефектов и план закрытия.


14. Замечания по шуму в логах прогона

Две записи в журнале прогона к HY2XS отношения не имеют:

  • ystemctl — опечатка в shell;
  • пустой journalctl -u hysteria-server --since '-2 min' — ожидаемо, поскольку реального внешнего клиента в этот момент не подключали.