Skip to content

Latest commit

 

History

History
157 lines (124 loc) · 43.3 KB

tech-stack.md

File metadata and controls

157 lines (124 loc) · 43.3 KB

5. Технологический стек

Политика зависимостей

Критерий границы — ADR-027 (2026-08-31). Раздел больше не перечисляет подсистемы по именам, а отвечает на вопрос «кто принимает решение»:

  • Своё — там, где мы решаем, что считать правильным: раскладка бокса, момент инвалидации стиля, порядок отрисовки, поведение вложенного контекста просмотра, что браузер показывает пользователю и чего он не отправляет.
  • Готовое — там, где решение уже принял комитет и записал в спеку: формат файла, таблица Unicode, OpenType-lookup, конечный автомат разбора URL, алгоритм сжатия, криптографический примитив.

Проверка: если мы напишем это сами и разойдёмся с эталонной реализацией — правы мы или нет? Если неправы по определению, решение принадлежит спеке. Требования trait-anchor и обоснования в теле коммита не отменяются — см. конец раздела.

Стратегия (обновлено 2026-05-15): сначала рабочий браузер, потом разговор «что переписывать самим». Раньше §5 формулировался бинарно — «всё своё, кроме 5 exception». На практике это упирало в задачи, которые ничего не определяют в идентичности Lumen, но стоят месяцев работы (image decoders, Unicode UAX-таблицы, Brotli, WOFF2, HTTP/3). Новая формулировка — две категории exception:

  • Permanent. Никогда не пишем сами. Универсальное правило безопасности / здравого смысла. 5 шт.
  • Provisional accelerators. Берём готовое сейчас ради скорости Phase 1-3, но за trait-anchor в lumen-core::ext, чтобы при желании заменить. У каждого — «graduation criterion»: событие, при котором имеет смысл писать своё. Большинство criterion-ов в духе «реалистично — никогда» (формат стабильный, без архитектурной ценности для Lumen) — это не лицемерие, а честная маркировка.

«Не делаем Google Chrome» — это про ядро. Lumen остаётся проектом про собственный rendering engine. HTML/CSS/DOM/style/layout/paint/font/encoding, URL, HTTP/1.1, DNS-резолвер с DoH/DoT, adblock matcher, knowledge layer (§12), UI shell — всегда наши. Если кто-то предлагает «возьми готовое» для пункта из этого списка — это уже Chrome-форк, а не Lumen.

Поэтому мы по-прежнему пишем свой код для:

  • HTML / CSS парсеров, DOM, style cascade, selectors;
  • layout (block, inline, flex, grid), paint, compositing;
  • Punycode (RFC 3492) и homograph-проверки IDN — часть решения «что показать в адресной строке», см. lumen-core::idn. Сам разбор URL закрыт LIB-6 (2026-09-01): scheme/host/path/query/fragment splitting, percent-encoding, dot-segment removal и resolve() теперь конечный автомат WHATWG через крейт url (см. Permanent exceptions ниже) — своя ~370-строчная реализация проходила 41.95% urltestdata.json (LIB-0), новая — 81.86%;
  • HTTP/1.1, HTTP/2, DNS-резолвера с DoH/DoT (HTTP/3 — через provisional quinn);
  • определения и конвертации кодировок (cp1251, KOI8-R, CP866 и др.);
  • PNG-декодера (готов в lumen-image + свой DEFLATE…) — устарело, исправлено 2026-08-31. Своего PNG-декодера нет: crates/engine/image/src/png/mod.rs — 125-строчная обёртка над zune_png, JPEG/WebP/AVIF — такие же обёртки. Своего DEFLATE тоже нет: crates/network/src/flate.rs — это flate2, WOFF1 распаковывается miniz_oxide. Все растровые кодеки и сжатие — provisional-категория ниже;
  • TrueType-парсинга (lumen-font: cmap/glyf/CFF/COLR/CPAL/fvar/gvar — variable и color fonts), подбора шрифтов и растеризации глифов. Text shaping закрыт rustybuzz (LIB-1, 2026-08-31, production default с LIB-2, 2026-08-31; см. таблицу Permanent exceptions ниже) — свой GSUB/GPOS движок (GSUB 1/4 из 8, GPOS 1/2 из 9, без диакритики и сложных письменностей) удалён целиком в LIB-3 (2026-09-01), rustybuzz — единственный TextShaper за trait-anchor lumen_core::ext::TextShaper;
  • движка адблок-фильтров;
  • 2D-растеризации поверх GPU-абстракции;
  • ephemeral KV-хранилища (in-memory, для тестов и session-scope данных);
  • IPC, async-примитивов;
  • UI-фреймворка (иммедиат-режим поверх своих paint-примитивов);
  • собственных MD5 / SHA-256 (для HTTP Digest, не security-критично — challenge-response, не KDF), Base64;
  • knowledge layer §12 — это пользовательская ценность Lumen, не делегируется внешним библиотекам.

Bidi (UAX #9), line breaking (UAX #14), segmentation (UAX #29), normalization (UAX #15) — формально были в «своё», но писать свои Unicode-таблицы — это годы работы с обновлениями при каждом релизе Unicode. Переходят в provisional через icu4x.

Permanent exceptions (5 шт., никогда не переписываем)

Это единственные deps, для которых принципиально нет смысла писать своё. Каждая прячется за trait в lumen-core::ext.

Crate Что покрывает Trait-anchor Почему не сами
winit OS event loop, окна, ввод WindowingBackend Win32 + X11 + Wayland + AppKit — ~50–100k LOC платформенно-специфичных багов и behaviour quirks
wgpu GPU API (Vulkan / Metal / DX12 / GL) RenderBackend 4 разных API, разные семантики, driver-баги. Свой = годы работы и регрессий
rustls + webpki-roots TLS, X.509, X25519, AES-GCM, HKDF; webpki-roots — bundle корневых CA-сертификатов (Mozilla CA bundle). Без него HTTPS не валидируется. TlsBackend Универсальное правило безопасности: не пишите свой crypto. rustls — аудит + формальная верификация частей кода. webpki-roots — pure data + lookup, partner-crate к rustls
rustls-pemfile PEM-декодирование X.509-сертификатов и приватных ключей — используется tls::trusted_root_store() (crates/network/src/tls/mod.rs) для загрузки дополнительного CA из файла, названного LUMEN_EXTRA_CA_CERT (BUG-785) TlsBackend (тот же, что rustls) Partner-crate к rustls (тот же upstream org). Своя реализация base64/PEM-разбора сертификатных данных — тот же «не пишите свой crypto» антипаттерн
aes-gcm + aes (RustCrypto) AEAD AEAD_AES_128_GCM + сырой AES-128 блок для QUIC packet protection (RFC 9001 §5.3/§5.4) в lumen-network::h3; тот же aes-gcm уже под Web Crypto (lumen-js) и шифрованием хранилища (lumen-storage) нет отдельного (низкоуровневый примитив под QUIC) То же правило «не пишите свой crypto» (Exception #3). Pure-Rust RustCrypto, без native-кода; ручная реализация AEAD/AES — security antipattern
cbc (RustCrypto) AES-CBC режим с PKCS7 padding для SubtleCrypto AES-CBC (lumen-js::subtle_crypto, W3C WebCrypto §17.7); оборачивает aes 0.8 нет отдельного (примитив режима шифрования под WebCrypto) Pure-Rust RustCrypto cipher-mode wrapper; ручная реализация CBC с padding — security antipattern
ctr (RustCrypto) AES-CTR stream cipher (Ctr128BE) для SubtleCrypto AES-CTR (lumen-js::subtle_crypto, W3C WebCrypto §17.8); оборачивает aes 0.8 нет отдельного (примитив режима шифрования под WebCrypto) Pure-Rust RustCrypto; ручная реализация CTR — security antipattern
x25519-dalek (dalek-cryptography) X25519 (Curve25519) ECDH для TLS 1.3 key_share (RFC 7748 / RFC 8446 §4.2.8) в lumen-network::h3::key_agreement — превращает пару ephemeral KeyShareEntry в (EC)DHE shared secret для tls_schedule::handshake_secret нет отдельного (низкоуровневый примитив под QUIC/TLS) То же правило «не пишите свой crypto» (Exception #3). Constant-time скалярное умножение, pure-Rust, без native-кода; ручная реализация Curve25519 — security antipattern
ed25519-dalek (dalek-cryptography) Ed25519 (EdDSA над Curve25519, RFC 8032) — верификация подписи схемы ed25519 в TLS 1.3 CertificateVerify (RFC 8446 §4.2.3) в lumen-network::h3::tls_cert_verify::ed25519_verify (peer-аутентификация QUIC/HTTP/3). Переиспользует curve25519-dalek уже в дереве (транзитивно через x25519-dalek) нет отдельного (низкоуровневый примитив под QUIC/TLS) То же правило «не пишите свой crypto» (Exception #3). Verify-only, constant-time, pure-Rust dalek; ручная реализация Ed25519 — security antipattern
rsa (RustCrypto) (1) RSASSA-PSS с SHA-256 — верификация подписи схемы rsa_pss_rsae_sha256 в TLS 1.3 CertificateVerify (RFC 8446 §4.2.3, RFC 8017 §8.1) в lumen-network::h3::tls_cert_verify::rsa_pss_sha256_verify. (2) W3C WebCrypto SubtleCrypto — RSA-OAEP encrypt/decrypt (P3-webcrypto slice 3), RSA-PSS sign/verify, RSASSA-PKCS1-v1_5 sign/verify + generateKey/importKey/exportKey для всех трёх схем в lumen-js::subtle_crypto. Переиспользует sha2 уже в дереве нет отдельного То же правило «не пишите свой crypto» (Exception #3). Pure-Rust RustCrypto, без native-кода; ручная реализация RSA — security antipattern
rand_core (RustCrypto) OsRng (OsRng::default()) для генерации RSA-ключей (rsa::RsaPrivateKey::new) в lumen-js::subtle_crypto::generate_key (W3C WebCrypto SubtleCrypto). Companion crate к rsa — тот уже транзитивно тянет rand_core; явный dep для признака getrandom нет отдельного (примитив CSRNG под keygen) RustCrypto; без getrandom feature OsRng недоступен на Windows
p384, p521 (RustCrypto) ECDSA P-384/P-521 — верификация подписи схем ecdsa_secp384r1_sha384 / ecdsa_secp521r1_sha512 в TLS 1.3 CertificateVerify (RFC 8446 §4.2.3) в lumen-network::h3::tls_cert_verify::ecdsa_p384_sha384_verify / ecdsa_p521_sha512_verify (peer-аутентификация QUIC/HTTP/3; более крупные NIST-сиблинги p256-схемы). Тот же RustCrypto ecdsa/signature v2, что и p256 нет отдельного (низкоуровневый примитив под QUIC/TLS) То же правило «не пишите свой crypto» (Exception #3). Verify-only, pure-Rust RustCrypto, без native-кода; ручная реализация ECDSA — security antipattern
SQLite (rusqlite с bundled feature) Персистентное хранилище: history, bookmarks, notes, read-later, cookies-TTL, профили. FTS5 для §12.1 полнотекстового поиска. StorageBackend + KnowledgeStore 25 лет TH3-тестирования (100% MC/DC branch coverage), стандарт индустрии браузеров (Firefox/Chromium/Safari). Цена ошибки persistent storage асимметрична — молчаливая порча данных пользователя; та же логика, что у crypto. FTS5 закрывает §12.1 без своего inverted index

Долгосрочная стратегия: pure-Rust storage (redb + tantivy). SQLite остаётся permanent exception на Phase 0–2. Однако rusqlite с bundled тянет ~250 КБ C-кода, который нельзя аудировать средствами Rust — это противоречит принципу «свой код = прозрачность». Целевая архитектура (Phase 3+): redb (pure Rust, ACID copy-on-write B+tree, ноль unsafe, используется в cargo) для key-value подсистем (localStorage, sessionStorage, IndexedDB, HTTP cache) + tantivy (Rust-native FTS) для полнотекстового поиска §12.1. Оба за StorageBackend / KnowledgeStore trait — drop-in замена. Graduation criterion: замерить p99 latency SQLite WAL vs redb на реальной нагрузке; если SQLite < 1 мс — миграция не срочна, но остаётся целью ради чистоты стека. | rayon | Work-stealing parallel iterator framework. ADR-016 M4.1: параллельный compute_style для независимых дочерних узлов flex/grid/table item containers в lumen-layout::box_tree. | нет (внутренний примитив параллелизма) | 8+ лет в production, правильный NUMA-aware work-stealing; своя реализация — недели без ценности для идентичности Lumen. Параллелизм layout — не ядро браузерного engine | | unicode-bidi, unicode-bidi-mirroring | UAX #9 Unicode Bidirectional Algorithm (правила P2–I2) и таблица Bidi_Mirroring_Glyph (правило L4) — свойства direction / unicode-bidi в lumen-layout::bidi. unicode-bidi уже был в дереве через lumen-encoding (Icu4xUnicodeProvider::bidi_runs), unicode-bidi-mirroring — транзитивно через text-стек | нет (чистые Unicode-таблицы, не заменяемая подсистема) | Алгоритму нужно свойство Bidi_Class каждой кодовой точки Unicode; своя таблица — это генератор из UCD и её ежегодная синхронизация, без какой-либо ценности для идентичности Lumen. Крейты — pure-Rust данные + ~1500 строк правил, без unsafe и без C | | JS engine (v8 v150.1.0 — дефолт с cutover S12, ADR-018, 2026-07-14, единственный движок с S12b-F1, 2026-08-04; rquickjs полностью удалён из Cargo.toml/Cargo.lock срезом S12b-F4, 2026-08-04) | Исполнение JavaScript | JsRuntime | V8 — 15 лет, миллиарды долларов, сотни инженеров. QuickJS был движком Phase 0–2; S0 go/no-go PASS 2026-07-13, cutover S12 2026-07-14. Новый код — только против V8-пути или движко-независимого шима (WEB_API_SHIM). | | rustybuzz (0.14.1, crates/engine/font, обязательная зависимость с LIB-3) | Text shaping — OpenType GSUB/GPOS layout engine: mark attachment (GPOS 4), Arabic joining (GSUB 5/6/8), Indic reordering. Единственный TextShaper: свой GSUB/GPOS движок (U-2, GSUB 1/4 из 8, GPOS 1/2 из 9) удалён в LIB-3 (2026-09-01) после того, как LIB-2 (2026-08-31) продержал его продакшн-дефолтом через полный цикл пересъёмки graphic-tests/CPU/paint снимков без дрейфа. Уже был в дереве транзитивно через femtovg (её собственный, неиспользуемый text-путь) той же версии 0.14.1 — подключение не добавило новых версий зависимостей ни rustybuzz, ни ttf-parser | lumen_core::ext::TextShaper | Таблицы OpenType, решение принимает спека (ADR-027) | | ttf-parser (0.21.1, транзитивно через rustybuzz) | Парсер таблиц шрифта для rustybuzz. Свой парсер (lumen-font::face) остаётся — растеризация, метрики, variable/color fonts читает он; оба парсера ленивые и заимствуют одни и те же байты шрифта без копирования, второй набор офсетных структур в памяти — не второй набор байтов шрифта | нет отдельного (внутренняя зависимость rustybuzz) | Уходит только вместе с rustybuzz | | resvg/usvg (=0.44.0 точно, не последняя 0.48 — crates/engine/image, обязательная зависимость с LIB-4, 2026-09-01) | Растеризация внешнего SVG (<img src=*.svg>, background-image: url(*.svg)) — lumen_image::decode_svg (src/svg.rs), диспетчеризуется из decode_raw по сигнатуре наравне с PNG/JPEG/GIF/WebP/AVIF. Свой SVG-движок (box_tree/svg.rs/svg_path.rs) рисует ноль linearGradient/radialGradient/clipPath/mask/filter/pattern/marker (измерение ADR-027) — растровый логотип с градиентом выходил плоским. Версия зафиксирована точно, потому что её tiny-skia — 0.11.4, та же, что уже несёт lumen-paint's cpu-render: cargo tree -i tiny-skia держит одну версию при обоих фичах разом. Фича raster-images включена (вложенный <image>), text/system-fonts выключены (свои шрифты — детерминизм графгейта важнее рендера текста внутри SVG) | нет отдельного (decode_raw's единая точка диспетчеризации по сигнатуре) | Таблицы SVG 1.1/2, решение принимает спека (ADR-027) | | url (2.5.8, crates/core, обязательная зависимость с LIB-6, 2026-09-01) | Scheme/host/path/query/fragment splitting, percent-encoding normalization, dot-segment removal, relative-reference resolution — весь WHATWG URL Standard state machine, вызывается из lumen_core::url::Url (inner: url::Url). Своя ~370-строчная реализация проходила 41.95% urltestdata.json (LIB-0), сосредоточенных в двух целиком отсутствующих ветках алгоритма (IPv6-литералы [...], forbidden/control host code points) — новая проходит 81.86%. Url::host() НЕ берёт значение из url: WHATWG-парсер всегда ASCII/IDNA-нормализует host (Punycode + lowercase), а адресная строка (crates/shell/src/address_bar.rs::guard_display_text, DS-6) ищет в тексте ТОЧНУЮ подстроку, которую ввёл пользователь — host() остаётся своей raw-экстракцией; host_ascii()/IDN homograph-проверки остаются на lumen-core::idn/lumen-core::punycode, как и решено в LIB-6 | нет отдельного (crate::url::Url — сам swap-point) | Конечный автомат WHATWG, решение принимает спека (ADR-027) |

Provisional accelerators (берём готовое сейчас, заменяем по событию)

Trait-anchor у каждого — в lumen-core::ext. Подключаем по мере того, как фаза реально упирается в задачу. Список открыт.

Crate (кандидаты) За что Trait-anchor Phase Graduation criterion
zune-jpeg, image-webp, узкий image без default features Декодирование JPEG / WebP / GIF в RGBA. PNG остаётся свой в lumen-image ImageDecoder 1 Едва ли когда-то. Форматы стабильные, без архитектурной ценности; цена реализации (JPEG — DCT+Хаффман+chroma subsampling+progressive; WebP — VP8/VP8L) непропорциональна выгоде
image (feature avif-native, за cargo-фичей avif в lumen-image, не в default; GAP-avif срез 1, 2026-09-21) Декодирование AVIF в RGBA через её avif-native (dav1d C + mp4parse) ImageDecoder (AvifImageDecoder) 1 Едва ли. dav1d-sys требует системный dav1d через pkg-config — без него cargo build --features avif падает на build-скрипте dav1d-sys (проверено: нет pkg-config в тулчейне). Чистого-Rust AV1-декодера с готовой ISOBMFF-обвязкой нет (rav1d публикует только unsafe C-ABI) — своя реализация непропорциональна выгоде, пока AVIF не станет обязательным форматом
jxl-oxide (lumen-image, не за фичей — в default с GAP-avif срез 3, 2026-09-21) Декодирование JPEG XL в RGBA (jxl.rs::decode_jxl) ImageDecoder (JxlImageDecoder) 1 Едва ли. Чистый Rust, без cmake/meson/nasm/pkg-config (проверено cargo tree) — та же категория цены/выгоды, что у JPEG/WebP выше, но декодер CMYK/CMYKA и анимацию не покрывает; расширить их своими силами, если появится реальный спрос
weezl (прямая зависимость lumen-image с 2026-08-20, до этого — транзитивная через gif) LZW-распаковка пикселей GIF-кадра собственным циклом (lumen_image::gif::lzw_decode_into) вместо gif::FrameDecoder, который на кадре с оборванным LZW-потоком не возвращает управление (BUG-787) ImageDecoder (тот же, что у GIF-декодера) 2 Уйдёт только вместе с gif: пока GIF-контейнер разбирает он, weezl в дереве всё равно, и отдельная замена ничего не убирает. Свой LZW (спек GIF §22, ~200 строк) осмыслен лишь если понадобится побитовый контроль сверх того, что даёт decode_bytes
icu4x (выборочные модули: segmentation, line-break, bidi, normalization, CLDR-минимум) Unicode UAX #9 / #14 / #29 / #15 + локалевые таблицы UnicodeProvider 1–2 Реалистично — никогда. Unicode Consortium = «универсальное правило безопасности» для Unicode, аналогично rustls для crypto. Своя реализация = годы поддержки таблиц на каждом релизе Unicode
brotli-decompressor Brotli decompression для HTTP Content-Encoding: br расширение ContentDecoder 1–2 Едва ли. Формат RFC 7932 стабилен, своя реализация = недели с собственным dictionary
ruzstd / zstd-safe Zstandard decompression для HTTP Content-Encoding: zstd (Cloudflare и nginx уже отдают; через 1-2 года будет распространено) расширение ContentDecoder 1–2 Реалистично — никогда. Формат RFC 8478 стабилен, без архитектурной ценности; своя реализация = недели
publicsuffix (или собственный загрузчик publicsuffix.org/list/public_suffix_list.dat) Public Suffix List для cookie domain matching (example.co.uk ≠ co.uk), eTLD+1 расчёта, SameSite=Strict boundary PublicSuffixList 1 Едва ли. Данные обновляются раз в неделю-месяц, формат — простой текст; собственный loader тривиален, но crate избавляет от поддержки парсера
idna Полный UTS#46 mapping table для IDN (ß, ZWJ, контекстные правила) IdnaProvider (на базе текущего Url::host_ascii()) 1–2 Когда найдём real edge-case, который наш str::to_lowercase-Punycode не покрывает
hyphenation Перенос слов (TeX-словари, включая русский) HyphenationProvider 2 Phase 2+ при типографике. Словари можно переписать на свой формат, но low priority
woff2 Распаковка WOFF2 в TTF расширение FontFormat 2 Phase 2 при WebFonts. Формат стабилен, маловероятно писать своё
hunspell-rs / spellbook Spell-check (русская морфология обязательна) SpellChecker 3 Phase 3 при spell-check. Морфология русского сложна, цена своей реализации перекрывает выгоду
quinn HTTP/3 / QUIC расширение NetworkTransport 3 Реалистично — никогда. QUIC = год+ работы (congestion control, packet loss recovery, 0-RTT, key updates)
windows-sys (lumen-network, cfg(windows), с 2026-10-13; уже в графе у lumen-storage/lumen-shell/lumen-a11y) Чтение корней доверия ROOT и Disallowed из хранилища Windows (CertOpenStore/CertEnumCertificatesInStore, COMPAT-TRUST-OSROOTS) нет — единственный потребитель tls/os_roots.rs 1 Постоянная, пока Windows — поддерживаемая платформа
windows 0.58 (windows-rs, lumen-media-native, cfg(windows), с 2026-10-15; версия та же, что у gpu-allocator) COM-интерфейсы Media Foundation (IMFTransform, IMFSample, IMFMediaType, IMFActivate): H.264/HEVC/AAC из ОС (COMPAT-MEDIA-OSDEC-WIN) VideoBackend/AudioBackend реестра декодеров 1 Постоянная, пока декодеры ОС — путь для патентованных кодеков (ADR-035 п.3); функции mfplat.dll не линкуются, а грузятся LoadLibraryExW
redb Pure Rust ACID key-value (copy-on-write B+tree). Альтернативный storage backend для горячих key-value (localStorage, IndexedDB, HTTP cache) StorageBackend 2–3 Замерить p99 latency SQLite WAL vs redb на реальной нагрузке localStorage/IndexedDB. Если SQLite < 1 мс — не нужен
tantivy Rust-native полнотекстовый поиск. Замена SQLite FTS5 для §12.1 knowledge layer при миграции на pure-Rust storage KnowledgeStore 3+ Только вместе с redb — при решении полностью отказаться от SQLite C-кода
mp4parse 0.17 (MPL-2.0, Mozilla, используется в Firefox), matroska-demuxer 0.8 (Zlib/MIT/Apache), ogg 0.8 (BSD-3) + lewton 0.10 (MIT/Apache) — crates/engine/media-demux, с 2026-10-15 (UX-VIDEO-DEMUX, ADR-035 п.2). Контейнеры MP4/ISOBMFF, WebM/Matroska, Ogg — формат определяет спека, не мы (ADR-027). provisional: заменяются своим разбором, если упрёмся в их пределы (mp4parse не знает HEVC/VP9/AV1 в stsd — эти боксы разбираем сами; читатель Matroska не отдаёт ReferenceBlock и перематывает «после метки» — индекс ключевых кадров свой) lumen_core::ext::VideoDecoder (пакеты уходят бэкендам кодеков реестра) 1–2 Граница пересматривается в UX-VIDEO-NATIVE: если свои обходы станут больше самих читателей
rav1d 1.1 (BSD-2, Rust-порт dav1d) — crates/engine/av1 (lumen-av1), с 2026-10-15 (UX-VIDEO-AV1, ADR-035 п.2; до этого лежал в lumen-image только для AVIF). Единственный встроенный AV1-декодер на всех платформах: AVIF-картинки и видео. По умолчанию без asm (не нужен nasm); релизные сборки — фича av1-asm (nasm при сборке на x86), ≈3,5× быстрее (замер). lumen_core::ext::ImageDecoder (AVIF) и lumen_core::ext::VideoDecoder (бэкенд реестра) 1–2 permanent: AV1 — реальный веб-кодек (YouTube, VK Видео, AVIF); пересматривается, только если появится аппаратный AV1 на всех платформах
Ручной FFI-слой (opaque-указатели) поверх FFmpeg shared-dev, БЕЗ ffmpeg-next/ffmpeg-sys-next (статус: crate landed под feature ffmpeg (off by default), не подключён к crates/js, решение — ADR-030, 2026-09-19; с 2026-10-12 только необязательный бэкенд для разработчиков, в выпуск не входит — видео декодируется без FFmpeg, ADR-035) Декодирование video/mp4/video/webm/video/ogg (GAP-MEDIADECODE) — сегодня декодируется только анимированный GIF. Пользователь выбрал «FFmpeg целиком» из трёх вариантов кодек-стратегии (альтернатива — узкий набор кодеков или внешний процесс-декодер) lumen_core::ext::VideoDecoder/VideoDecodeSession реализованы в crates/engine/media-ffmpeg (FfmpegVideoDecoder/FfmpegSession), срез 4 Таблицы видео-кодеков (H.264/VP8/VP9/AV1/…), решение принимает спека (ADR-027). ffmpeg-sys-next's bindgen-генерация FFI на целевой x86_64-pc-windows-msvc даёт несовпадающий layout для набора структур FFmpeg И libc's struct tm (см. ADR-030) — этот путь остаётся отклонён. Ревизии 2026-09-19 (срезы 2-3) подтвердили живым прогоном в scratch-PoC (D:\Temp\ffmpeg-ffi-poc, не коммитился), что обходной путь работает целиком, включая декодирующий цикл и sws_scale. Срез 4 перенёс это в реальный крейт crates/engine/media-ffmpeg: hand-rolled FFI (src/ffi.rs), VideoDecoder/VideoDecodeSession impl поверх custom AVIOContext (декодирует &[u8] в памяти, не путь на диске — обязательное условие для сетевого/Blob-ответа <video>), av_seek_frame+avcodec_flush_buffers для frame_at(secs), build.rs резолвит FFMPEG_DIR. Крейт спрятан за feature ffmpeg (default off): без неё cargo build --workspace не требует FFmpeg вовсе ни на одной машине. Живые тесты (cargo test -p lumen-media-ffmpeg --features ffmpeg, нужен FFMPEG_DIR + DLL на PATH) декодируют 2x2-green.webm (VP8/9) и test.mp4 (H.264) из tests/wpt/. Не сделано: подключение к crates/js/src/video_bindings.rs's resource-selection алгоритму (следующий срез), лицензионная заметка про GPL-компоненты FFmpeg-дистрибутива (не cargo-deny-проблема — в Cargo.lock не появилось ни одной новой лицензируемой Rust-зависимости, поскольку FFI ручной; риск — в связанных DLL, а не в графе Cargo)
cc (build-dependency lumen-js, с 2026-09-19) Сборка ОДНОЙ C++-единицы трансляции crates/js/cpp/undetectable.cc — локального биндинга к v8::ObjectTemplate::MarkAsUndetectable() и SetCallAsFunctionHandler() (GAP-DOCALLDDA/BUG-1057). Оба символа уже лежат в прекомпилированной rusty_v8.lib, но Rust-обёрток к ним крейт v8 не даёт, а патч его binding.cc потребителю не помогает: тот файл компилируется только под V8_FROM_SOURCE — (вызов хост-тулчейна C++; тем же cc пользуется и сам крейт v8) 3 Удаляется целиком, как только denoland/rusty_v8#2078 выйдет в релизе крейта — обёртка тогда приедет из самого v8

Принципы работы с provisional-категорией:

  • Trait-anchor обязателен. Перед добавлением dep в Cargo.toml сначала появляется trait в lumen-core::ext и default-имплементация (наша, заглушечная или wrapped-around готового crate). Это гарантирует, что замена в будущем — drop-in, без переписывания потребителей.
  • Подключение «по событию», не превентивно. Не добавляем icu4x пока bidi реально не понадобится; не добавляем quinn пока HTTP/3 не на повестке.
  • Annual review. Раз в год — проход по provisional-списку: какие graduation criteria сработали → завести задачу на свой код; какие нет → продлить.
  • Расширение списка — через DECISIONS.md. Каждое добавление в provisional — новая запись в DECISIONS.md с обоснованием и graduation criterion.

Что НЕ берём как зависимости (даже временно — ядро Lumen)

Эти крейты регулярно обсуждаются как «возьми готовое», но для всех решение — отвергнуть. Это идентичность проекта: места, где решение принимаем мы (ADR-027).

  • html5ever → свой HTML-парсер по HTML5 spec (см. §6.1).
  • cssparser + selectors → свой CSS-парсер по CSS Syntax L3 (§6.2).
  • stylo → свой каскад и computed values (§6.4).
  • taffy → свой layout: block, inline, flex, grid (§6.5).
  • hyper → свой HTTP/1.1 и HTTP/2 поверх rustls + std (только HTTP/3 через provisional quinn).
  • hickory-resolver → свой DNS-резолвер с DoH/DoT поверх rustls.
  • encoding_rs → свои таблицы декодирования (cp1251, KOI8-R, CP866, UTF-8, ASCII, Win-1252).
  • unicode-security → свои homograph checks для IDN.
  • adblock (Brave) → свой filter matcher.
  • readability → своя реализация readability heuristics с настройкой под кириллицу (§10.9).
  • postcard + serde → своя компактная binary serialization для IPC.
  • tokio → свой минимальный async-исполнитель поверх std + epoll/kqueue/IOCP (или single-threaded на старте).
  • egui / iced / Slint → свой иммедиат-режим UI поверх wgpu-примитивов из paint-крейта.
  • flate2 / miniz_oxide для PNG — запись отменена 2026-08-31. Решение не выполнено и выполнено не будет: своего PNG-декодера нет (обёртка над zune_png), своего DEFLATE нет (flate2 в crates/network/src/flate.rs, miniz_oxide в WOFF1). Историческая запись — DECISIONS.md.

Покинули этот список 2026-08-31 по ADR-027 — во всех четырёх случаях решение принимает спека, а не мы:

Крейт Почему ушёл Задача
rustybuzz Свой шейпер закрыт задачей U-2 как done и покрывает GSUB 1/4 из 8, GPOS 1/2 из 9; диакритика не прикрепляется, сложных письменностей нет LIB-1…LIB-3
ttf-parser Приходит транзитивно с rustybuzz. Свой парсер таблиц остаётся — растеризация, метрики, variable/color fonts наши LIB-1
resvg / usvg Исходная причина (до замены): в пути отрисовки SVG ноль упоминаний linearGradient/radialGradient/url(#…); нет clipPath, mask, filter, pattern, marker, <image>. Текущее состояние — см. Permanent exceptions выше LIB-4 (done 2026-09-01, внешний SVG через сам resvg/usvg), LIB-5 (done частично 2026-09-01 — только градиенты inline SVG, свой tree/cascade сохранён: публичное API usvg pub(crate)-закрыто, целиком непригодно как библиотека), LIB-9 (done частично 2026-09-01 — <mask> inline SVG, wgpu-путь пока с багом BUG-936)
url Конечный автомат WHATWG против ~370 своих строк; официальный корпус urltestdata.json уже лежит в tests/wpt/url/resources/ LIB-0 (done), LIB-6 (done 2026-09-01, обязательная зависимость — см. Permanent exceptions выше)
tiny-skia Уже зависимость (crates/engine/paint/Cargo.toml, бэкенд cpu-render) — запись была неверна фактически —

Devtools (не runtime — допустимы)

Инструменты, которые не попадают в бинарь, но используются на CI / при разработке:

  • cargo-deny — аудит лицензий и CVE четырёх exceptions и их транзитивных зависимостей.
  • cargo-vet — supply-chain reviews.
  • cargo-dist — упаковка релизов (опционально).
  • cross — кросс-компиляция на CI.
  • cargo-hakari + workspace-hack (internal crate) — feature-unification: 55 общих транзитивных зависимостей пришпилены к единому набору фич; устраняет перекомпиляцию при переключении между -p lumen-shell и -p lumen-driver. Категория: постоянная dev-инфраструктура; публикации нет; workspace-hack — path-dep только внутри workspace. Добавлено 2026-07-13 (§3.4 docs/build-speed.md).
  • tracy-client — визуальный профайлер (Tracy GUI, https://github.com/wolfpld/tracy), фича tracy (lumen-core/lumen-layout/lumen-shell), не в default. В отличие от остальных пунктов этого раздела — МОЖЕТ попасть в собранный бинарь, но только при явном --features tracy; обычная сборка (cargo build -p lumen-shell) не тянет его вовсе (dep:tracy-client опционален, макрос lumen_core::tracy_zone! компилируется в no-op без фичи). Категория: provisional (см. workspace Cargo.toml, комментарий над версией). Добавлено 2026-07-15 (BUG-284 perf investigation, docs/plan/security-performance.md §14.3).

Принцип «no new dep без обоснования»

Если в коммите / Pull Request добавляется новая зависимость в Cargo.toml, в описании обязателен пункт:

Why this dependency: <обоснование, почему свой код тут категорически неуместен — иначе пишем сами>

CI-чек на новые [dependencies]-строки добавим, когда появится remote.

Язык и тулинг

  • Rust edition 2024, MSRV — последний stable (сейчас 1.95).
  • cargo workspace.
  • Сборка релизов — xtask-крейт, опционально cargo-dist поверх.