diff --git a/docs/WAVE_REPORT_2026-07-10-v2.md b/docs/WAVE_REPORT_2026-07-10-v2.md new file mode 100644 index 00000000..e99e1a78 --- /dev/null +++ b/docs/WAVE_REPORT_2026-07-10-v2.md @@ -0,0 +1,247 @@ +# Wave Report 2026-07-10 v2 — untouched-modules code audit + +Ветка: `feat/wave-report-2026-07-10-v2` (от `main`, HEAD `6850649`). +Предыдущая волна того же дня — `feat/wave-report-2026-07-10` (PR #59) — чинила +сборку и каталогизировала N1-N13. Эта волна её НЕ повторяет: она уходит в +слабо-покрытые модули (`routing.rs`, `gf16.rs`, `modem.rs`, `router.rs`) и ищет +то, что все прошлые волны и PR #59 пропустили. Anchor: phi^2 + phi^-2 = 3. + +--- + +## 0. Честная преамбула (проверено командами, не на слово) + +- `main` НЕ собирается из чистого клона. Воспроизведено на этой машине + `cargo 1.95.0`: + ``` + error[E0308]/[E0599] build.rs:29 map_or применён к Option + error: could not compile `trios-mesh` (build script) due to 4 previous errors + ``` + Починка живёт в draft PR #60 (`feat/pipeline-guard-2026-07-10`), ещё не влита. + Ни одно число в этом отчёте не подразумевает зелёный `main`. +- Счётчики заземлены командами: `find specs -name '*.t27' | wc -l` = 68; + `grep -rE '^\s*#\[test\]' src tests | wc -l` = 101. Никаких "120 тестов". +- Железо. M1-crypto проверен на кремнии PS (ARM Cortex-A9) трёх плат P201 Mini + (`smoke/M1_RESULTS.md`, RC=0). FPGA-ткань Zynq-7020 (PL) НИКОГДА не прошивалась. + AX7203 (Artix-7 `xc7a200t`) — единственная плата, доказанная на кремнии. + "SNR 108.6 dB" — цифровой loopback, не OTA. Все PHY-числа `-sim`. +- Прескрипшены ниже — это ПЛАН правок (спека/алгоритм), не патчи. Эта волна + ничего не мержит и не пишет боевой Rust. Бизнес-логика движется через .t27, + где модуль уже на миграционном пути (см. `docs/T27_FIRST_MIGRATION.md`); + `routing.rs`/`router.rs`/`modem.rs`/`gf16.rs` пока рукописный Rust, поэтому + для них прескрипшн — это алгоритмическое изменение плюс указание будущего + .t27-дома, а не готовый diff. + +--- + +## 1. Три метафоры для чтения карты (для не-инженера тоже) + +- Замок и стены. Прошлые волны укрепили ВОРОТА (crypto-рукопожатие, HELLO-MAC, + src-спуф, split-horizon в PR #59). Эта волна проверила ВНУТРЕННИЕ КОРИДОРЫ — + таблицу маршрутов, счётчик прыжков (TTL), приёмник радио, арифметику GF16. + Нашлось: маршрут, который «застревает» и никогда не обновляется; счётчик + прыжков, который отпускает пакет на один коридор дальше положенного; + радио-приёмник, который держит фазу только на короткой ровной дистанции. +- Тепловая карта. Один P1 (заморозка маршрута — это дыра в самом центре, + холодная и незаметная, пока сеть не начнёт менять топологию) и четыре P2 + (тёплые края: TTL, Doppler-хвост, GF16-округление, вырожденный FFT). +- Что я реально нашёл в коде. Не «плохо пахнет», а конкретные строки с + воспроизведением: `routing.rs:158-185` заморозка метрики доказана отдельной + программой (обновление 8.0 и inf оба отвергнуты, маршрут застыл на 3.0). + +--- + +## 2. Карта слабых мест (только НОВОЕ; heatmap) + +| # | Слабое место | Severity | Файл:строка | Fix E-id | +|---|---|---|---|---| +| 1 | `is_feasible` реализует «строго лучше установленной метрики», а НЕ RFC 8966 feasibility distance: нет seqno, нет retraction, нет expiry. Апдейт от текущего next-hop с худшей (8.0) или бесконечной (inf) метрикой молча отвергается → маршрут замерзает на лучшем-за-историю значении навсегда, мёртвый next-hop не отзывается. Воспроизведено. | P1 | `routing.rs:158-185` | E1.3 | +| 2 | Ветка `None` в `is_feasible` возвращает `true` для inf-метрики (finiteness проверяется только в ветке `Some`) → `learn_route` устанавливает маршрут, который источник объявил недостижимым (`path_etx = inf`). Воспроизведено (ставит `(5, inf)`). | P2 | `routing.rs:160,163` | E1.3 | +| 3 | TTL проверяется ДО декремента (`hdr.ttl == 0`), потом форвардит `ttl-1`. RFC 1812 §5.3.1 требует отбрасывать, когда декремент даёт 0 → истёкший кадр уходит на один прыжок дальше радиуса. Переживает split-horizon из PR #59 (тот ловит только 2-узловой отскок, не ≥3-узловую транзитную петлю). | P2 | `router.rs:411-423` | E2.4 | +| 4 | Восстановление несущей — только первого порядка: один скаляр omega + линейный derotate, оценка через lag-1 DD-дифференциал. Нет члена Doppler-rate (2-й порядок), нет tracking-петли → остаточная фаза хвоста растёт ~i^2 на длинном кадре при дрейфе генератора / ускорении платформы (ровно FANET-сценарий, ради которого PHY и существует). | P2 | `modem.rs:293-317, 336-359` | E2.5 | +| 5 | Комплексная «бабочка» FFT округляет ОДИН раз на комплексное произведение (`CGf16::mul` считает re/im в f64 и округляет однократно). Дискретная 4-множительная GF16-RTL-бабочка округлит 3 раза на реальный выход → host-модель НЕ бит-точна к «4-mul form», как утверждает её же комментарий; обещанный 1:1 sim↔RTL паритет провалится на bring-up. Скалярные encode/decode/add/mul/fma — корректны. | P2 | `gf16.rs:287-300, 329-336` | E4.2 | +| 6 | `bit_reverse` делает сдвиг на 32 (`>> (32 - bits)` при `bits=0`) для вырожденного `n=1` FFT → UB/panic в debug. `fft` пропускает n=1 через `is_power_of_two`. | P2-low | `gf16.rs:304-313` | E4.2 | + +Что НЕ подтвердилось (чтобы не раздувать). Кандидат «next_hop возвращает узел +без линков» — в `router.rs:313-350` сам `next_hop()` защищён: path-route, +ranked и best-ветки все проверяют `etx(nh).is_finite()`. Буквального «нет +линка» не происходит. Но заморозка #1 бьёт уровнем выше: `path_route(dst)` +возвращает застывший next-hop, чей ПРЯМОЙ линк жив, а ДАЛЬНЯЯ часть пути +устарела — форвард в чёрную дыру. То есть проблема реальна, но её корень — #1, +а не отсутствие проверки в `next_hop()`. GF16 encode/decode арифметика — +проверена по строкам, ошибок не найдено; слабое место GF16 — в паритете +округления составного датапата (#5), а не в скалярных операциях. + +--- + +## 3. Наука → прескрипшн (каждый пункт с первичной ссылкой) + +- #1, #2 (routing feasibility). Babel строит feasibility на паре (seqno, metric) + и feasibility distance = историческом минимуме метрики для данного seqno; + апдейт от текущего successor'а с бОльшим seqno принимается ДАЖЕ если метрика + выросла, а retraction — это метрика = infinity (0xFFFF). Это и есть механизм + против count-to-infinity, которого тут нет. + Ссылка: RFC 8966 (Babel), §3.5.1 feasibility condition, §3.8 route + maintenance/retraction — https://www.rfc-editor.org/rfc/rfc8966 . + Метрика ETX как основа — De Couto et al., "A High-Throughput Path Metric for + Multi-Hop Wireless Routing", MobiCom 2003, DOI 10.1145/938985.938993. + Прескрипшн: ввести per-source seqno + expiry-таймер маршрута + приём апдейта + от текущего successor'а безусловно (в т.ч. рост метрики и retraction inf); + запретить установку inf-метрики (finiteness и в ветке `None`). Дом: будущий + `specs/routing.t27` (модуль на миграционном пути). + +- #3 (TTL). RFC 1812 §5.3.1: маршрутизатор декрементирует TTL, и «если TTL + уменьшился до нуля, датаграмма ДОЛЖНА быть отброшена» — то есть решение по + ПОСТ-декрементному значению. Ссылка: RFC 1812 §5.3.1 + https://www.rfc-editor.org/rfc/rfc1812 ; базово RFC 791 + https://www.rfc-editor.org/rfc/rfc791 . Прескрипшн: отбрасывать при + `hdr.ttl <= 1` (эквивалент пост-декремента), форвардить `ttl-1` только для + `ttl >= 2`. Семантика TTL — в `specs/wire.t27` (заголовок уже .t27-генерится). + +- #4 (residual CFO). Data-aided оценщик несущей по короткому пилоту + предполагает ПОСТОЯННЫЙ сдвиг на интервале наблюдения и имеет жёсткий предел + диапазона — классический результат Moose. Ссылка: P. H. Moose, "A Technique + for OFDM Frequency Offset Correction", IEEE Trans. Commun. 42(10):2908-2914, + 1994, DOI 10.1109/26.328961; sync-пилот — Schmidl & Cox, arXiv:1905.07792. + Прескрипшн: добавить член 2-го порядка (квадратичная фаза / Doppler-rate) или + decision-directed по-символьный tracker; критерий приёмки — вектор с + линейно-нарастающим CFO. Дом: `modem.rs` (рукописный Rust, -sim). + +- #5, #6 (GF16 датапат). Слитое (fused) округление и дискретное per-op + округление дают РАЗНЫЕ биты — это фиксирует IEEE 754-2019 (fused vs discrete). + Формат GF16 не заявляет точностного превосходства, честный аргумент — площадь; + но паритет sim↔RTL требует зафиксировать МОДЕЛЬ округления. Ссылки: GoldenFloat + arXiv:2606.05017; IEEE 754-2019 DOI 10.1109/IEEESTD.2019.8766229. Прескрипшн: + либо задокументировать контракт «fused complex-MAC» и генерить RTL под него, + либо переключить host-модель на дискретное округление под 4-множительную + бабочку; в обоих случаях — golden-вектор, который ПАДАЕТ при неверной + структуре округления; починить n=1. Дом: `gf16.rs` + `tests/gf16_conformance.rs`. + +--- + +## 4. Декомпозиция на 4 спринта (E.) + +Sprint 1 — идентичность + целостность сообщений +- E1.1 Аутентифицированное рукопожатие Noise (es+se + transcript hash). + Файлы: `crypto.rs`. Критерий: MITM-тест — атакующий не выводит ключ сессии; + smoke на PS-плате RC=0. Effort M. (уже в работе: PR #63/#65, N3). +- E1.2 HELLO-MAC подключён в daemon, per-node ключи, constant-time сравнение. + Файлы: `discovery.rs`, `bin/trios_meshd.rs`. Критерий: подделанный HELLO + отвергнут на устройстве; `verify_mac` реально вызывается. Effort M. (N4/N8). +- E1.3 Babel-верная feasibility: seqno + retraction + expiry; запрет inf-маршрута. + Файлы: `routing.rs` (→ `specs/routing.t27`). Критерий: (a) апдейт текущего + successor'а с худшей/inf метрикой ПРИНИМАЕТСЯ (маршрут обновлён/отозван); + (b) inf-маршрут не устанавливается никогда; (c) property-тест: 100 случайных + топологий со смертями линков → 0 вечно-устаревших маршрутов. Effort M. НОВОЕ. + +Sprint 2 — разнообразие путей + self-heal gate +- E2.3 Метрика сходимости self-heal (B11): `link_loss_to_reroute_ms` с + порогом PASS/FAIL. Файлы: `bin/trios_meshd.rs`. Критерий: измеримый порог, + ранее UNDEFINED. Effort M. (известный B11; ссылка BFD RFC 5880 + https://www.rfc-editor.org/rfc/rfc5880 как аналог быстрого детекта). +- E2.4 TTL пост-декремент (RFC 1812 §5.3.1). Файлы: `router.rs`, + `specs/wire.t27`. Критерий: узел с `ttl=1` отбрасывает (не передаёт `ttl=0`); + тест 3-узловой форс-петли делает N-1 кругов, не N. Effort S. НОВОЕ. +- E2.5 Восстановление несущей 2-го порядка. Файлы: `modem.rs`. Критерий: + кадр 1760 символов при линейно-нарастающем CFO (0→0.02 cyc/sym) восстановлен + бит-точно; текущая одно-omega модель на том же векторе падает. Effort M. НОВОЕ. + +Sprint 3 — hardening +- E3.1 Расширение replay-окна + rekey-cap по числу кадров. `crypto.rs`. + Критерий: переупорядоченный в пределах окна кадр не отвергается; нет reuse + nonce у порога. Effort M. (S5/S8). +- E3.2 Rate-limit gateway FETCH (амплификация DoS). `bin/trios_meshd.rs`. + Критерий: ограничение исходящих соединений/сек; нагрузочный тест. Effort S. (N7). +- E3.3 Права `/tmp/mesh.drop` (не world-writable). `bin/trios_meshd.rs`. + Критерий: сторонний локальный процесс не может убить линк. Effort S. (N10). + +Sprint 4 — паритет верификации +- E4.1 GF16 golden-conformance: 0x47C0-anchor + 84-format вектора. + `tests/gf16_conformance.rs`. Критерий: SHA-256-заякоренный набор, sim↔эталон. + Effort M. (B08/B14; arXiv:2606.09686). +- E4.2 Зафиксировать модель округления комплексного датапата + починить n=1. + `gf16.rs`, тесты. Критерий: golden-вектор, ПАДАЮЩИЙ при неверной структуре + округления; n=1 FFT не паникует. Effort M. НОВОЕ. +- E4.3 T27 iverilog cross-check для `wire`/`routing` спек. Критерий: сгенерённый + Verilog проходит те же вектора, что Rust. Effort M. + +--- + +## 5. Три линии кооперации для следующей волны + +Lane A — Babel-верное ядро маршрутизации (E1.3 + E2.4) +- Scope: `routing.rs` → `specs/routing.t27`, плюс TTL-семантика в `wire.t27`. +- Actor fit: контрибьютор по distributed-systems / routing. +- Deliverable: feasibility на (seqno, metric) + retraction + expiry; TTL + пост-декремент; property-тест «100 топологий со смертями → 0 stale». +- DEMO: `cargo test routing::` + новый property-тест зелёные; репро из этого + отчёта переворачивается (обновление 8.0/inf теперь принято). +- Cite: RFC 8966 §3.5.1/§3.8; RFC 1812 §5.3.1; ETX DOI 10.1145/938985.938993. +- Effort: M. Risk: трогает .t27-миграционную поверхность — стартовать ТОЛЬКО + после PR #60 (pinned-t27c), иначе regen снова сломает дерево. + +Lane B — Восстановление несущей для мобильности (E2.5) +- Scope: `modem.rs` (host `-sim`). +- Actor fit: DSP/SDR-контрибьютор. +- Deliverable: член 2-го порядка (Doppler-rate) или per-symbol tracker + вектор + с нарастающим CFO. +- DEMO: `recovers_ramping_cfo_tail_coherent` проходит; одно-omega baseline на + том же векторе падает (показать оба прогона). +- Cite: Moose 1994 DOI 10.1109/26.328961; Schmidl-Cox arXiv:1905.07792. +- Effort: M. Risk: только -sim; OTA-валидация закрыта тайскими правилами и + непрошитой PL — паритет остаётся стендовым до прошивки. + +Lane C — GF16 conformance-оракул (E4.1 + E4.2) +- Scope: `gf16.rs` + `tests/gf16_conformance.rs`. +- Actor fit: контрибьютор по numeric-formats (стыкуется с 80-format Corona + оракулом / numeric-conformance-toolkit). +- Deliverable: 0x47C0-заякоренные golden-вектора, которые ПАДАЮТ у неверно + округляющей RTL-бабочки; фикс n=1; явный контракт модели округления. +- DEMO: вектор, различающий fused vs discrete округление; n=1 FFT без паники. +- Cite: GoldenFloat arXiv:2606.05017; IEEE 754-2019 DOI 10.1109/IEEESTD.2019.8766229. +- Effort: M. Risk: RTL ещё нет — проверка ПРЕВЕНТИВНА (страхует будущий + bring-up), не чинит текущий сбой. + +--- + +## 6. Что дальше логично для TRI-NET (единственный шаг наибольшего рычага) + +Влить PR #60 (pinned-t27c build + drift-guard) — ПЕРВЫМ, до всего остального. + +Почему именно он. Рычаг = (что разблокируется) × (радиус поражения) / усилие. + +- Что разблокируется: всё. На дереве, которое не собирается из чистого клона, + нельзя честно ни влить фикс маршрутизации/crypto, ни прогнать CI, ни измерить + self-heal. Каждая новая волна заново натыкается на N1/N2 (сломанный build.rs + + невалидный `gen/rust`). 12 открытых feat-веток ждут зелёного base. +- Радиус: drift-guard закрывает КОРЕНЬ — запрещает писать в репо `gen/rust`, + сгенерённый старым t27c (именно это сломало дерево коммитом f608dad). Без + guard'а зелёный main снова деградирует на следующем regen — Sisyphus-loop. +- Усилие: маленькое и механическое (build.rs + восстановление `gen/rust` под + pinned t27c + CI-гейт), PR уже готов как draft. + +Второй по рычагу шаг (после зелёного main): назначить владельца спринту +идентичности (E1.1/E1.2, драфты PR #63/#65 уже есть) — неаутентифицированное +рукопожатие (N3) это единственная P0-дыра БЕЗОПАСНОСТИ и она гейтит любой +реальный/OTA-выход. Фикс feasibility (E1.3) едет в тот же спринт: и то, и другое +делает mesh доверяемым (identity ворот + честная таблица маршрутов внутри). + +Почему НЕ железо сейчас: PL Zynq-7020 не прошита, OTA закрыта тайскими +правилами, доказанный кремний — только AX7203. Любой хардварный шаг упирается в +физический доступ и regulatory; софт-инвариант «чистый клон → зелёная сборка» +не упирается ни во что и разблокирует максимум работы за минимум усилия. + +--- + +## 7. Граница — что эта волна НЕ может + +- Не мержит, не пушит в `main` (branch-protected), только draft PR. +- Не прошивает железо, не запускает OTA, не заявляет `hw` без строки + on-device лога RC=0. +- Не пишет боевой Rust-фикс: прескрипшены выше — план правок спек/алгоритма. + Мердж и владелец — за человеком. +- Не переписывает `docs/archive/STRENGTHEN.md`: волна ДОБАВЛЯет датированный + отчёт, бэклог курирует владелец. + +--- + +phi^2 + phi^-2 = 3