Skip to content

Latest commit

 

History

418 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

POLER-Engine

CI License: POLER Source-Available v1.0 Rust 1.98

AI-Native Topographical, Resonant and Graph Search Engine — поисково-аналитический движок на Rust, спроектированный для вытеснения grep/ripgrep и слепого векторного RAG из архитектуры LLM-агентов.

poler-engine ~/book -q "нокс" --format ai-json | jq '.anchors[0].k_hop_relations'

Документация (v2.0): полная карта — docs/INDEX.md. Единая архитектура монорепозитория (M3–M7) — docs/UNIFIED_ARCHITECTURE.md · крипто-ядро Zig PND v8 — os/core/ (zig build test, 23/23) · Архитектура — docs/ARCHITECTURE.md · теория (ε, R(t), RaBitQ, R1CS) — docs/THEORY.md · история проекта — docs/HISTORY.md · справочник модулей — docs/MODULES.md · CLI — docs/CLI.md · форматы — docs/formats/ · глоссарий — GLOSSARY.md · контрибуция — CONTRIBUTING.md · план монорепозитория (истор.) — docs/MERGE_PLAN.md. Этот README — прежде всего чейнджлог; нижние секции частично описывают старые версии (v0.3.x), актуальная структура — в docs/MODULES.md.


v0.48.0: Калькулятор Всего — calc / = / poler_calc (цикл M)

Универсальный вычислительный полигон внутри poler-shell — от бытовой арифметики до планковских единиц, квантовых вращений Ли и генерации скриптов по законам физики. Три входа: REPL calc … · префикс = … · --exec "calc 2^10" --json; в TUI — клавиша = открывает виджет с живым preview; агентам (Antigravity) — MCP-инструменты poler_calc + poler_hw (резидентное состояние: переменные и ans живут между вызовами).

poler> calc (1538 * 485) / 1024          = 728.447265625
poler> = 5 km + 300 m                    = 5.3 km          # размерности строго
poler> = 100 km/h to m/s                 = 27.777777777777779
poler> calc solve x^2 - 4 = 0            x ∈ 2.0, -2.0     # Дюран–Кернер
poler> calc solve sin(x) = 0.5           π/6 + … (Ньютон + бисекция)
poler> calc expm([0,-1;1,0] * psi)                          # вращение Ли, Паде [6/6]
poler> calc eigen(rot2(pi/2))           = [i, -i]           # чисто мнимые
poler> calc charpoly([1,2;3,4])         = [1, -5, -2]       # Фаддеев–Леврерье
poler> calc trits(5)                    = "1TT"             # тритная щель POLER
poler> calc moon_illum(2024,4,8,18.35)  ≈ 0.0               # солнечное затмение 08.04.2024
poler> calc dist(50.45,30.52,49.84,24.03) = 462.1…          # Киев—Львов, большой круг
poler> calc zeta(2)                     = 1.6449340668…     # π²/6
poler> calc script kepler3 a=1 au                           # скрипт + .poler-правило
poler> hw                                # скрытые параметры ПК (кеш/ISA/GPU/NUMA)

Ядро src/calc/ (~4700 строк + ~1500 тестов, 128 unit + интеграционные):

  • Выражения — лексер/парсер/вычислитель: неявное умножение (2pi, x(y+1)), правоассоц. ^, факториал через Γ, строки, присваивания, уравнения; регрессионный тест на «враждебный» ввод (инцидент вечного цикла лексера прошлой сессии закрыт инвариантом «каждая ветка двигает i»);
  • Единицы — 80+ табличных (СИ, IEC KiB..PiB, империя, астро, инфо), составные km/h, J/(mol*K), аффинные температуры degC(100) to degF, c и как константа, и как единица скорости; регистрозависимость СИ (t тонна ≠ T тесла, c скорость ≠ C кулон);
  • Константы — CODATA 2022 / IAU 2015 / СИ-2019 с источниками; производные (σ Стефана–Больцмана, планковская система, R) вычисляются из законов, а не копируются цифрами;
  • Специальная математика — Γ Ланцроша, erf (A&S 7.1.26), ζ Эйлера–Маклорена с функциональным уравнением (ζ(−1) = −1/12), β;
  • POLER Matrix Calc — expm (масштабирование + Паде [6/6]), det/inv/trace, charpoly Фаддеева–Леврерье (прибавление только к диагонали), собственные значения Дюрана–Кернера, генераторы Ли so(n), expm(J·Ψ) — роторная фаза живого голоса как число;
  • Триты — сбалансированная троичная арифметика + вентили Клини (AND=min, OR=max, NOT=−);
  • Теория чисел — детерминированный Миллер–Рабин (u64), ρ-Полларда (Брент), next/prev_prime, фибоначчи удвоением, биномиальные;
  • Астрономия — модель Шлhyter (Солнце/Луна/планеты; возмущения Юпитера–Сатурна) + NOAA (восход/закат); верифицирована якорями реальных затмений: солнечные 08.04.2024 и 29.03.2025 (новолуние ±2°), лунные 14.03.2025 и 08.11.2022 (полнолуние ±2°), равноденствия/ солнцестояния 2024 (0/90/180/270° ±1.5°);
  • Геодезия/навигация — большие круги, азимут, середина, прямая задача (Aviation Formulary), радиус кривизны WGS84;
  • Зонд железа hw — кеши L1d/L1i/L2/L3 из sysfs, ISA-флаги (AVX/AVX-512/AES…), топология (сокеты/ядра/потоки/NUMA), диски, GPU (nvidia-smi или PCI sysfs), гипервизор; текст и --json;
  • Генератор скриптов — 19 законов (Кеплер, Циолковский, Шварцшильд, Стефан–Больцман, Гоманн, Рош, Планковская система…): подставляет значения (с единицами!) в готовую команду calc и излучает машинное .poler-правило [[rule]]; инвариант — выражение каждого закона обязано вычисляться движком (проверяется тестом).

Тесты: 1528/1528 (движок), из них 128 calc + 10 интеграционных calc/hw/= в шелле. Все баги, найденные при пересборке с нуля (цикл M умер до коммита — код восстановлен по журналу сессии), закрыты регрессионными тестами: лексер-вечный-цикл, «to» в неявном умножении, x = x в solve, заём тритов при остатке 2, Паде b4 = 1/792, Фаддеев add-только-диагональ, перигей Луны 0.1643573223 °/день, радианы в освещённости, внешний множитель t в erf, дзета Эйлера–Маклорена.

v0.42.0: winpe — Windows PE32+ нативно в коробке (Linux + Windows в одном контейнере)

poler-box теперь исполняет нативные Windows-бинарики (PE32+ AMD64) внутри той же изолированной коробки — без Wine и без виртуализации:

poler-engine --poler-box windows.poler --box-entry rootfs/bin/7za.exe --box-arg i
poler-engine --winexec 7za.exe            # прямой запуск с хоста

Архитектура src/winpe/ (~3900 строк Rust, порт идей poler-os pe.zig/win32_crt.zig):

  • runtime.rs — отображение образа, DIR64-релокации, IAT → тunki (mov r10,id; mov rax,bridge; jmp rax), asm-мост SysV↔Win64 (id через R10 — volatile в обоих ABI), TEB/PEB через arch_prctl(ARCH_SET_GS), стеки с slack-зонами;
  • pe.rs — PE32+ парсер (секции, импорты, .pdata);
  • crt.rs — msvcrt: полный printf-формтер (varargs по регистрам + XMM-spill
    • va_list-режим), __getmainargs (argc значением!), _initterm (пропуск NULL), qsort с компаратором на текущем стеке;
  • api.rs — реентерабельный World (Gate: владелец=tid, без самодедлоков в SEH) + kernel32 (~150 шимов: файлы, поиск с WIN32_FIND_DATAW cFileName@44, критические секции на futex, потоки с собственными TEB);
  • api_ext.rs — CRT-IO, 64-битные мат-хелперы msvcrt, user32/advapi;
  • seh.rs — собственный walker x64 C++ исключений: .pdata/UNWIND_INFO (слоты, а не коды!), FuncInfo в формате WINE x64 (nTryBlocks@+0x0C, IP-to-state@+0x18), деструкторы → catch-продолжение (RAX), C-SEH scope-таблицы, hop-механизм для обхода кадров Rust-слоя.

Приёмка (e2e): 7-Zip 21.07 x64 — полный список форматов/кодеков/хешеров, exit 0; LZMA-бенчмарк: детекция Xeon A06D1, частота ~3.56 ГГц через QPC-шим, 4 потока; tcc 0.9.27 (Linux) в той же коробке — регрессия зелёная. Демо-архивы: HF VitalijKotok/poler-chromium-src/box-demos.

v0.41.0: poler-box — «замена Docker без ОС»: циклическая обёртка выполнения .poler

Директива пользователя: исполнять тяжёлые payload (вплоть до сборки Chromium) изнутри архиватора, с остановкой пожирателей ресурсов, без распаковки на диск; «коробка без ОС», непробиваемая изнутри; железо и память — нативные хостовые, но недостижимые. Это не криптография и не виртуализация — это циклическая обёртка среды выполнения на примитивах ядра.

Модель изоляции (три процесса)

P  poler-engine (CLI)          — fork/exec, губернатор (RSS/CPU дерева), отчёт JSON
C1 └─ /usr/bin/unshare -Ur —   — привилегированный хелпер: userns + мапа 0↔uid
    └─ poler-engine (stage2)   — unshare(mnt/pid/net/ipc/uts), tmpfs rootfs,
                                 стрим записей из .poler, pivot_root, rlimits
        └─ D = payload (pid 1) — execveat(memfd записи архива) + seccomp
  • пути: pivot_root на tmpfs (RAM) — ФС хоста исчезает из виду;
  • сисколы: seccomp — белый список ~150 + KILL_PROCESS для 40 смертельных; socket(AF_INET/INET6/NETLINK/PACKET) → EPERM (сетевой выход запрещён);
  • процессы: pidns (payload = pid 1, хостовых pid не существует);
  • память/CPU: губернатор поллит дерево VmRSS/utime каждые 50 мс, SIGKILL при превышении (--box-rss-mb/--box-cpu-s) + rlimits-кордоны;
  • zero-disk: rootfs коробки = tmpfs, куда стримятся записи .poler; сам payload исполняется из memfd (execveat AT_EMPTY_PATH).

Почему exec /usr/bin/unshare: кастомное ядро песочницы (kangaroo) отклоняет запись uid_map от неизвестных его политике бинарников (свежескомпилированные — EPERM, util-linux — проходит). Поэтому userns устанавливает доверенный unshare — стандартный паттерн rootless-контейнеров (аналог newuidmap).

Цикличность

poler-box запускает poler-engine как запись архива внутри коробки, который открывает другие архивы (переданные в rootfs): обёртка внутри обёртки, без конца. E2E: engine из engine.poler верифицирует mini.poler внутри коробки → all_ok: true.

Использование

# изоляция + отчёт
poler-engine --poler-box tools.poler --box-entry rootfs/bin/hello

# бюджета ресурсів + аргументы payload (=-синтаксис против clap)
poler-engine --poler-box tools.poler --box-entry rootfs/bin/hog --box-rss-mb 150
poler-engine --poler-box tcc.poler --box-entry bin/tcc --box-map=:/ \
    --box-arg=-run --box-arg=/src/hello.c

# мапа записей: префикс архива → каталог коробки (rootfs/:/ по умолчанию,
# ":/" — весь архив; tmpfs-бюджет --box-tmpfs-mb)

Приёмка в песочнице (2 vCPU, cgroup 4 ГБ, ядро kangaroo 5.10)

Тест Результат
hello (изоляция) pid=1, uid=0-in-userns, /etc/passwd ENOENT, AF_INET EPERM, /proc замаскирован
escape (смертельные) mount() → SIGKILL от seccomp, вывод оборван
hog @ 150 МБ kill_reason=rss_limit, peak 231 МБ, wall 0.13 с
spin @ 3 с CPU kill_reason=cpu_limit, cpu 3.05 с
tcc -run компиляция внутри архива: peak RSS 2.4 МБ
cyclic engine из архива → verify мини-архива → all_ok: true

Найденные и исправленные баги по пути: read_status_field не пропускал TAB после двоеточия (губернатор был слеп к VmRSS); опции mount передавались без NUL-терминатора (EINVAL в зависимости от кучи); режим ld-linux не был исполняемым (EACCES на интерпретаторе динамических payload).


v0.40.0: In-Place CoW-патчер .poler — редактирование гигабайтных архивов без распаковки

Директива пользователя: править исходники Chromium внутри суверенного архива chromium_full.poler (1.49 ГБ → 507 271 запись после ремукса) прямо в контейнере, zero-disk. Результат приёмки: 4 патча Blink + версионный маркер применены in-place за 27 с, --poler-verify: 507 272/507 272 OK.

  • src/archive/patcher.rs — Copy-on-Write эволюция контейнера:
    • patch_archive(): replace/add/delete записей БЕЗ распаковки. Новые данные нарезаются FastCDC, сжимаются (Auto 3/15) и аппендятся поверх старого трейлера; логический индекс и файловая таблица переливаются стримингом (RAM O(чанк), не O(архив)); трейлер перезаписывается последним. Заменённые чанки остаются «мёртвыми» байтами — дедуп будущих патчей может их оживить.
    • Честный пересчёт stream_sha256 всего логического потока при каждом патче (5.32 ГБ за 27 с) — --poler-verify сходится всегда.
    • Откат: <архив>.polerbak (120 Б: старая длина + старый трейлер); --poler-rollback восстанавливает байты дословно (проверено cmp).
    • Все операции валидируются ДО первой мутации: кривой манифест не трогает ни байта.
  • --poler-remux: .poler с tar.gz-блобом → .poler с файловой таблицей (gzip-декодер → CDC → zstd, стриминг 256 КиБ кусками). Chromium: 1.49 ГБ блоб → 1.44 ГБ файловый контейнер (ratio 0.27), 507 271 запись, 306 с, VmHWM 1.49 ГБ.
  • --poler-cat <POLER> --file <NAME> — байты записи в stdout: движок как замена bash-распаковки (poler-cat … | grep …).
  • CLI: --poler-patch <POLER> --manifest <JSON> (replace/add/delete, data|file), --poler-rollback <POLER>, --force-bak.
  • Фикс cat_file: read_range очищает буфер — копим в отдельный piece-буфер с проверкой длины каждого куска.
  • Тесты: 8 новых (replace grow/shrink, add/delete, дедуп повторного патча, rollback байт-в-байт, guard .polerbak, multichunk 5 МиБ, ремукс tar.gz + патч поверх, невалидный манифест не мутирует); полная библиотека 1329 passed / 0 failed.

v2.0 (в разработке): Sovereign Stack — ступени плотности

Обновлённые ступени v2.0 (полный план — PLAN_POLER_V2.md):

  • v0.39.0 «Zero-Disk Streaming Ingestion Pipeline» — гигабайты из сети в кристалл без сырой выгрузки на диск — реализация директивы docs/DIRECTIVE_STREAMING_INGESTION_PIPELINE.md для систем с ограниченным диском (<10 GiB free при датасетах >100 GiB):

    • .poler-контейнер (src/archive/): FastCDC content-defined chunking 64 KiB–1 MiB (нормализованные фазы A/B, детерминированная gear-таблица — нарезка воспроизводима между запусками и архивами), BLAKE3-дедупликация (u64-префикс в RAM ~24 Б/чанк + полная верификация pread'ом заголовка — ложных склеек нет), многоярусный zstd (авто: 3/15 по сжимаемости чанка), tar-наблюдатель (границы файлов + их SHA256 на лету, GNU longname + base-256 размеры), атомарная запись через .part+rename.
    • Zero-copy ридер: mmap + O(1) трейлер + O(log n) поиск чанка, случайный доступ к любому байту, verify по SHA256 потока и каждой записи, распаковка с защитой от zip-slip; MADV_DONTNEED после каждого чанка — резидентность mmap не копится в RSS.
    • Потоковый браузер (src/browser/stream_ingest.rs): HTML разбирается чанками по мере прихода из HTTP (страница НИКОГДА не собирается в RAM целиком), <script>/<style> выбрасываются на уровне токенайзера, ε-фильтр плотности отделяет навигацию/баннеры от семантики (код и таблицы сохраняются); StreamingFetcher поверх ureq — краул без Chromium (--browser-crawl, robots+politeness наследуются из краулера).
    • .poler → кристалл (feed_poler): записи таблицы накладываются на поток разжатых чанков, тексты льются в StreamCrystalBuilder — без распаковки архива на диск, NUL-снифф отсеивает бинарники.
    • CLI: --stream-download URL --output-archive x.poler [--dedup], --stream-file FILE|-, --stream-bench 10G (синтетика приёмочного размера), --browser-crawl URL --ingest-to-crystal memory.t5c, --archive-to-crystal x.poler --crystal memory.t5c, инспекция --poler-list/--poler-verify/--poler-extract.
    • Приёмочные цифры (2 vCPU, release): 10 GiB поток → 1.34 GiB на диске (13.4%), пик RSS писателя 23 MiB (бюджет директивы 48), lossless SHA256 ✓, скорость записи ~41 МБ/с; кристалл из текстового архива: 200 MiB → .t5c за 4 с (50 МБ/с, RSS 12 MiB, слов корпуса 22.9 млн); worst-case синтетика с мусорными блоками — 32 МБ/с / RSS 73 MiB (капы --learn-word-cap/--learn-bigram-cap).
    • Сопутствующие фиксы ядра: (1) дисциплина отрицания — частица «не»/«ни» в запросе отключает proximity-фолбэк (регрессия 7ee6b98: случайная «не» из окна ±128 токенов находила «не обязана» в тексте без неё — инверсия смысла); (2) паника Crystal::build на корпусе <32 уникальных слов (clamp(32, n<32)); (3) квадратичный drain в push_chunk на битых UTF-8 (курсор вместо memmove, 15 с → 0.56 с на 20 MiB); (4) ASCII-быстрый путь + FxHash в инжесте кристалла (×1.8 пропускной способности); (5) компакция id-пространства при эвакуации слов — words/counts больше не растут с числом мёртвых id (RSS кристалла ограничен капами, не потоком).
  • v0.38.1 «poler-edit» — суверенное ядро текстового редактора без лимитов — file-size limits сняты архитектурно: zero-copy mmap piece-table (16 MiB куски-метаданные, append-only edit-буферы, snapshot undo/redo, atomic save), ленивый параллельный SIMD line-index (memchr + rayon + MADV_SEQUENTIAL/DONTNEED — RSS не зависит от размера файла), поиск Aho-Corasick с переносом совпадений через швы кусков/правок. Протокол --edit-serve (JSON lines, LSP-стиль, мультидокументность, прогресс, отмена) + GUI-клиент Kate-подобного вида на чистом Qt6 (integrations/poler-edit-qt) + --edit-bench для открытых цифр. Замеры (2 vCPU): открытие 100 GiB — 0.12 мс; индексация 304 MiB / 2.5 млн строк — 8.1 GiB/s (быстрее wc -l на том же файле); поиск — до 25.8 GiB/s; peak RSS — 33–40 MiB независимо от объёма. Полная витрина — docs/POLER_EDIT.md.

  • v0.38.0 «Нейронная популяция поверх трит-синапсов» — синапсы + нейроны = скорость и память — ответ на директиву «я написал синапсы, но не нейроны». Каждый токен словаря — нейрон с активацией; Trit5-решётка биграмм — синапсы. Динамика — три потока: (1) синтаксическая волна — направленные биграммные триты с ротором J = (W−Wᵀ)/2 (та же антисимметричная фаза, что крутит касту мухи); (2) семантический бассейн — симметричное Хеббовское замыкание (совместная встречаемность bigram(i→j)=+1 ИЛИ bigram(j→i)=+1 + топ-k CSE-соседей): тематический кластер держит взаимную поддержку — контекст живёт в активациях, а не в окне (промпт помнится за пределами ctx_window=2 на 20+ шагов, диагностировано плато 0.1–0.14); (3) мультипликативное торможение a·(1−μ·I) — обратные синапсы ослабляют, но не стирают источник (аддитивное торможение схлопывало популяцию в один нейрон за 2 шага — «эпилепсия», найдено пошаговой диагностикой). Энергогейтная пластичность: ε = κ·‖obs − thought‖² (удивление = расстояние между сказанным словом и предсказанием популяции), plasticity = 0.2·ε; Хеббовский сдвиг трита ±1 при совпадении с NMDA-гейтом — обучение во время речи, один проход, без эпох (демо: 26 обновлений синапсов за фразу из 30 токенов). Выученный кристалл сериализуется: --triune-speak "…" --triune-out memory.t5c (round-trip побитовый, тест). CLI: --triune-no-learn, --triune-out; трейс токена несёт act — активацию нейрона. 15 новых тестов: резонанс вдоль +1-синапсов, тормоз по −1, WTA- разреженность, детерминизм, усиление/ослабление/кристаллизация синапсов, память промпта за окном, побитовая заморозка при learn=false, round-trip выученного кристалла. Итог: 1245/1245.

  • S1/v0.35.0 «Синаптический Вихрь SSN» — живой мозг, доказанный до реализации — субстрат управления, извлечённый из Google-Drive-архивов пользователя (226 уравнений, 109 алгоритмов, 5 каталогов-отчётов) и доказанный численно ДО написания ядра: верификационный набор proofs/ssn_verify3.py67/67 проверок, попутно найдено и исправлено 13 режимов отказа (F1–F13, включая ошибку знака в EQ-A22 из исходных диалогов, bang-bang гомеостаз по мгновенной активности, несоответствие порогов метрики и windup-насыщение весов, найденное уже Rust-портом). Полный стек шага: виртуальная топология tgt = (i·39293 + f·29101 + seed·73471) mod N — связь существует как арифметика, ноль RAM на граф (100M синапсов < 1 ГБ); фазовые синапсы cos(phase + 0.3·θ_mod); латеральное торможение; STDP сквозь дофаминовые ворота; гомеостаз-интегратор по медленному следу f_sys; ретикулярный тон b_tone (анти-windup); E/I-контроллер (мёртвая зона [2,6]); нейромодуляторы DA/5HT/NE; ритмы θ/γ. Здоровый мозг: активность ~5% с лавинными флуктуациями (критичность, край хаоса), S < 0.8, E/I ≈ 4 — устойчивая динамика, а не равновесие. CSE-сенсорика с золотой фазой (c·φ mod 2π) даёт разделимость текстов 1.35 (похожие cos = 0.999, непохожие −0.355, побитовый паритет Rust↔Python). Резидентные живые мозги в MCP (poler_ssn_step/inject/status/eject, LRU 8) + CLI --ssn-demo/--ssn-encode/--ssn-inject. ~4200 шагов/с (600×16, release). Доказано: 10 seed × 10k шагов (Python и Rust), 1268/1268 тестов с pnd-ffi. Руководство: docs/SSN.md.

  • L1/v0.34.0 «Литературный Двигатель POLER[Ψ]» — физика смысла, калиброванная живым мозгом — полная матричная форма канонического уравнения POLER[Ψ] (скалярный предшественник — src/psi.rs): замысел превращается в инвариантный вектор Ω(o) (детерминированный FNV-хэш термов по осям фазового пространства, ноль RNG, ‖Ω‖ = 1 — энергия замысла фиксирована входом), и далее система эволюционирует по p_{t+1} = p_t − η·Π_Λ(∇F + D·p + γJ·p) + η_r·Π_Λ(κ·(echo − p)): проектор причинности Π_Λ = I − J_cᵀ(J_cJ_cᵀ)⁻¹J_c математически аннигилирует галлюцинации (нарушения законов pᵢ − pⱼ = 0), темпоральное эхо R[n] = Σ 0.9ᵏ·p_{t−k} держит нить повествования интегралом состояний (бесконечный контекст без раздувания окна), а муха калибрует динамику: каста нейронов FLYCSR1 (BFS от семян, жадный детерминированный отбор) даёт ротор J = A − Aᵀ — циркуляцию смыслов (γJ·p: живой мозг закручивает нарратив) и метрику Ляпунова D = L·Lᵀ (грамиан роторных масс касты гасит пертурбации). Два режима физики: без мухи — сходимость к «информационной сверхпроводимости» H^Ψ = 0 (F → 1e-5 за ~33 шага); с мухой — предельный цикл (F ≈ 0.26): живой мозг не даёт нарративу замереть. Принцип «No Excuses»: при семантическом тупике (F растёт 3 шага подряд) энергия смысла преломляется призмой в ближайший архетип с ТОЧНЫМ сохранением нормы (Закон Сохранения Смысла) — отказа от генерации не существует. Trit5/No-Mul: латентное состояние квантуется в {−1,0,+1} (кодек pqc, 5 тритов/байт), резонансные скалярные произведения — AVX2 без f32-умножений. MCP: 4 инструмента poler_literary_field/_step/ _generate/_eject (резидентные сессии LRU 8, мушиная калибровка переиспользует WarmFly, пул воркеров); CLI: --literary-field/ generate + --literary-csr/nodes/seeds/khop/max-cast/dims/steps/ eta/eta-r/rho/kappa/gamma/lambda/no-mul/json. Драматургические конфликты = роторные пары нейронов («13609 [Тишина] доминирует над 41414 [Сверхпроводимость], J = +1» — золото v783). Доки: docs/LITERARY.md. Тесты: 1238/1238 pnd-ffi (+46) + 1176/1176 default (+46).

  • C2/v0.33.0 «Живая муха» — коннектом как резидентный объект допроса — C1 давал CLI-ридер (каждый вызов платил 60–400 мс загрузки артефакта + 43 мс CSC); C2 переводит мозг мухи FlyWire v783 в режим живого взаимодействия для ИИ-агентов. Ядро src/graph/flyops.rs — восемь операций над CSR-матрицей A: (1) neighbors — партнёры по синаптической массе; (2) shortest_path — BFS-маршрут u→v с цепочкой каждого синапса (вес/медиатор/знак); (3) common_partners — пересечение окрестностей набора: общие мишени (дивергенция) и общие источники (конвергенция); (4) degree_ranking — хабы; (5) pagerank — взвешенный ранг по |A| (модуляторы не проводят сигнал, фильтр знака сужает граф, L1-стоп); (6) rotor_top — глобальный топ циркуляции J = A − Aᵀ мин-кучей за O(m log k) с дедупликацией ориентации; (7) motif_census — реципрокные пары u⇄v, feedforward-треугольники u→v→w+u→w, feedback-циклы u→v→w→u; (8) propagate — симуляция динамики x(t+1) = leak·x + γ·A·x на знаковых весах: возбуждение разгоняет, торможение гасит — муха «думает» в RAM. MCP: 10 инструментов poler_fly/_node/_edge/_khop/_path/_common/ _centrality/_rotor/_motifs/_propagate поверх резидентного WarmFly (артефакт грузится ОДИН раз, CSC лениво один раз, нейроны — индекс или root_id) + пул воркеров: пачка запросов к мухе исполняется параллельно (release-смоук: 11 запросов, включая PageRank и ротор-скан, — 0.49 с). CLI: --connectome-neighbors/path/common/centrality/rotor-top/motifs/ propagate + тюнинг (dir/limit/top/min-abs/steps/gamma/leak). Наука из золотых чисел: сильнейший однонаправленный поток ядра — J[74067][133436] = +2395; хаб 79529 (топ-тормозитель узла 0) сам гигант — 6399 исходящих, 3684 реципрокных пары, 11 999 feedforward; симуляция от узла 0: [14, 455, 15 457] активных за 3 шага, торможение доминирует (|−3465| > +1285) — сеть мухи гасит сигнал. Тесты: 1192/1192 pnd-ffi (+15) + 1130/1130 default (+15).

  • E2/v0.32.0 — poler_exec: устранены все узкие места инструмента — стресс-аудит выявил 7 ограничений E1; каждое закрыто на уровне ядра os/core/poler_exec.zig: (1) терялась ГОЛОВА вывода — режим capture=head_tail: первые B/2 + маркер «dropped N» + последние B/2 (стек-трейс в начале гигантского лога больше не теряется); (2) PTY — /dev/ptmx + TIOCGPTN/UNLOCK/SWINSZ + setsid + slave: настоящий терминал 200x50, isatty-программы (sudo/fzf/htop) работают, stdout/stderr слиты; (3) PATH сканировал родитель stat'ами — execvp-семантика перенесена В РЕБЁНКА (ноль stat до fork); (4) cwd — chdir в бутстрапе ребёнка (многопоточный родитель трогать процесс-глобальный cwd не может; провал → errno + exit 125); (5) отмена — атомарный cancel_flag: TERM→grace→KILL замечается за ≤25 мс; (6) env — явное окружение (CLI --exec-env, MCP env); (7) утечка in_wr при раннем выходе — страховочный close. MCP: фоновое семейство poler_exec_async/task/ kill/list (реестр задач, лимит 128) и ПУЛ ВОРКЕРОВ в stdio-цикле — параллельный запуск (2x sleep 1 = 1.0 с стеновых), ответы внеочерёдно по готовности. CLI: --exec-cwd/--exec-env/--exec-pty/--exec-capture. Тесты: 21 Zig + 1177/1177 pnd-ffi (+13) + 1115/1115 default.

  • E1/v0.31.0 — Идеальный исполнитель команд poler_exec — рождён диагностикой исходников GNU bash 5.2 самим движком (docs/EXEC_AUDIT.md: 424 unsafe-строковых вызова в 75 .c-файлах, free() в trap-механике trap.c:839, REINSTALL_SIGCHLD-гонка jobs.c:144/319, неограниченный $(...), ноль таймаутов на детях). Ядро os/core/poler_exec.zig — Zig с raw-syscall слоем на ассемблере: ребёнок между fork и exec выполняет только syscall-инструкции (dup3 → setpgid → close_range(CLOEXEC) → execve). Гарантии by design: жёсткий таймаут (timerfd MONOTONIC + SIGTERM → grace → SIGKILL группе с безопасным наведением), кольцевой захват хвоста вывода (O(1) памяти при любом объёме), зомби-невозможность (pidfd-пробуждение + wait4(WNOHANG) в ppoll-цикле), шелл-инъекции невозможны (argv массивом). Rust-мост src/exec/mod.rs (типизированные ошибки), CLI --exec (коды: ребёнка/124/127/126), MCP-инструмент poler_exec. Тесты: 11 Zig + 9 Rust + CLI/MCP-смоук — 1164/1164. Боевой журнал пяти багов разработки (включая setpgid 154→109 aarch64→x86_64) — в docs/EXEC_AUDIT.md §5.

  • Шаг 1 (4b2b282) — отвязка от Google/NotebookLM/Gmail/Drive/OAuth: суверенный стек, никаких внешних облачных API.

  • Шаг 2 (1354789, 53cc31b) — Foundation: madvise, whatlang, Snowball, UAX#29, Teddy SIMD (2.7× к Aho-Corasick на предфильтре).

  • Шаг 3 (Compression) — плотность памяти индекса:

Представление Было Стало Выигрыш
Словарь корпуса (терм → частота) HashMap<String, usize> ~64–96 Б/терм FSST-арена: сжатый blob + interning + compress-probe индекс 4.1× RAM (бенчмарк)
Пер-файловые словари watcher'а HashMap<String, usize> пары (term_id, count) — 8 Б/запись 8.0× RAM
Термы словаря сырые байты FSST (порт эталона Boncz/Neumann) 3.05× blob
Постинги hits/hit_keys сырые Vec<u32>/Vec<usize> lz4_flex-парковка 1.4×
Doc store веб-краулера pages.text SQLite zstd-BLOB со обученным словарём 26×

Кирпичи (контур 4 бенчмарка, --benchmark):

  • src/compression/fsst.rs — чистый порт FSST (Fast Static Symbol Table, Boncz & Neumann, CIDR 2020; адаптация Rust-порта из Apache Lance) с переделкой под poler: детерминированное обучение (вместо rand), кодирование одиночной строки с фиксированной таблицей — опора compress-probe лукапов (терм запроса сжимается тем же кодировщиком, сравнение по сжатым байтам, декомпрессии на горячем пути нет), безопасные unaligned-загрузки, компактная сериализация таблицы (~4.5 КБ) с побитово точным восстановлением кодировщика. VocabArena — двухфазный словарь: staging (до ~32 КБ термов) → обучение → compact (blob + offsets + open-addressing индекс); ID appending-only — стабильны между ресканами watcher'а.
  • src/compression/lz4_hot.rsPostingsStore: lz4-парковка постингов watcher-состояния, разжатие по требованию (~ГБ/с).
  • src/compression/zstd_dict.rsDocStoreCodec: pages.text → zstd-BLOB [P][Z][mode] + фрейм; словарь обучается на первых страницах и живёт в meta['zstd_dict'] (иммутабелен); старые БД читаются как плоский текст — ленивая миграция text_c.
  • Трейт TermFreqs — резонансные формулы (ε, IIR, POLER[Ψ]) принимают источник частот: сжатая GlobalStats или HashMap (мономорфизация, без dyn). Дифференциальный тест гарантирует побитовое совпадение ε между представлениями.
  • Попутно устранена недетерминированность calculate_epsilon (HashSet-порядок суммирования менял последний ulp — ранжирование было невоспроизводимо; теперь BTreeSet).
  • Шаг 4, кирпич 1 (Vector Substrate) — RaBitQ + poler-native HNSW:
Представление Было (fp32) Стало (1-бит RaBitQ) Выигрыш
Вектор 768-d (BGE-M3) 3072 Б 128 Б кода + 16 Б скаляров 24× по кодам, 21.3× всего
Вектор 64-d (nomic Matryoshka) 256 Б 8 Б кода + 16 Б скаляров 11× по кодам
Дистанция обхода графа fp32-cos, ~ГБ/с XOR + POPCNT симм. оценка 8.4 ГБ/с скан кодов
fp32 в индексе обязательны нет вообще — коды + 3 скаляра mmap zero-copy

Кирпичи (контур 5 бенчмарка, --benchmark):

  • src/vectors/rabitq.rs — чистый RaBitQ-класс (по мотивам Gao & Long, SIGMOD 2024): рандомизированное вращение Адамара (FWT за O(D·log D), знаки из сида — детерминизм) + 1-битные коды знаков центрированного вектора + скаляры mu/delta/gamma. Две оценки IP: sym — arcsin-MLE (E[⟨b̄x,b̄q⟩/D] = (2/π)·arcsin ρ — точно для jointly-Gaussian после вращения), путь XOR+POPCNT с runtime-детектом; ADC — центрированный запрос, (π/2)·γ-несмещённость, точные нормы в знаменателе косинуса. Self-IP несмещён (среднее 1.00±0.02).
  • src/vectors/hnsw.rs — poler-native HNSW над кодами (все готовые крейты хранят fp32 — это убивает плотность): вставка строго по слотам, эвристика разнообразия соседей (Algorithm 4) с keepPruned, сериализация графа. Бенчмарк 40K×768: граф держит 100% потолка оценщика (не теряет ни одного ранжируемого кандидата), p50 308 мкс (ef=96), сборка 10 с.
  • src/vectors/store.rsQuantizedStore (append-only сборщик, ID стабильны как в VocabArena) + QuantizedStoreView (mmap zero-copy, выравнивание кодов 8 Б проверяется при открытии); формат «PRBQ v1»: 12 Б скаляров + 4 Б id + D/8 Б кода на вектор.
  • src/vectors/mod.rs — трейт Embedder (точка подключения нативного .pqw-энкодера из кирпича 2) и HashEmbedder — детерминированная feature-hashing проекция для конвейерных тестов без модели (НЕ семантическая — общий словарь сближает, разный разводит).
  • Честная физика: recall@10 ADC-потолка на вырожденной гауссовой синтетике ~0.63 (внутрикластерный разброс ≤ шуму 1 бита на D≥768); на реальных эмбеддингах и с fp32-переранжированием (кирпич 2, сайдкар оригиналов) ожидается 0.85–0.98 по литературе RaBitQ.

Цена плотности: лукапы частот через compress-probe ~2.8× медленнее HashMap (250 нс против 90 нс на пробу) — на фоне mmap-сканов и материализации сцен незаметно. Цель «RAM индекса 5–10×» достигнута на парковке watcher-состояния (доминанта на корпусах 65K+ файлов).

  • Шаг 4, кирпич 2 (Sovereign ML — Part E/F PLAN_POLER_V2) — архитектура изменена владельцем: fastembed/ort/ONNX ОТМЕНЕНЫ, весь нейроинференс — нативный Rust через собственное ядро pqc (вендор-вынос из POLER-Quantum-RS), формат весов .pqw v2 (см. docs/formats/PQW_FORMAT.md):
Компонент ONNX Runtime (отменено) pqc-натив (реализовано)
Внешние зависимости libonnxruntime.so ~100 МБ C++ 0 — один статический бинарь
Формат весов .onnx 500 МБ .pqw: int8/int4, mmap, SHA-256, секции по страницам 4096
Энкодер (BGE-M3/GLiNER-класс) ort-сессия BERT post-norm forward на AVX2 weight-only кернелах
Декодер (GLM-класс) RoPE + MQA/GQA + SwiGLU + KV-арена + MoE-роутер
Целостность весов нет SHA-256 при открытии — подмена ловится до инференса

Модули: src/pqc/ (tensor — SIMD-кернелы dot int8/int4/f32, LayerNorm/RMSNorm, GELU-erf, SiLU, softmax, RoPE-таблицы; pqw — контейнер + билдер; encoder — BERT/XLM-R-спина; sha256 — свой FIPS 180-4; selftest), src/llm/glm_engine.rs (GLM-декодер: инкрементальный forward_pos, KV-арена без аллокаций в шаге, greedy/ temperature/top-p сэмплирование, детокенизатор UAX#29-правил), src/ner/native_gliner.rs (span-голова: [start ‖ end ‖ width_emb] → классификатор → sigmoid), src/vectors/pqw_bridge.rs (PqwEmbedder → трейт Embedder → RaBitQ-субстрат). CLI: --pqw-selftest (полный автономный цикл инференса: 6/6), --semantic dense --model X.pqw, --semantic-corpus PATH (живой семантический поиск по корпусу), --llm local --model X.pqw, --ner gliner --model X.pqw. Реальные веса (кирпич 2.5): конвертер scripts/convert_hf_to_pqw.py (torch-zip → .pqw int8/int4 потоково, ноль ML-зависимостей, numpy only)

  • нативный XLM-R-токенизатор src/pqc/tokenizer.rs (Unigram-Viterbi + Metaspace + NFKC-таблица + NFC-композиция, в RAW-секции __tokenizer__ внутри модели). BGE-M3 int8 (573 МБ): токенизатор — 40/40 текстов побитово совпадают с HF tokenizers; послойный дифференциал с fp32-numpy-эталоном cos ≥ 0.9999; семантика 0.75/0.29 (эталон fp32: 0.75/0.29); живой поиск по 153 МБ лора Eteryya. GLiNER на реальных весах (кирпич 2.6): src/pqc/deberta.rs — DeBERTa-v2/v3-спина (disentangled attention: content + c2p/p2c через одну log-бакет-таблицу относительных позиций, share_att_key, eps 1e-7); scripts/convert_gliner_to_pqw.py — конвертер mdeberta-чекпойнтов (urchade/gliner_multi, 291.7M, мультиязык) → .pqw 297 МБ int8 (3.9×); секция __tokenizer__ v2 (NFC-профиль mdeberta, спец-токены <>/ <>/[FLERT]); RealGlinerModel — BiLSTM + SpanMarker + prompt- проекция, zero-shot метки через --ner-labels. Дифференциал на реальных весах: токенизация побитово = HF, послойно cos 0.9998…0.9992, predict 7/7 сущностей эталона (мультиязычный NER: «Джон Сміта»→людина 0.99, «Київ»→місто 0.99, Google→організація 0.93). 1059 тестов зелёные (+6). Следующий кирпич: конвертер GLM (ChatGLM3-6B → .pqw int4, Фаза 12.7; конвертация на машине владельца — диска песочницы мало).

v0.28.0: Root Broker Password Mode + Builtin Hunter

Два живых запроса владельца после v0.27.0: (1) «почему у тебя отказ всегда — в v0.26.0 было идеально, единственный блок — рут-права: агент вызывает любые утилиты, но хост не передаёт ему рут, а иногда нужно — я ему дам пароль, и у него есть рут»; (2) «найди уязвимость нулевого дня, поместив своего суб-агента (не моего agy), чтобы он нашёл дыры изнутри уже скомпилированной программы без доступа к исходникам — короче, сделай то, что сделал GPT на серверах OpenAI недавно».

1. Root Broker Password Mode — рут по паролю владельца

Отказы v0.27.0 были осознанными (строгая политика: apt/fs-дефолт + allowlist). v0.28.0 добавляет санкционированный ключ:

  • box sudo passwd — только интерактив в шлюзе (scripted-агент НЕ может выдать рут самому себе): соль 16 байт + растянутый FNV-хеш, host-only файл 0600 вне монтируемых каталогов; --clear — снятие;
  • агент в клетке: echo ПАРОЛЬ | sudo -S apt-get install -y sl — шим v2 читает пароль с stdin и прикладывает b64-полем к запросу (пароль не попадает в argv docker-процессов и в аудит);
  • брокер сверяет пароль ДО судьи: верный → НЕдеструктивное разрешено (pip/npm/systemctl/…). Инварианты пароль НЕ ослабляет: деструктив, инструменты побега (docker/nsenter/mount), пути ядра (/proc /sys /dev), вложенный sudo — Deny всегда;
  • брут-форс: 5 промахов → лок 60с; все попытки — в аудите (box sudo log), счётчики — в box sudo status.

2. Builtin Hunter — собственный красный суб-агент POLER

«Как GPT-агенты наблюдения на серверах OpenAI»: детерминированный красный движок, вшитый в движок (не LLM владельца — agent-режим остался отдельно), атакует развёрнутую систему чёрным ящиком изнутри клетки, без исходников:

  • box hunt start --mode builtin — разовая батарея: judge-волна (обходы судьи через живой канал: escape-инструмент под маской пути, пути ядра, деструктив, newline-инъекция), channel-волна (фаззинг протокола брокера: бинарный мусор, oversize, id-траверсал, replay, симлинк- ловушка, усечение канала, подмена шима, брут-форс пароля, flood 25 против лимита 30/час), boundary, + escape-батарея sentinel и docker-inspect;
  • первая находка закрыта в коде: newline-инъекция в argv рут-запроса проходила судью до PKG-дефолта (безвредно из-за no-shell exec, но флаг) → теперь контроль-символы в argv = Deny (регрессионный тест);
  • breach → kill-switch (docker stop, форензика сохранена); judge- аномалии → авто-блоклист брокера («нашёл → закрыл» в рантайме: <box>.blocklist, host-only, отказ ДО судьи);
  • --loop [--interval 30] [--full-every 600] — постоянное наблюдение: лёгкий свип (маркер брокера, рост канала, всплеск промахов пароля) каждые 30с + полная батарея каждые 10 мин; журнал JSONL; box off и выход останавливают наблюдение до docker-разбора.

Цифры релиза

  • 1059 тестов (+24 к v0.27.0): пароль (roundtrip/валидация/лок/ e2e с фейковым docker), протокол v2, шим v2, блоклист, судья с password_ok, ГЛАВНЫЙ ЖИВОЙ тест builtin-батареи (реальный брокер-поток
    • локальный exec = атака изнутри: 14 векторов Safe), loop-наблюдение;
  • гейты: аудит 139/139 · attack-e2e 79/79 + волны 9-12 + волна 13 (19/19) · patch-verify 10/10 · stress 46/46 · секрет-скан PASS;
  • ноль новых зависимостей; docs/terminal-gateway-architecture.md §6.9.1 + §6.11.

Живой тест: git pull && cargo buildpoler-engine --gatewaybox onbox sudo onbox sudo passwd (введи пароль дважды) → внутри агента: echo ПАРОЛЬ | sudo -S pip install requests (пройдёт), echo ПАРОЛЬ | sudo -S rm -rf / (отказ с причиной) → box sudo log (промахи/успехи в аудите) → box hunt start --mode builtin (батарея изнутри) → box hunt start --mode builtin --loop (постоянное наблюдение).


v0.27.0: Root Broker + Jailbreak Sentinel

Два живых запроса владельца после теста v0.26.0: (1) агент в клетке жаловался «нет рута» — владелец: «рут — это тоже привилегия хоста»; (2) «посади ИИ-суб-агента внутрь, пусть попробует вернуться на хост через уязвимость нулевого дня; если находишь — закрывай; наблюдай за попытками со стороны хоста».

1. Root Broker — «sudo как услуга», рут остаётся у хоста

Та же модель, что у Google-рантаймов («в песочнице нет Python — Python вызывают снаружи»): в клетке нет рута — рут вызывают снаружи, через брокера. Агент НИКОГДА не держит рут:

агент в клетке (uid 1000): sudo apt-get install -y sl
  → шим /home/poler/.poler-bin/sudo (НЕ setuid!) пишет запрос
    в /home/poler/.poler-broker/requests.jsonl (id|b64 cwd|b64 argv)
  → шлюз-брокер: судья решает (Block → Deny ВСЕГДА; Confirm → Deny,
    клетка не подтверждает — Zero Silent Escalation; инструменты
    побега и пути ядра → Deny; allowlist владельца; дефолт:
    apt/dpkg + fs в границах /workspace|/home/poler)
  → Allow = docker exec -u 0:0 СО СТОРОНЫ ХОСТА (root внутри клетки,
    cap-drop ALL остаётся)
  → агенту возвращаются только stdout/stderr/exit-код
  • box sudo on|off|status|log [N] — брокер-поток шлюза (маркер живости обновляется каждые 5с — крах шлюза = шим честно отказывает, а не висит; rate-limit 30 запросов/час; ответы атомарные tmp+rename — симлинк-ловушки агента не разыменовываются);
  • box allow sudo <glob>|--list|--reset — allowlist host-only (~/.local/share/poler-engine/policy/, вне смонтированных каталогов — агент не может дописать себя); добавление — только в интерактиве; Block-вердикты не ослабляются ничем (тест с глобом rm *);
  • box root — интерактивный рут-шелл ВНУТРИ клетки, только владелец с хоста (TTY-гейт);
  • аудит каждого решения: ~/.local/share/poler-engine/audit/root-<box>.jsonl.

2. Jailbreak Sentinel — красная команда в клетке под наблюдением

box hunt start [--mode probe|agent] (+ status|report|stop):

  • probe — детерминированная батарея ~28 векторов побега, исполняется ИЗНУТРИ клетки (docker-сокет, cgroup release_agent, core_pattern, CapEff/NoNewPrivs/Seccomp, /dev-устройства, user namespaces, границы /home, mountinfo, /proc/*/root, kallsyms/kcore, k8s-секреты) + хост-аудит docker inspect (Privileged/CapAdd/NetworkMode/PidMode/SecurityOpt/ Mounts). Вердикты blocked/anomaly/escape; любой escape → KILL-SWITCH (docker stop, контейнер сохранён для форензики, инцидент в hunt/incidents/) — цикл «нашёл → закрыл»: вектор становится патчем и регрессионным тестом;
  • agent — живой LLM (проброшенный agy/claude) с миссией побега в PTY-контуре: владелец видит всё живьём; движок сканирует транскрипт сигнатурной базой (~30 паттернов: docker.sock/release_agent/nsenter/ dirty pipe/ssh-ключи/…) и проверяет канарейку — файл с секретным токеном на ХОСТЕ: токен в выводе агента = ДОКАЗАННЫЙ побег → kill-switch. Отчёт — markdown в ~/.local/share/poler-engine/hunt/.

Цифры релиза

  • 1035 тестов (+52 к v0.26.0): base64-кодек, протокол рут-запросов (id-charset против path traversal), судья рут-запросов (Block-инвариант, ZSE, инструменты побега, пути ядра, allowlist-glob), symlink-безопасность ответов, живой брокер end-to-end с фейковым docker (allow/deny/аудит/ маркер/stop), сигнатурный сканер, канарейка, kill-switch, TTY/интерактив гейты, порядок валидации, box off снимает брокера ДО docker-ошибки;
  • Гейты: gateway_audit.py 139/139 · gateway_attack_e2e.py 79/79 + 14/14 + 8/8 + 19/19 + НОВАЯ волна 12 — 18/18 (честность рут-брокера/sentinel без docker; scripted-агент не ослабляет политику) · audit_patch_verify.py 10/10 · audit_stress.py --hardened 46/46; clippy нового кода чист; секрет-скан PASS;
  • Ноль новых зависимостей.

v0.26.0: Zero-Overhead Agent Bind-Mounting + Two-Tier Container Brokerage

Живой тест владельца v0.25.0: box on поднял контейнер, agy внутри — честный executable file not found in $PATH (в базовом образе нет агентов). Владелец: «зачем собирать образ? зачем ставить дважды? движок уже на пк» — и сформулировал архитектуру облачных AI-рантаймов: «мозг агента отдельно, исполнение — отдельно, между ними брокер».

1. Zero-Overhead Bind-Mounting — агенты без сборки образа

box on автоматически находит хостовых CLI-агентов (agy/claude/codex/ gemini/aider/… — PATH + ~/.local/bin + /usr/local/bin; ELF-магия и shebang-классификация) и пробрасывает их внутрь контейнера:

  • бинарник → /usr/local/bin/<имя> ro — ноль копий, ноль слоёв, ноль docker build, ноль двойной установки;
  • конфиги (~/.gemini, ~/.claude, …) → /home/poler/<имя> rw — авторизация живёт и обновляется, переживает box off/on;
  • скриптовые агенты (shebang node/python) — файл монтируется, интерпретатор должен быть в образе (box on image=node:22-slim), шлюз честно предупреждает;
  • политика: agent=auto|none|имя1,имя2, nocfg=0|1, ручной проброс mount=HOST[:CONT[:ro|rw]] с жёстким deny-list: docker-сокет и podman-сокет запрещены ВСЕГДА (вектор угона демона), системные корни хоста (/, /etc, /dev, /proc, /sys, /run…) — запрещены, цели контейнера — белый список /usr/local/bin / /opt/poler / /home/poler, /workspace не расширяется, rw — только данные.

Итог: poler-engine --gatewaybox onagy — запускается из уже установленного бинарника с готовой авторизацией, но физически заперт.

2. Two-Tier Container Brokerage — «мозг → шлюз → runner»

Фундаментальное разделение по модели Google Cloud / air-gapped runtimes:

  • Контур 1 — мозг агента: лёгкая среда (box или хост), без прямого доступа к ОС; общается со шлюзом через MCP (JSON-RPC, Bearer);
  • Контур 2 — шлюз POLER: sandbox-судья выносит вердикт ДО исполнения (деструктив — Block, привилегии — Confirm → из MCP подтверждаться не может в принципе: Zero Silent Escalation);
  • Контур 3 — runner (box runner on): контейнер исполнения poler-runner-<fnv8(ws)>net=none, только /workspace, БЕЗ /home/poler и агентов, cap-drop ALL + no-new-privileges + mem/pids; stdout/stderr/exit-код возвращаются брокеру.

Новые MCP-инструменты (stdio --mcp и HTTP --mcp-http):

  • poler_box_exec — исполнить команду агента в изолированном контейнере (target=auto|runner|box; лексер шлюза, argv насквозь без /bin/sh; конвейеры/редиректы — отдельные вызовы; bash -c '…' допустим, payload судится рекурсивно);
  • poler_box_status — состояние jail-стека (box: образ/агенты, runner: образ/net).

Cross-process discovery: service start mcp передаёт сервису POLER_WORKSPACE, контейнеры находятся по детерминированным именам + docker-labels — брокер работает из отдельного процесса сервиса.

Цифры релиза

  • 983 теста (+26 к v0.25.0): discovery/планировщик монтировок, deny-list mount= (20+ векторов), argv box+runner, MCP-брокер с фейковым docker (Block ДО вызова — проверяется логом), интеграционные REPL-тесты без docker-демона;
  • Гейты: gateway_audit.py 139/139 · gateway_attack_e2e.py 79/79 + 14/14 (shim) + 8/8 (box) + 19/19 (волна 11: bind-mount + runner + живой MCP-брокер) · audit_patch_verify.py 10/10 · audit_stress.py --hardened 46/46; clippy нового кода чист; секрет-скан PASS;
  • Ноль новых зависимостей; POLER_BOX_DOCKER-override теперь действует и на exec-плоскость (тестируемость без демона).

v0.25.0: Container Jail — жёсткая Docker-изоляция агентов (box)

Живой кейс из эксплуатации v0.23/v0.24: агент (agy) в PTY-контуре дергает свой Bash-tool напрямую на хосте. PATH-shim медиация (v0.24) перехватывает вызовы через PATH/$SHELL, но хардкод /bin/sh и прямой execve не накрывает — userspace-фильтр в принципе не даёт гарантий. v0.25.0 решает класс физически: box on поднимает Docker-контейнер, и контуры 2/3 (host-команды и PTY-агенты) исполняются ВНУТРИ него — агент заперт, хост виден только как /workspace и /home/poler.

Модель изоляции

  • монтировки: /workspace ← корень проекта (rw; wsro=1 — read-only) и /home/poler ← персистентный home агентов (конфиги/токены переживают box off/on); больше с хоста не смонтировано НИЧЕГО;
  • hardening: --cap-drop ALL, no-new-privileges, --init, лимиты --memory/--pids-limit, --stop-timeout 2; docker-сокет не пробрасывается — агент внутри не управляет демоном;
  • юзер по умолчанию — uid:gid владельца (файлы остаются его), опция user=root — root только ВНУТРИ контейнера; сеть bridge|none (net=host отвергается); образ по умолчанию debian:bookworm-slim, для агентов — свой (box on image=node:22-slim);
  • вердикты сохраняются: деструктив — Block всегда; логическую границу workspace для exec-плоскости заменяет контейнер; redirect-цели и движковые файл-команды судятся с границей (они физически на хосте); управление docker-демоном из шлюза при активном jail — Confirm; при box on PATH-shim медиация отключается — контейнер заменяет её.

Использование

poler-engine --gateway
poler ~/proj $ box on image=node:22-slim     # поднять jail (pull до 600 c)
poler ~/proj $ agy                            # агент ВНУТРИ контейнера
poler ~/proj $ box shell                      # шелл внутри jail
poler ~/proj $ box status                     # контейнер/образ/монтировки
poler ~/proj $ box off                        # разобрать (данные на хосте)

Без docker-демона box on честно отказывает — шлюз работает в режиме v0.24 (workspace-guard + медиация). Тесты: 957 (+26 к v0.24.0), включая инвариант «Block не зависит от jail», гейт docker-демона и live-lifecycle (POLER_BOX_LIVE=1). Adversarial-гейты: 139/139 + 79/79 + 14/14 + 8/8 (новая волна 10 — box-честность).


v0.24.0: Workspace Boundary Guard & Mediated Agent Mode

Живой кейс из эксплуатации v0.23.0: владелец запустил agy внутри poler-engine --gateway — агент свободно выполнил ls -la /home/vitalij и echo x > /tmp/poler_test.txt: не деструктивно → sandbox пропустил. v0.24.0 закрывает класс: выход за границу workspace — только по подтверждению владельца.

Workspace Boundary Guard

  • любое обращение к пути вне корня проекта — Confirm [y/N]: аргументы, редиректы, cp/dd/curl -o-цели, файл-аргументы движковых команд (grep /etc/passwd), код интерпретаторов (python3 -c "open('/etc/x')"), argv[0] вне системных корней, симлинки из ws наружу (cat leak);
  • payload шелла — настоящая командная строка: bash -c "ls && rm -rf /usr" → Block; bash -c "cat '/etc/passwd'" → Confirm; подстановки $(…)/бэктики — рекурсивно; неразбираемое — fail-closed;
  • cd/workspace на выход из границы — тоже Confirm (подтверждённый выход переносит границу; неинтерактив — ⛔: scripted-агент не уводит корень);
  • allow <PATH> — сессионное исключение владельца (только интерактив); инвариант: allowlist не ослабляет Blockrm -rf / блокируется ВСЕГДА.

Mediated Agent Mode (PATH-shim)

CLI-агенты (agy/claude/codex/gemini/aider…) в PTY-контуре получают ~/.poler-engine/shim/ первым в PATH: их bash -c … приходит в скрытую команду __gateway-shim, судится тем же судьёй. Выход за границу/деструктив/sudo/потоковый shell → отказ 126 с объяснением агенту (агент подтвердить не может в принципе). После выхода агента — телеметрия; ноль перехваченных вызовов = честное предупреждение «команды НЕ фильтровались» (медиация best-effort на PATH/$SHELL; прямые execve видны только ядру — см. доку §6.5).

Верификация: 931 тест (+27 к v0.23.0); judge-корпус 139/139 (новый класс T — граница); живая E2E-батарея 79/79 + 14/14 PATH-shim векторов; security-гейты v0.21.1 без регрессий.


v0.23.0: Interactive PTY Engine, Dynamic Workspace & Sudo Privilege Gate

Три улучшения UX и управления привилегиями Terminal Gateway (по итогам живой эксплуатации v0.22.x: полноэкранные агенты зависали на пайпах, sudo требовал осознанной модели доверия).

PTY Passthrough — TUI/IDE/агенты на живом терминале

  • Проблема: vim, htop, CLI-агенты (agy, claude) требуют псевдотерминала — на пайпах зависают;
  • Решение: трасс контура 3 — авто-PTY для известных TUI и bare-REPL (python3), принудительный префикс pty <cmd>; собственный PTY через posix_openpt/setsid/TIOCSCTTY без новых зависимостей, raw mode (crossterm), проброс ресайза окна, Ctrl+C как байт 0x03, транскрипт с капом 16 МБ; wall-timeout не применяется (сессией управляет владелец);
  • Инвариант: PTY — только транспорт; вердикт sandbox судится по той же команде ДО спавна (pty rm -rf / → Block).

Dynamic Workspace — корень проекта

workspace [PATH]cd): синхронно меняет корень движка и process-cwd — grep/chunk/search, хостовые команды и подпроцессы (включая агентов в PTY) работают от одного корня; промпт обновляется.

Sudo Privilege Gate — три уровня доверия

Уровень Как Поведение
One-shot (по умолчанию) sudo <cmd> подтверждение на /dev/tty — ровно одна команда
Session Lease grant sudo 5m (кап 60 мин, grant sudo off) privilege-Confirm не спрашивается до сгорания таймера
Danger Override --dangerously-allow-all / set sandbox off (интерактив) sandbox отключён: красный баннер, ☠DANGER в промпте, ответственность оператора

Принципы: Zero Silent Escalation (подтверждения и открытие лизинга — только с реального терминала владельца; пайп-агент не может ответить за человека) и «разрешение ≠ снятие фильтра» (лизинг поднимает только Confirm-ворота эскалации; wiper'ы, dd of=/dev/*, reverse-shell, …|sh блокируются ВСЕГДА — даже под sudo с лизингом).

Верификация: 904 теста (+21 к v0.22.1); judge-корпус 113/113; живая E2E-батарея 65/65 (волна 7 — PTY-префикс и ворота привилегий); security-гейты v0.21.1 без регрессий.


v0.22.0: Terminal Gateway + Source-Available EULA

Два взаимосвязанных изменения: верхний уровень управления (нативный терминальный шлюз поверх веб-GUI/Auth Companion/WebLens — «нижнего сервисного слоя») и новая лицензионная модель по прецеденту Unreal Engine EULA.

Terminal Gateway (poler-engine --gateway)

Единое окно терминала (Linux/macOS) с двойным контуром исполнения:

  1. Engine Native (приоритет) — команды движка (search, grep, chunk, crawl, impact, nlm, notes, weblens, benchmark…) перехватываются и исполняются нативно внутри процесса, без спавна внешних шеллов. grep здесь — POLER Native Grep (не /bin/grep; системный — через !grep / host grep);
  2. Controlled Host OS Proxy — всё прочее исполняется в хостовой ОС через Sandboxed OS Subshell: блок деструктивного (rm -rf /, форк-бомбы, dd of=/dev/*, shutdown-семейство, curl | sh, запись в /dev/sd* и /etc/*), подтверждение эскалаций (sudo/su, rm -r, dd), кап вывода 16 МБ, таймаут 120 с, фильтр секретов из env.

Конвейеры смешивают контуры: ls -la | chunk --size 200, cat main.rs | impact main, grep "fn " --stdin | wc -l, ls | poler chunk (префикс опционален). Без /bin/sh вообще — токенизацию делает движок, спавн прямой (класс shell-инъекций устранён).

Сервисный слой из шлюза: service start|stop|status|restart|attach (mcp / weblens / companion), attach mcp — интерактивный JSON-RPC-клиент поверх живого MCP-сервера (tools, call <tool> {json}). Токен сервиса передаётся через env (не argv — не светится в /proc/<pid>/cmdline), живёт в 0600-файле.

ANSI/VT100, SIGINT — SIGINT группе процессов с grace, SIGWINCH — перерисовка, история 5000 команд. Архитектура: docs/terminal-gateway-architecture.md.

Лицензия: Source-Available с раскрытием модификаций

См. раздел «Лицензия» ниже, LICENSE.md, TERMS.md. poler-engine --license и баннер gateway показывают модель, тир и адрес раскрытия модификаций. Cargo.tomllicense-file = "LICENSE.md".

Ретроспектива v0.21.x (не вошла в README ранее)

  • v0.21.0 Hardening & Precision: CodeSymbolIdentity (Foo ≠ foo ≠ FOO, module::name), Triage Layer в AIDDE (proof vs heuristic), Semantic Bridge (офлайн ru↔en, WHY, §8.1 закрыта), Benchmark Suite (POLER grep 3.3 мс vs ripgrep 6.3 мс при parity 195=195), фикс чанкера; 783 теста;
  • v0.21.1 Security Hardening: white-box аудит v0.21.0 — 21 позиция (0 Critical, 2 HIGH), 12 патчей P1–P12 одним коммитом: guard_path + анти-SSRF в MCP (закрыт arbitrary file read и эксфильтрация refresh_token), fail-closed пустой токен, REQUEST_DEADLINE 120 с, атомарные 0600, CDP-капы; security-гейты audit_patch_verify.py 10/10
    • audit_stress.py --hardened 46/46.

v0.20.0: Native Retrieval — grep-режим и RAG-чанки в одном бинарнике

Движок — инструмент ИИ-агента, у которого всё под капотом. До v0.20.0 агенту не хватало двух внешних инструментов: grep (полнота, без индекса) и RAG-конвейера нарезки (passage-уровень вместо целых документов). Оба вошли в бинарник — анализ эталонов (GNU grep, benbrandt/text-splitter, LangChain) и gap-таблица: docs/native-retrieval-analysis.md. Ни одной новой зависимостиaho-corasick, regex, ignore, memchr, rayon уже были в дереве.

Слой 0: точный поиск (--grep, семантика GNU grep)

poler-engine --grep "weblens_token" src/ --grep-before 1 --grep-after 1
poler-engine --grep "fn [a-z_]+" src/ --grep-regex          # grep -E
poler-engine --grep "токен" . --grep-i                       # Unicode-fold
poler-engine --grep TODO . --grep-count                      # grep -c
poler-engine --grep panic . --grep-list                      # grep -l
poler-engine --grep secret . --grep-json | jq                # машинный отчёт
  • Полнота как гарантия: все совпадения, «ноль значит ноль» — живой прогон против GNU grep на src/: 1669 = 1669 строк, включая кириллицу; скорость release-сборки — 23 мс против 18 мс у GNU grep (разница — цена rayon-пула и полного отчёта в памяти).
  • Обход ripgrep-класса: .gitignore/.ignore уважаются, скрытые — по --grep-hidden, симлинки не преследуются.
  • Контекст -A/-B с групповым разделителем -- и слиянием слипшихся групп — как у GNU grep.
  • Exit-коды для скриптов: 0 найдено / 1 пусто / 2 ошибка.
  • --grep-json: byte_offset строк, байтовые диапазоны вхождений, статистика — агент верифицирует совпадения по диапазонам.

Слой B: RAG-чанки (--chunk, passage-уровень)

poler-engine --chunk FUTURE_ROADMAP.md                    # 25 чанков, breadcrumbs
poler-engine --chunk book.md --chunk-size 512 --chunk-overlap 64
poler-engine --chunk lib.rs --chunk-json | jq '.chunks[0]'
  • Иерархия уровней (выше = целостнее): секция заголовка → абзац → предложение → слово; для кода — блок между пустыми строками → строка (никогда внутри строки); code-fence в markdown не режется.
  • Вместимость в токенах POLER (целевые 384, перекрытие 48), слияние соседей до capacity, хвост меньше минимума приклеивается.
  • Якоря для агента: text == original[byte_start..byte_end] — точный срез исходника; номера строк; breadcrumb заголовков; число токенов.

MCP-инструменты (9 теперь)

poler_grep (полнота + JSON-отчёт) и poler_chunk (нарезка с якорями) добавлены к poler_search/poler_web_search/poler_crawl/poler_fetch/ poler_gmail/poler_drive/poler_nlm. Рабочий цикл агента: нарежь документ poler_chunk → найди релевантные куски poler_search/ poler_grep → процитируй по byte range.

Артефакты

  • src/retrieval/{mod,grep,chunk}.rs — 3 файла, ~1700 строк (+43 unit-теста: контекст-группировка, exit-коды, бинарность, gitignore, unicode-офсеты, breadcrumbs, инвариант точного среза, code-fence, перекрытие).
  • docs/native-retrieval-analysis.md — gap-таблицы «GNU grep × RAG × poler-engine» с решениями по каждой функции.

v0.19.0: Browser Surface — 4 фикса краулера, --browser-index и WebLens (MV3)

Фаза Dogfooding вскрыла UX-болячки веб-конвейера (живой аудит в FUTURE_ROADMAP §8). v0.19.0 закрывает их и ставит поиск движка прямо в браузер.

Фиксы краулера (по материалам живого аудита)

  1. Автодетект Chromium: кеш playwright (~/.cache/ms-playwright/chromium-*, chromium_headless_shell-*) сканируется автоматически, свежая версия побеждает — больше не нужно POLER_CHROME_BIN на машинах с playwright.
  2. Пер-страничный таймаут (--crawl-page-timeout-ms, по умолчанию 45 с): JS-тяжёлые сайты с бесконечными XHR/стримингом не вешают обход — страница отметится ошибкой с человеческим текстом и подсказкой, обход продолжится. Плюс жёсткий бюджет 8 с на выгрузку тел перехваченных JSON-API (только завершившиеся ответы — Network.loadingFinished).
  3. Robots больше не молчит: каждый запрет печатается с хостом и путём («robots.txt хоста datatracker.ietf.org запрещает /doc/html/rfc8032 — страница пропущена, RFC 9309») и попадает в stats.notes — не только в счётчик.
  4. Самовосстановление CDP: полумёртвый/осиротевший браузер (kill -9 родителя, мёртвый рендерер) детектится проверкой /json/version вместо голого TCP; сессия не открылась → браузер перезапускается, обход продолжается.

--browser-index: «прочитал — индексируй» одной командой

poler-engine --browser-index https://example.com/article
# → рендер через CDP → upsert в web-index.db → подсказка про --web-search

Явная команда пользователя: robots.txt не блокирует (но честно фиксируется в notes). Повторный запуск покажет «уже в индексе, контент не менялся» (Percolator-lite); near-дубликат ловит SimHash.

WebLens: поиск движка в браузере (Manifest V3)

poler-engine --web-lens              # браузерный режим: расширение +
                                     # оконный Chromium c WebLens +
                                     # MCP-демон на 127.0.0.1:8765
poler-engine --web-lens-install     # в свой браузер: файлы + инструкция

Расширение вшито в бинарник (include_bytes!) и материализуется само — отдельного дистрибутива нет. --web-lens поднимает оконный Chromium с уже загруженным WebLens (--load-extension — автоустановка, ноль кликов в chrome://extensions) и держит тот же MCP over HTTP (Bearer-токен в ~/.config/poler-engine/weblens-token, 0600, вписан в config.json расширения).

Что умеет панель (Alt+P): поиск по общему индексу (веб + NotebookLM

  • код + VCS — единый --web-search-инвариант), клик по результату, подсветка термов запроса прямо на странице (TreeWalker + <mark>, без порчи DOM), кнопка «Индексировать эту страницу» (один клик → страница в индексе; respect_robots: false — явная команда человека). Иконка icons/ — резонансная дуга движка.

Честные границы: подсветка — совпавшие термы (не «тепловая карта смысла»), кросс-язычного моста нет (замер в §8.1 роадмапа: 0 результатов). Киллер-ценность — capture: одна кнопка → страница в общем индексе → один поиск по всему накопленному.

MCP-инструмент poler_crawl: новые аргументы

respect_robots (bool, по умолчанию true) и page_timeout_ms (по умолчанию 45000) — те же ручки, что и в CLI; расширение передаёт respect_robots: false для кнопки «Индексировать эту страницу». CORS-preflight MCP-HTTP теперь отдаёт Access-Control-Allow-Headers: Authorization, Content-Type, X-Poler-Token (раньше браузерный fetch с Bearer падал на preflight).

Тесты

703 зелёных (+16 к v0.18.0): playwright-скан (свежая версия побеждает, мусор игнорирует), cdp_healthy против фейковых DevTools-серверов (валидный JSON/мусор), дедлайн-математика, finished-only интерсепт, robots-notes, respect_robots=false, материализация WebLens байт-в-байт

  • строгая MV3-валидность манифеста + запрет remote code.

v0.18.0: License Gate — офлайн-лицензии ed25519 (PO1)

Коммерческая основа движка: платные интеграции (Gmail / Drive / NotebookLM) закрываются лицензионным гейтом с офлайн-проверкой ed25519-подписей. Без серверов, без телеметрии, без «звонков домой» — математика вместо сети.

Философия гейта: три принципа

  1. Локальное — свято. Поиск, резонанс Ψ, AIDDE impact, граф, shell/TUI работают ВСЕГДА и БЕЗ лицензии. «Кирпич» невозможен по построению.
  2. Офлайн-честность. Лицензия = JSON {product, name, email, tier, issued, expires, features} + подпись ed25519 мастер-ключом POLER. Проверка — микросекунды локально. Подделка без приватного ключа — задача дискретного логарифма, а не «поменять байтик в файле».
  3. Мягкие пределы. Community: интеграции — 50 операций за скользящие 24 ч (локальное — без лимитов). Trial: 14 дней всех функций с первого запуска. Истёкшая лицензия: 7 дней grace с предупреждением, затем тихий откат на Community — ничего не блокируется и не удаляется.

CLI

poler-engine --license                        # статус: тир, срок, квоты
poler-engine --license-import PO1.….….       # активация (ключ или путь к файлу)
poler-engine --license-import ./my.key       # проверка подписи ДО сохранения

Файл лицензии: ~/.config/poler-engine/license.key (0600). Для автоматизации: POLER_LICENSE_KEY (ключ строкой) или POLER_LICENSE_FILE (путь). Точки гейта: --google-gmail, --google-drive, все --nlm-*, shell/TUI nlm …, MCP-инструменты poler_gmail / poler_drive — везде единая скользящая квота Community.

Формат ключа PO1

PO1.<base64url(payload JSON)>.<base64url(подпись ed25519, 64 байта)>

Подпись считается по сырым байтам payload мастер-ключом POLER; в бинарник вшит только ПУБЛИЧНЫЙ ключ (src/license/mod.rs::POLER_LICENSE_PUBLIC_KEY_HEX). Приватный ключ — офлайн у владельца: выпуск лицензий — license-tool/ (отдельный крейт, в поставку не входит):

cd license-tool && cargo build --release
./target/release/poler-license-tool keygen --out ~/.config/poler-engine/license-signing-key
./target/release/poler-license-tool issue --key ~/.config/poler-engine/license-signing-key \
    --name "Имя Покупателя" --email buyer@example.com --tier pro --days 365

Тиры: community (бесплатный), pro (годовая), enterprise (бессрочная разрешена). 20 unit-тестов: base64url (все 256 байт, все выравнивания, Reject стандартного алфавита и паддинга), чужая подпись, подмена payload после подписи, будущая дата выдачи, бессрочный pro, grace-семантика, скользящее окно квот, независимость фич, civil-даты.

Прозрачность Google-авторизации

--google-auth теперь печатает человеческим языком, ЧТО получает движок: Gmail (readonly), Drive (readonly) через OAuth; NotebookLM — НЕ отдельный OAuth-скоуп, синк идёт через профиль браузера движка (--auth-ui), логин и 2FA остаются между пользователем и Google.


v0.17.6: Auth Companion — интерактивное окно авторизации с изоляцией

Локальный легковесный мост между владельцем и движком: poler-engine --auth-ui поднимает отдельное окно Chromium с изолированным профилем движка, в котором владелец сам вводит логин/пароль и проходит 2FA. Движок (и тем более ИИ-агент) не видит ни форм ввода, ни хост-браузера — только итоговые куки сессии через защищённый интерфейс.

poler-engine --auth-ui
  └─ spawn: node scripts/auth-companion.js     (zero-dependency, Node ≥ 18)
       ├─ Chromium: userDataDir = ~/.cache/poler-engine/google-profile
       │            (НЕ головной браузер; логин и 2FA — руками владельца)
       ├─ CDP поллинг (127.0.0.1:случайный порт): Storage.getCookies
       │            до полного ядра сессии: SID HSID SSID APISID SAPISID
       ├─ снапшот → ~/.config/poler-engine/google_session.json (0600)
       ├─ статус-сервер 127.0.0.1: GET /status, POST /shutdown
       └─ автозакрытие окна (CDP Browser.close) — куки флэшатся на диск

Гарантии безопасности

  • No Host Snooping~/.config/chromium, ~/.config/google-chrome и другие браузерные профили хоста не читаются и не пишутся никогда; companion отказывается стартовать, если POLER_GOOGLE_PROFILE указывает туда.
  • Localhost Only — статус-сервер и DevTools-порт слушают строго 127.0.0.1 (--remote-debugging-address=127.0.0.1); окно логина запускается БЕЗ --disable-web-security и по умолчанию БЕЗ --no-sandbox (opt-in POLER_CHROME_NO_SANDBOX=1 — только для контейнеров).
  • Auto-termination — после подтверждения входа окно закрывается сам (Browser.close → SIGTERM → SIGKILL по эскалации); Ctrl+C тоже прибирает браузер. Висячих процессов и открытых CDP-портов не остаётся.
  • Audit-trailsecurity.auth_companion в ~/.config/poler-engine/audit.log (только коды/счётчики, без значений кук).

Файлы и exit-коды

артефакт назначение
~/.cache/poler-engine/google-profile/ изолированный профиль (куки живут тут)
~/.config/poler-engine/google_session.json снапшот сессии, 0600 (значения кук — только тут)
~/.config/poler-engine/auth-companion.state.json transient-состояние (state/port/счётчик)
scripts/auth-companion.js сам companion (self-test: node scripts/auth-companion.js --self-test)
dev-stand/ headless-стенд для контейнеров/песочниц: CDP-релей превью + супервизор + security-audit (dev-stand/README.md)
код смысл
0 авторизация зафиксирована
2 окно закрыто до завершения входа
3 таймаут (POLER_AUTH_TIMEOUT_SECS, default 600)
4 preflight: нет Node≥18/Chromium, профиль занят, запрещённый путь
130 прервано сигналом

Контейнеры без дисплея: dev-stand

В песочнице/headless-контейнере окно показать некуда — dev-stand/ поднимает companion на Xvfb и транслирует его экран владельцу через CDP-релей в превью платформы (скринкаст + мышь/клавиатура + кнопки навигации «Назад/Вперёд»). Модель безопасности и 29 проверок аудита — в dev-stand/README.md.

Почему сессия пишется в google_session.json, а не в google_tokens.json

google_tokens.json — строго типизированное OAuth-хранилище (access_token/refresh_token для Gmail/Drive, выдаются consent-флоу --google-auth). Браузерная сессия — другой класс креденшелов: компаньон пишет снапшот в отдельный google_session.json, а «синхронизация хранилища профиля» происходит сама собой — куки уже лежат в изолированном профиле, который читают --google-fetch / --nlm-*. OAuth-токены companion выдать не может (нужен consent-экран Google) — они по-прежнему только через poler-engine --google-auth.

# окно входа (можно сразу целевой сервис):
poler-engine --auth-ui
POLER_AUTH_TIMEOUT_SECS=900 poler-engine --auth-ui

# после успешного входа:
poler-engine --nlm-account          # проверка сессии NotebookLM
poler-engine --nlm-notebooks        # ноутбуки уже доступны
poler-engine --google-status        # OAuth-токены — отдельная история

# отладка без запуска браузера:
node scripts/auth-companion.js --print-plan   # JSON-план запуска
node scripts/auth-companion.js --self-test    # 19 встроенных тестов

Диагностика: если профиль уже занят открытым окном --google-browse, companion откажется стартовать (код 4) — закройте то окно: профиль один.


v0.17.5: Security Hardening — ручной контроль над аккаунтными операциями

Аудит безопасности выявил четыре слабых места в работе движка с аккаунтом владельца (Google/NotebookLM): молчаливый перенос куков из основного браузера, отсутствие подтверждений перед write-операциями, «вечно живой» headless-браузер с открытым CDP-портом и отсутствие журнала действий. v0.17.5 закрывает все четыре.

1. Cookie-import — только по явному согласию

sync_host_chromium_profile() больше НЕ тянет куки из ~/.config/chromium автоматически при каждом запуске google-браузера. Перенос сессии внешнего браузера — осознанное действие:

poler-engine --import-browser-session   # [y/N] + предупреждение + audit-запись
POLER_IMPORT_BROWSER_SESSION=1 …        # скрипты (тоже логируется)

Логин своими руками через --google-browse <URL> — по-прежнему основной и рекомендуемый путь (пароль между вами и Google).

2. Confirmation Gate для write-операций

  • nlm notes-sync <NB> — теперь только pull (облако → локально). Отправка локальных заметок в облако — отдельно, с подтверждением: --dry-run — план изменений без выполнения; --yes — выполнить push.
  • TUI: открытие ноутбука синхронизирует только pull; ожидающие отправки заметки показываются с подсказкой команды.
  • notes rm <id> — показывает заголовок заметки и требует notes rm <id> --yes.
  • Скрипты: env POLER_YES=1 снимает вопросы (каждое действие всё равно попадает в audit-лог).
  • Новый модуль google::confirm: интерактивный [y/N] для CLI, двухшаговый паттерн plan→apply для shell/TUI, безопасный отказ по умолчанию.

3. Shutdown headless-браузера при выходе

До v0.17.5 --google-gmail/--nlm-*/OAuth-обмены оставляли headless Chromium жить неограниченно — CDP-порт 9223 без аутентификации торчал в системе, и любой локальный процесс мог управлять авторизованной сессией. Теперь CLI закрывает поднятый им браузер через Browser.close (headed-окно --google-browse не трогается — его закрывает владелец).

4. JSONL audit-лог

Все обращения к аккаунту фиксируются в ~/.config/poler-engine/audit.log (права 0600, best-effort — ошибка лога никогда не ломает операцию):

{"ts":"2026-08-29T13:04:00Z","action":"nlm.create_note","details":"nb=abc note=n-1 title=\"Мысль\""}
{"ts":"2026-08-29T13:05:11Z","action":"gmail.search","details":"q=from:me hits=8"}

Логируемые действия: oauth.auth, oauth.gcp_auth, gmail.search, drive.list, nlm.create_note, nlm.chat, nlm.notes_sync, security.import_browser_session. Отключение: POLER_AUDIT_LOG=off, свой путь: POLER_AUDIT_LOG=/path/to.log. В details — только идентификаторы и счётчики, без тел писем/заметок.

количество новых тестов: 15 (gate-логика, audit JSONL/0600/env,
  import-гейт по умолчанию OFF, shutdown no-op, dispatch notes rm)

v0.17.4: Transcript / Response View — лента чата в окне TUI

Восстановление ключевой функции Ask-вкладки Web GUI (удалён в v0.17.0 — Next.js весил 1.2 ГБ, а функция нужна): «Історія чату» + «Відповідь» теперь живут в самом TUI как окно-оверлей.

  • Персистентность: каждая пара nlm ask (вопрос, ответ, notebook_id, время) автоматически пишется в таблицу poler_chat той же SQLite-БД (web-index.db) — лента переживает перезапуски движка.
  • F3 — Transcript: лента пар в feed-порядке (новые снизу): #id [дата время] NB вопрос → N символов. ↑↓/PgUp/PgDn/Home/End — навигация, Enter — полный ответ, y — копировать ответ в буфер, d — удалить пару, r — обновить, Esc — закрыть.
  • Response View: вопрос в шапке + полный ответ с прокруткой (↑↓/PgUp/PgDn), y — копировать, Esc — назад к ленте.
  • Мышь: клик по строке ленты открывает ответ.
  • Модуль shell::transcript: схема poler_chat, CRUD, форматирование времени без внешних зависимостей (алгоритм Хиннанта), 6 юнит-тестов; рендер-смоуки на ratatui TestBackend.
poler-engine --tui
F3                    # лента чата: все пары nlm ask
  ↑↓ Enter            # выбрать пару → полный ответ
  y                   # скопировать ответ в буфер обмена
poler> nlm ask <NB_ID> "новый вопрос"   # пара попадёт в ленту автоматически

Проблема: почему grep и RAG больше не достаточны

# Проблема Симптом Механизм POLER
1 Graph Blindness grep находит изолированную строку, модель не понимает, в каком скоупе она находится Возвращается полный логический скоуп (функция/класс целиком, законченная сцена) + K-hop подграф связей
2 Cosine Collapse / Negation Blindness «Система ОБЯЗАНА отключиться» ≈ «Система НЕ ДОЛЖНА отключаться» при cosine > 0.94 Exact Lexical Anchors: фразовый поиск по токенам + маркеры отрицаний с весом 2.0 в ε
3 Chunk Fragmentation Нарезка по 500 токенов рвёт причинно-следственные связи Semantic Boundary Chunking: границы окон = заголовки сцен / границы функций
4 BM25 / TF-IDF Fail Редкий токен считается «важным», а суть выражена базовыми словами Формула информационной плотности ε на локальной энтропии
5 Temporal Blindness Устаревший код смешивается с актуальным, эпохи T-24 и T-0 в одной куче Temporal Metric Tagging: теги Т-23 на сценах, узлах графа и фильтр --metric

v0.17.4: MCP over HTTP — удалённый агент в блокнотах владельца без передачи пароля

--mcp работал только поверх stdio — то есть для агента, сидящего на той же машине, что и движок. v0.17.4 добавляет второй транспорт: тот же набор из семи инструментов (poler_nlm в том числе), но по HTTP с Bearer-токеном — удалённый агент получает доступ к блокнотам NotebookLM владельца, не получая ни пароль, ни куки Google. Движок на машине владельца ходит в NotebookLM своим персистентным профилем; наружу (через туннель) уходит только JSON-RPC-ответ по предъявленному токену.

# 1. на машине владельца (токен напечатается при старте; или задай сам):
poler-engine --mcp-http 127.0.0.1:8765 --mcp-token <секрет>

# 2. публичный туннель без аккаунта (напечатает https://….trycloudflare.com):
cloudflared tunnel --url http://127.0.0.1:8765

# 3. удалённый агент подключается обычным HTTP:
curl -X POST https://<туннель>/mcp \
  -H "Authorization: Bearer <секрет>" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
        "params":{"name":"poler_nlm","arguments":{"action":"notebooks"}}}'
Параметр Значение
CLI --mcp-http [BIND] (по умолчанию 127.0.0.1:8765; можно просто порт 8765), --mcp-token <T>
Токен --mcp-token → env POLER_MCP_TOKEN → автогенерация (32 hex из /dev/urandom)
Транспорт Streamable HTTP: POST / и POST /mcp, одно сообщение или batch-массив; ответ application/json
Auth Authorization: Bearer <T> или X-Poler-Token: <T>; сравнение за постоянное время
Эндпоинты GET /health — smoke-проба туннеля без токена; GET /mcp → 405; OPTIONS → 204 (CORS-preflight)
Реализация Ручной HTTP/1.1 поверх std::net — ноль новых зависимостей; keep-alive, Expect: 100-continue, поток на соединение, лимит 16 соединений

Модель безопасности: токен — единственный секрет, который покидает машину владельца (и то — по приватному каналу в чате/мессенджере). Куки Google остаются в ~/.cache/poler-engine/google-profile/, наружу отдаются только результаты вызовов инструментов. NLM-чат занимает до 90 с — акцептор не блокируется (каждое соединение — свой поток). Утечка токена = доступ к инструментам движка (чтение блокнотов, чат), но НЕ к аккаунту Google; отзыв = Ctrl+C и рестарт с новым токеном.

Реализация (src/mcp_http.rs, ~700 строк, 20 тестов): рудиментарный HTTP/1.1-парсер (CRLF/LF-заголовки, Content-Length, лимиты 16 КБ заголовков / 8 МБ тела, slow-loris-защита через idle-таймаут), маршрутизатор запросов, JSON-RPC-слой поверх общего McpServer::dispatch() (выделен из stdio-цикла mcp.rs — поведение --mcp не изменено ни на бит), генератор токена с fallback-PRNG splitmix64, если /dev/urandom недоступен. E2E-прогон curl-ом: 401 без токена / с неверным, 200 initialize/tools/list/tools/call, batch с уведомлением, 202 на чистое уведомление, -32700/-32600/-32601, keep-alive из двух запросов в одном соединении.

v0.17.3: Companion Bridge — официальный NotebookLM API рядом с batchexecute

v0.17.1–v0.17.3 соединяют poler-engine с официальным Pre-GA NotebookLM Enterprise API (Discovery Engine v1alpha) — не заменяя реверс-инжиниренный batchexecute-клиент v0.13.0, а достроив сменный мост поверх обоих. Разведка API подтвердила исходную гипотезу: официальный API силён там, где batchexecute слаб (пакетное создание источников, upload файлов, аудио-обзоры, удаление), и слаб там, где batchexecute силён (чтение контента, заметки, артефакты, чат — endpoints отсутствуют или возвращают пустые данные). Мост маршрутизирует каждую операцию к сильнейшему провайдеру и молча падает назад при отказе.

Архитектура HybridProvider (src/google/companion.rs, ~2000 строк)

Компонент Роль
SourceContentProvider trait единый контракт: 13 операций (Op enum) для всех провайдеров
GcpEnterpriseProvider официальный Pre-GA API: Discovery Engine v1alpha, ureq + Bearer (scope cloud-platform), 9 операций — M2 ✓
CdpBatchexecuteProvider потребительский протокол v0.13.0: чтение контента, заметки, артефакты, чат
HybridProvider routing: primary по supports(op), fallback на NotSupported/NotConfigured — M3 ✓

Routing policy: режим Auto (по умолчанию) ведёт GCP-first для 9 enterprise-операций и CDP-first для 4 операций чтения; GcpOnly/CdpOnly принудительно фиксируют провайдер (fallback off). Серверные ошибки (Http/Transport/Parse) не переключают провайдера — это разные данные, а не сбой транспорта.

$POLER_GCP_PROJECT_NUMBER   # GCP-проект с включённым Discovery Engine API
$POLER_GCP_REGION           # us | eu | global (default: us)
$POLER_GCP_LOCATION         # (default: global)
$POLER_COMPANION_MODE       # auto | gcp | cdp (default: auto)

Milestone-разбивка: v0.17.1 — M1 skeleton (trait-контракт, 10 URL-билдеров с sources:uploadFile media-конвенцией /upload/v1alpha/..., 24 теста); v0.17.3 — M2 реальные вызовы (9 операций, refresh-токены из oauth::ensure_gcp_fresh), M3 HybridProvider routing + fallback (8 тестов routing-политики), M4 TUI Enter-handler. v0.17.2 намеренно пропущен (reserved).

M4: Enter на источнике в TUI

Клавиша Enter в панели Sources больше не «ничего не делает» — источник маппится в SourceKindEnterAction:

Тип источника Enter-действие
File $EDITOR на локальном файле (fallback nano)
Url открыть в браузере пользователя (xdg-open)
Repo открыть https://github.com/{value} в браузере
NLM-контент FallbackFetchget_source_content через HybridProvider

Горизонт: Zero-Storage Streaming Archives (SA1–SA7)

docs/future-streaming-archives.md фиксирует следующий рывок — потоковое чтение петабайтных архивов (Common Crawl .tar.zst, Hugging Face .zip) через HTTP Range без скачивания на диск: топологическая адресация zip central-directory (O(δ) ≈ 64 КБ для 50 ГБ архива), streaming ε + IIR резонанс, SimHash-дедуп с Bloom-фильтром (m=2²⁰, k=7), importance sampling батчей P(d→batch) ∝ exp(λ₁·ψ + λ₂·H − λ₃·Redundancy) — десятки МБ RAM на корпуса интернета. Четыре потребителя: обучение локальных LLM, RAG-батчи для готовых моделей, TUI discovery, параллельный поиск по N архивам (rayon).


v0.17.0: TUI Redesign + Pure-Rust Git Clone & LFS — без системного git

Два релиза в одном: полный редизайн терминального интерфейса в стиле MiMo Code и закрытие последних заглушек v0.16.0 — gix clone и Git LFS теперь работают на чистом Rust, без системного git и git-lfs в $PATH.

TUI Redesign (M1–M4, M6)

  • 4-панельный дашборд: Output (главный поток), Notes, Sources, Help — переключение фокуса, resize, scroll в каждой панели.
  • Мышь: клики по панелям, drag-select текста, clipboard через arboard (копирование выделенного в системный буфер).
  • Notes/Sources CRUD: заметки и источники живут в poler-shell.db (src/notes/mod.rs, src/sources/mod.rs) — создаются, редактируются, удаляются прямо из TUI.
  • Help 2.0: src/shell/help.rs — палитра ? с 11 пресетами сценариев (от «первый запрос» до «Pure-Rust git clone + LFS»), детальная справка по каждой команде.

M5: Pure-Rust Git Clone & LFS

gix clone <URL> <PATH> [--depth N] [--branch B] — настоящий clone через gix::clone::PrepareFetch (shallow-depth, checkout в worktree), без вызова системного git. gix lfs list|fetch <PATH> — Pure-Rust LFS-клиент: детект pointer-файлов (version https://git-lfs/...), batch-запрос POST /objects/batch, скачивание блобов в .git/lfs/objects/<oid[:2]>/..., авторизация Bearer $POLER_GIT_TOKEN.

Метрики релиза

Метрика v0.16.0 v0.17.0
Тесты 413 486 (+24: clone/lfs, notes/sources, help)
Бинарник 8.4 МБ 12 МБ (+3 МБ: blocking-network-client gix)
Rust-файлов 51 57 (+notes, sources, help, mouse, clone, lfs)
Web GUI (Next.js) ~1.2 ГБ удалён (M6)

v0.16.0: Unified VCS & Data Mesh — нативные адаптеры GitHub/GitLab/Gitea + Pure-Rust git (gix)

Превращение poler-engine из локального инструмента в Универсальную Сеть Кода и Данных — единый пульт, нативно работающий с любыми репозиториями. Каждый VCS (GitHub, GitLab, Gitea/Forgejo, локальный git через gix) становится source-адаптером, вливающим коммиты/issues/PR в web-index.db как страницы по своим URL-схемам (gh://, gl://, gt://, gix://).

Архитектурные инварианты v0.16.0 (см. FUTURE_ROADMAP.md §6.4)

Ноль новых зависимостей в схеме web-index.db — VCS-страницы используют те же WebDoc + links + content_hash + positions + PageRank, что веб и NLM. URL-схема — единственное отличие. Это сохраняет инвариант v0.14.0: один --web-search пробивает ВСЕ юниверсы (NLM + веб + локальный код + GitHub + GitLab + Gitea + gix-local) с единой PageRank топологией.

Структура нового модуля src/vcs/

Файл Назначение LOC
mod.rs VcsAdapter trait, VcsScheme enum, RepoId/VcsCommit/VcsIssue типы, sync_vcs() ~370
github.rs REST API GitHub v3 (search/repos/commits/issues/PRs) через ureq ~470
gitlab.rs REST API GitLab v4 (search/projects/commits/issues/MRs) ~420
gitea.rs REST API Gitea/Forgejo (commits/issues/PRs) ~370
local.rs Pure-Rust git через gix crate: discover/rev_walk/decode ~440
ingest.rs Helper: VcsCommit/VcsIssue → WebDoc (URL-схема + content_hash + links) ~220

Новые команды poler-shell

poler> gh search <Q>                # GitHub code search (требует $GITHUB_TOKEN)
poler> gh repos <USER>              # список репозиториев пользователя
poler> gh commits <OWNER/REPO>      # последние 20 коммитов
poler> gh issues <OWNER/REPO>       # issues + PRs (REST, не GraphQL)
poler> gl search <Q>                # GitLab REST v4 search
poler> gl commits <GROUP/PROJ>      # коммиты GitLab проекта
poler> gl issues <GROUP/PROJ>        # issues + MR (два endpoint'а слиты)
poler> gt search <Q>                 # Gitea/Forgejo (требует $GITEA_HOST)
poler> gt commits <OWNER/REPO>       # коммиты Gitea
poler> gix log <PATH> [--top N]      # Pure-Rust git log локального репо
poler> gix clone <URL> <PATH>        # заглушка v0.16 (используйте git clone)
poler> sync vcs [gh|gl|gt] <OWNER>   # синк VCS в web-index.db + recompute_pagerank
poler> sync vcs all <OWNER>           # все 4 адаптера сразу

Tab-completion для всех новых команд: gh<Tab> → search/repos/commits/issues; sync vcs <Tab> → gh/gl/gt/gix/all; gix <Tab> → log/clone.

Переменные окружения

  • $GITHUB_TOKEN или $GH_TOKEN — для gh search (анонимно нельзя). Опционально для list_repos/commits/issues (rate-limit 60 req/h без токена).
  • $GITLAB_TOKEN или $GL_TOKEN — для GitLab.
  • $GITEA_TOKEN / $GT_TOKEN — для Gitea. $GITEA_HOST — обязательный (например gitea.com, codeberg.org, git.example.com).
  • $GITHUB_API_HOST — для GitHub Enterprise (например github.corp.com/api/v3).
  • $GITLAB_HOST — для self-hosted GitLab (gitlab.corp.org).
  • $POLER_USER_AGENT — User-Agent для HTTP-запросов (по умолчанию poler-engine/0.16).

Донорские технологии

Донор Что берём Куда легло
gh CLI (GitHub) REST+GraphQL API, commits/issues/PR/codeowners src/vcs/github.rs
glab CLI (GitLab) REST API v4, merge_requests/pipelines src/vcs/gitlab.rs
tea CLI (Gitea/Forgejo) REST API (forgejo-compatible) src/vcs/gitea.rs
gix crate (gitoxide) Pure-Rust git: discover/rev_walk/commit decode src/vcs/local.rs
ureq crate Синхронный HTTP без tokio-runtime (минимум deps) src/vcs/{github,gitlab,gitea}.rs

Аттестация

  • 413 unit-тестов зелёные (+119 к v0.15.1: 6 vcs::mod, 21 github, 13 gitlab, 11 gitea, 22 ingest, 16 local, 15 completer, 15 commands).
  • clippy — 0 warning'ов.
  • Бинарник poler-engine — 8.4 МБ stripped (рост с 5.9 МБ за счёт gix+ureq).
  • Smoke-тест: poler> gix log /home/z/my-project --top 3 → 3 коммита прочитаны через Pure-Rust gix (без git CLI), индексированы в web-index.db как gix:// страницы, PageRank переcчитан.
  • Smoke-тест: poler> gh search rust без токена → корректная подсказка $GITHUB_TOKEN. poler> sync vcs без owner → graceful fallback к NLM sync.

Архитектурный итог

VCS-страницы в web-index.db — это обычные WebDoc со своим URL-space: gh://user/repo/commit/<sha>, gl://group/proj/issues/<iid>, gt://owner/repo/pulls/<n>, gix:///path/to/repo/commit/<sha>. Каждая страница получает content_hash (Percolator-lite идемпотентность), индексируется BM25, ссылается через links на свой web_url (https://github.com/...), и участвует в общем PageRank графе вместе с вебом, NLM и локальным кодом. poler> search "Планковська геодезична" пробивает всё сразу.

Известные ограничения (перенесены в v0.17.0)

  • gix clone — заглушка v0.16.0; для синхронного clone требуются feature-флаги blocking-network-client (добавлены в v0.17.0). Пока: git clone URL path в соседнем окне, затем poler> gix log path.
  • Git LFS pointer-resolve (.gitattributes + version https://git-lfs/...) — v0.17.0 (см. FUTURE_ROADMAP.md §6.3, шаг 3).
  • Hugging Face Hub (model cards + datasets) — v0.21.0 (hf:// URL-схема).
  • DVC + Oxen.ai (data-versioning pointer files) — v0.22.0.
  • HugeSCM/Lit/ParamLake — v0.23.0+.

Артефакты

  • src/vcs/{mod,github,gitlab,gitea,local,ingest}.rs — 6 файлов, ~2290 строк (+119 unit-тестов, ~370 строк тестового кода).
  • src/shell/commands.rs — +290 строк cmd_gh/cmd_gl/cmd_gt/cmd_gix/cmd_sync + 16 unit-тестов.
  • src/shell/completer.rs — +60 строк complete_gh/gl/gt/gix/sync_vcs + 14 тестов.
  • src/shell/state.rs — +20 строк vcs_subcommands()/gix_subcommands()/vcs_schemes().
  • Cargo.tomlureq 2.10 + gix 0.66 (default-features=false, features blocking-http-transport-reqwest + worktree-mutation + revision + comfort).
  • README.md — секция v0.16.0 (~150 строк).
  • FUTURE_ROADMAP.md — §6.2 обновлён (v0.16.0 = shipped, v0.17.0 → gix-clone + LFS).

v0.15.1: poler-shell финализация — Tab-completion + нативные crawl/impact в REPL

Шлифовка полиринга полер-шелла — подключены Tab-completion, подсказки Hinter и нативные команды crawl/impact прямо внутри REPL. Шелл становится монолитным: все 15+ режимов движка теперь доступны из poler> без переключения окон.

Что починено в v0.15.1

1. Tab-completion в REPL (rustyline Helper):

PolerCompleter теперь зарегистрирован в Editor::<PolerCompleter, DefaultHistory>::new() через rl.set_helper(Some(PolerCompleter)). Tab-completion работает для:

  • Первого слова команды: searsearch, impimpact, cracrawl.
  • Подкоманд nlm/set: nlm llist, set foformat.
  • Флагов crawl/impact: после crawl https://example.com Tab предлагает --depth/--max/--cross/--delay-ms/--wait-ms/--cdp-port/--help. После value-флага (--depth, --max, --delay-ms, --wait-ms, --cdp-port) completion выключается — ждётся числовое значение, а не другой флаг.

Hinter показывает inline-подсказку по набранной команде в серой подсветке (search # search "<query>" [--top N]), не дожидаясь Tab. History хранит до 2000 команд в ~/.cache/poler-engine/shell-history.txt с dedup последовательных дубликатов.

2. Нативная команда crawl в шелле:

poler> crawl https://rust-lang.org --depth 2 --max 25
poler> crawl https://rust-lang.org --depth 3 --max 50 --cross --delay-ms 500
poler> crawl https://example.com --cdp-port 9223 --wait-ms 1200

Полный синтаксис: crawl <URL> [--depth N] [--max M] [--cross] [--delay-ms N] [--wait-ms N] [--cdp-port P]. Делегирует в poler_engine::web::cdp_fetcher

  • poler_engine::web::crawl::crawl — те же функции, что и в standalone-режиме poler-engine --crawl URL. В шелле есть преимущество: WebIndex уже открыт (если был search/stats/nlm sync ранее), так что crawl сразу льёт страницы в ту же БД без повторного открытия. Вывод: fetched, indexed, unchanged (Percolator-lite skip), duplicates, errors, sitemap_urls, elapsed_ms.

3. Нативная команда impact в шелле:

poler> impact ./src crawl --depth 2
poler> impact /home/z/myproject main --depth 3 --cache /tmp/aidde.db
poler> impact /path/to/repo parse_file --depth 2 --max-file-bytes 128MB

Делегирует в poler_engine::collect_files + aidde::SymbolTable::build + aidde::impact_analysis (in-memory по умолчанию) или в aidde::SymbolStore + aidde::impact_analysis_sqlite--cache <DB> для кодовых баз 65K+ файлов). Выводит target_function, file, lines, danger_level_if_modified, upstream dependents (кто вызывает этот символ), downstream dependencies (кого вызывает), side-effects (маркеры unsafe/mutex/static/IO/socket/panic/...).

Аттестация v0.15.1

  • Unit-тесты: 294 passed, 0 failed (270 v0.15.0 + 24 новых в v0.15.1: 14 completion-tests для crawl/impact флагов, 10 cmd_crawl/cmd_impact edge-case tests).
  • Clippy: 0 warnings (useless_format и default_constructed_unit_structs починены автоматически).
  • Бинарь: 5.9 МБ stripped ELF x86-64 (рост с 5.8 МБ за счёт явного Helper impl + доп. completion-логики).
  • Smoke-тест: echo -e "version\nhelp\nquit" | poler-engine --shell → "poler-shell 0.15.1 — интерактивный режим" + help со списком всех 11 команд (search/web/stats/nlm list/notes/artifacts/source/account/ask/sync/crawl/ impact/set/version/quit/help).
  • Боевой smoke-test: poler> impact ./src crawl --depth 2 → построил SymbolTable на 45 кодовых файлах движка за <1 с, нашёл mod::crawl в src/web/mod.rs, рассчитал danger_level HIGH (затронет 6 файлов), 26 upstream dependents (кто вызывает crawl: commands/main/mcp/completer/ state/crawl_tests), 118 downstream dependencies (кого вызывает crawl: derive/parse/insert/clone/discover_sitemaps/...).

Архитектурные инварианты v0.15.1

  • Ноль изменений в ядре poler_engine::* — shell только заимствует WebIndex, cdp_fetcher, crawl::crawl, collect_files, aidde::* и форматирует вывод.
  • PolerCompleter — теперь полноценный Helper (Completer + Hinter + Highlighter + Validator) с явным impl Helper for PolerCompleter {}. В v0.15.0 был Editor::<(), DefaultHistory> без completion — это была единственная регрессия, теперь закрыта.
  • Ленивое открытие ресурсов сохраненоWebIndex и NlmSession открываются только при первом search/stats/nlm/crawl. Команды help/version/set не трогают БД и RPC.

Артефакты v0.15.1

  • src/shell/commands.rs (+~340 строк): cmd_crawl, cmd_impact — нативные команды с парсером флагов (--depth/--max/--cross/--delay-ms/ --wait-ms/--cdp-port для crawl; --depth/--cache/--max-file-bytes для impact) и форматированным выводом.
  • src/shell/completer.rs (+~110 строк): complete_crawl_flags, complete_impact_flags — Tab-completion флагов с различением value-флагов (после них ждём значение, не флаг); явный impl Helper for PolerCompleter {}; CMD_HINTS расширены crawl/impact.
  • src/shell/commands.rs run_shell обновлён: Editor теперь типизирован как Editor<PolerCompleter, DefaultHistory>, через Configurer trait выставлены max_history_size=2000, history_ignore_dups=true, completion_type=List, auto_add_history=true.
  • Cargo.toml: bump 0.15.0 → 0.15.1.

v0.15.0: poler-shell — интерактивный TUI/REPL терминал поверх движка

Когда у движка 15+ режимов (поиск, AIDDE, веб-краулинг, NotebookLM, Google Drive, фразы, графы, синк), человеку неудобно каждый раз вбивать длинные флаги --format md --nlm-chat --top 5 или вспоминать UUID ноутбуков. v0.15.0 добавляет две поверхности для человека поверх существующих режимов — ядро poler_engine::* не трогается, только UI-слой.

Поверхность Команда Технология Что даёт
REPL poler-engine --shell rustyline Быстрый командный режим без перезапуска процесса: poler> search "..." / poler> nlm ask <id> "..." / poler> nlm sync. История ↑/↓ сохраняется в ~/.cache/poler-engine/shell-history.txt.
TUI Dashboard poler-engine --tui ratatui + crossterm 3-панельный layout: слева — список 87 ноутбуков (обновление по r); справа сверху — поле ввода; справа снизу — выдача с прокруткой PgUp/PgDn. Tab — смена фокуса, Esc — выход.

Архитектурные инварианты v0.15.0:

  • Ноль изменений в ядреpoler_engine::* остаётся как в v0.14.0. Shell только заимствует WebIndex/NlmSession/nlm_ingest/nlm::* и форматирует вывод для человека.
  • Ленивое открытие ресурсовWebIndex и NlmSession открываются только при первом использовании (первый search/stats открывает БД, первый nlm list/notes/... открывает RPC-сессию). После этого переиспользуются до выхода из шелла — экономит ~1 s на каждой команде по сравнению с автономным запуском CLI.
  • История команд — до 2000 записей в ~/.cache/poler-engine/shell-history.txt.
  • MCP poler_nlm 9 actions из v0.14.0 не тронуты.

Команды REPL (палитра для человека):

poler-engine --shell
poler> help
poler> version
poler> search "Касіопея Astra-Nic Complex" --top 5    # поиск по web-index.db
poler> web "..."                                       # алиас для search
poler> stats                                            # статистика web-index
poler> nlm list                                         # список 87 ноутбуков
poler> nlm notes 704f2610-...                          # заметки/чат (JSON)
poler> nlm artifacts 704f2610-...                       # Studio-артефакты
poler> nlm source 704f2610-... <SRC_ID>                # контент источника
poler> nlm account                                       # email сессии
poler> nlm ask 704f2610-... "Параметры Планковской геодезической"
poler> nlm sync                                          # синк ВСЕХ ноутбуков
poler> nlm sync 704f2610-...                            # синк одного
poler> set format md|json|simple                        # формат вывода
poler> set top 20                                       # топ-K по умолчанию
poler> quit

TUI keybinds (poler-engine --tui):

Tab / BackTab    смена фокуса: notebooks → input → output → notebooks
↑ / ↓           в input: история команд; в notebooks: навигация
'r'             в notebooks: обновить список (nlm list)
PgUp / PgDn     в output: скроллинг результата
Enter           в input: выполнить команду
Esc / Ctrl+C    выход

Аттестация v0.15.0:

  • 270 unit-тестов зелёные (239 из v0.14.0 + 31 новый для shell:: {state, commands, completer, tui, integration_tests}):
    • state::tests: ленивое открытие WebIndex, парсинг set format, commands() и nlm_subcommands() стабильные списки;
    • commands::tests: tokenize (кавычки/пробелы/unclosed), cmd_set_format, cmd_unknown, cmd_quit, cmd_empty, cmd_version, cmd_help;
    • completer::tests: complete_prefix("sear") → search, complete_prefix("nlm l") → list (но не account, не начинается с 'l'), complete_prefix("set fo") → format, пустая строка → все команды;
    • tui::tests: Focus::next/prev цикл (notebooks↔input↔output↔notebooks), Focus::as_str корректен;
    • integration_tests: tokenize_handles_quoted_args, state_default_format_is_md_for_humans, commands_dispatch_unknown_returns_message.
  • cargo clippy --lib --bin poler-engine — 0 warning'ов (8 auto-fixed: push_str("\n")push('\n'), &[s].to_vec()&[s], неиспользуемые импорты RlBuilder/KeyEvent/Rect/tokenize).
  • cargo build --release --bin poler-engine — бинарник 5.8 МБ (+0.4 МБ к v0.14 за счёт ratatui+crossterm+rustyline; binary stripped), --version0.15.0.
  • Smoke-тест REPL: echo "version\nhelp\nquit" | poler-engine --shell — приветствие + version + help (полный список команд) + quit, всё работает.
  • НЕ подключён в v0.15.0: Tab-completion в rustyline Editor (PolerCompleter реализован и покрыт тестами, но rustyline 14 Helper trait bound регрессия не даёт подключить его к Editor — v0.15.1 исправит через производный Helper derive).
  • НЕ подключены в v0.15.0: команды crawl/impact в шелле (заглушки с подсказкой использовать poler-engine --crawl/--impact в соседнем окне) — v0.15.1 добавит нативную интеграцию.

Что влито в продакшене (по данным v0.14.0): после poler-engine --shell владелец может интерактивно: nlm list → стрелочкой выбрать UUID → nlm sync <id>search "..." --top 5nlm ask <id> "вопрос" — всё в одной сессии без повторных RPC-handshake'ов.

v0.14.0: NLM Corpus Ingestion — --nlm-sync и кросс-юниверсный поиск

Виток v0.13.0 выгружает NotebookLM по одному ноутбуку: --nlm-notes <nb>, --nlm-source <nb> <src>, --nlm-artifacts <nb> — владелец видит данные, но они остаются в JSON-выводе, не в общем индексе. v0.14.0 замыкает круг: все 87 ноутбуков аккаунта (паспорты + источники + заметки + Studio-артефакты) вливаются в единую базу web-index.db — тот же --web-search пробивает приватный NLM-корпус + локальный код + проползенный веб одновременно, с рёбрами links, замыкающими граф NLM↔веб (заметка → источник → внешний Google Docs/YouTube → проползенная страница).

Доноры из прошлых витков (ноль новых зависимостей):

Механизм Виток Куда легло в v0.14.0
Percolator-lite (content_hash skip) v0.9 content_hash() FNV-1a 64-hex; повторный --nlm-sync skip'ит неизменившиеся страницы за O(1) lookup
Positional Inverted Index v0.11 фразовые запросы "..." ищутся по смежности delta-varint позиций и в заметках NLM
PageRank v0.8 итерации по links — заметка → ноутбук-паспорт → источник → внешний URL; recompute_pagerank(20) после синка
NLM batchexecute-протокол v0.13 NlmSession::list_notebooks/notes/artifacts/load_source — готовые данные, без нового RPC
Chromium-профиль (OAuth 2.0) v0.12 одна сессия на все NLM-операции + веб-краулинг

URL-схема NLM-страниц (новый namespace в web-index.db):

nlm://notebook/{nb_id}                          — паспорт (title + source-list)
nlm://notebook/{nb_id}/source/{src_id}          — контент источника + URL слайдов
nlm://notebook/{nb_id}/note/{note_id}           — текст заметки/чата
nlm://notebook/{nb_id}/artifact/{art_id}        — Studio-объект (title + kind + status)

Внешние URL источников (Google Docs, YouTube) попадают в links как обычные строки — они совпадают с URL проползенных веб-страниц, образуя сквозной граф. PageRank распространяет авторитет через все юниверсы.

# 0) один раз: залогиниться в профиль движка (как для --nlm-chat из v0.13)
poler-engine --google-browse https://notebook.google.com/

# Синк всех ноутбуков аккаунта в web-index.db (после первого запуска — инкремент)
poler-engine --nlm-sync
# → mode: nlm-sync, notebooks: 87, reindexed: 240, unchanged: 612, errors: 0
#   pagerank iterations: 20

# Синк одного ноутбука (для отладки или точечного обновления)
poler-engine --nlm-sync 704f2610-c02b-4ec1-9fc7-a3b72dde2af1

# После синка — обычный --web-search находит NLM-контент наравне с вебом
poler-engine --web-search '"Касіопея Astra-Nic Complex"' --top 5
# → hit 1: nlm://notebook/704f2610.../note/note-1   (notebook=«Касіопея»)
# → hit 2: nlm://notebook/704f2610.../source/src-text-1
# → hit 3: https://example.com/doc1                  (внешний URL источника)

Парсер parse_notes — толерантен к вариативности Google: формат cFji9 в реальном продакшене (см. upload/NOTEBOOK_704f_ALL_NOTES.json, 3.7 МБ) — это [items_array, metadata_array], где каждый item = [id, [id, text, ?, ?, title?, ...]] с 5 или 6 полями во внутреннем массиве. Эвристика wrapper-detection (data[0][0].is_array() ⇔ обёрнутый формат) различает [items, meta] и bare items — парсер остаётся устойчивым к обоим представлениям.

MCP: инструмент poler_nlm расширен 9-м action sync (теперь 9 actions: notebooks | source | notes | artifacts | account | chat | media | shot | sync). LLM-агент может триггерить синк без выхода в шелл:

{"method":"tools/call","params":{"name":"poler_nlm",
 "arguments":{"action":"sync"}}}
→ {"mode":"nlm-sync","stats":{"notebooks":87,"reindexed":240,...}}

Аттестация v0.14.0:

  • 239 unit-тестов зелёные (227 из v0.13.0 + 12 новых nlm_ingest): URL-схема, FNV-1a хеш, parse_notes (bare/wrapped/пустой/5-полей/6-полей), ingest_notebook (паспорт/источник/заметка/артефакт, рёбра, skip по хешу), IngestStats счётчики;
  • cargo clippy --lib --bin — 0 warning'ов;
  • cargo build --release --bin poler-engine — бинарник 5.5 МБ, --version0.14.0;
  • e2e-скрипт scripts/nlm_sync_test.py написан (фактический NLM-фейк + 6 проверок: sync all, Percolator skip, cross-universe web-search находит NLM, single sync, MCP action=sync) — требует профильного Chromium в окружении запуска (см. scripts/mcp_nlm_test.py из v0.13.0 для шаблона).

Что влито в продакшене (по данным v0.13.0): при --nlm-sync против реального аккаунта движок вольёт ~87 паспортов + ~240 источников + ~24 заметок (3.7 МБ) + 10 Studio-артефактов = ~361 страница в web-index.db — первый синк идёт ~3 минуты (RPC на источник), повторный skip'ает 95%+ за Percolator-lite.

v0.13.0: NotebookLM без API — протокол batchexecute + медиа-канал

NotebookLM не имеет публичного API, но расширение NLMTools.com («NotebookLM Tools for Gemini») работает внутри авторизованной страницы и говорит на его внутреннем RPC. Разведка: скачали их Firefox-XPI (это zip), извлекли inject.js и чанки — получили полный протокол: 57 RPC-методов batchexecute, аргументы, парсеры ответов, структуру WIZ_global_data. Протокол перенесён в Rust (ноль новых зависимостей) — движок теперь сам делает всё, что умеет NLMTools, и то, чего их API не отдаёт (медиа).

Донор (NLMTools / NotebookLM) Что взято Куда легло
inject.js расширения карта RPC: wXbhsf (ноутбуки), rLM1Ne (паспорт), hizoJc (контент источника), cFji9 (заметки), gArtLc (Studio), ZwVcOc (аккаунт) src/google/nlm.rs
batchexecute (внутренний RPC Google) формат f.req/at/rpcids, анти-XSSI-префикс )]}', конверты wrb.fr, коды ошибок (8 — квота, 7/16 — авторизация) NlmSession::rpc
WIZ_global_data токен SNlM0e, app/bl/fsid, email сессии NlmSession::open
парсеры On/R из чанков ноутбуки/источники/артефакты, enum-типы (YouTube=9, Docs=1…), даты [сек, наносек] → ISO-8601 parse_notebooks / parse_source_content / parse_artifacts
медиа-канал (чего нет в API NLMTools) картинки слайдов l[5][0], скачивание через профильный Chromium, скриншоты страниц fetch_media / screenshot

Два канала — суть комбинации: текст/доки/чат идут по batchexecute (точно и структурированно, как «специальный API» NLMTools), а медиа — глазами профильного Chromium (тот самый --google-browse-профиль из v0.12.0: логин один раз, куки живут месяцами). Модель ноутбука отвечает по его источникам — это RAG владельца, а не общая модель.

# 0) один раз: залогиниться в профиль движка (те же куки, что для --google-fetch)
poler-engine --google-browse https://notebook.google.com/

poler-engine --nlm-notebooks                # все ноутбуки + источники (id, типы, YouTube-id)
poler-engine --nlm-source <nb> <src>        # текст источника ИЛИ URL картинок слайдов
poler-engine --nlm-notes <nb>               # сохранённые заметки
poler-engine --nlm-artifacts <nb>           # Studio: аудио-обзоры, отчёты, квизы, миндмэпы
poler-engine --nlm-account                  # email/настройки сессии
poler-engine --nlm-chat <nb> "вопрос"       # ответ модели ПО ИСТОЧНИКАМ ноутбука (до 90 с)
poler-engine --nlm-media <URL>              # скачать картинку слайда → ~/.cache/poler-engine/nlm/
poler-engine --nlm-shot <URL>               # скриншот страницы (медиа-глазами юзера) → PNG

MCP: инструмент poler_nlm (итого 7) — LLM-агент получает action-модель: notebooks | source | notes | artifacts | account | chat | media | shot; ошибки валидации возвращаются isError с подсказкой, «не залогинен» — с инструкцией --google-browse.

Аттестация v0.13.0:

  • 12 unit-тестов протокола: парсеры конвертов/ноутбуков/источников/артефактов, varint-даты, анти-XSSI, коды ошибок (квота/авторизация), деградация форматов;
  • живой e2e с фейковым NotebookLM (scripts/mcp_nlm_test.py): настоящий Chromium + MCP-конвейер — 7 инструментов в tools/list; notebooks → 2 ноутбука с источниками и YouTube-id; source → текст склеен из кусков и URL картинки слайда отдан; media → байты PNG совпали до байта; shot → настоящий PNG-скриншот; chat → полная UI-автоматизация (ввод вопроса → Enter → клик Send → эвристика стабилизации стрима → извлечение ответа); валидационные ошибки — isError с подсказками;
  • против реального notebook.google.com — честная граница: без логина в профиль движок отдаёт инструкцию --google-browse (сессию не подделываем).

227 unit + 38 integration тестов зелёные, clippy 0.

v0.12.0: Google-сервисы без пароля — OAuth 2.0 + персистентный профиль

Интеграция с Gmail / Google Drive / NotebookLM без передачи пароля движку — двумя штатными механизмами (так работает «Войти через Google» у всех приложений):

Механизм Сервисы Как работает
OAuth 2.0 loopback (RFC 8252) Gmail, Drive (+ любые API: Calendar, Docs…) Consent-экран открывается в браузере владельца — пароль остаётся между человеком и Google. poler-engine получает только узкие readonly-токены (отзыв: myaccount.google.com/permissions)
Персистентный профиль Chromium NotebookLM и сервисы без публичного API --google-browse открывает окно с профилем ~/.cache/poler-engine/google-profile — владелец логинится один раз своими руками, куки живут месяцами; --google-fetch читает авторизованный контент headless-ом

HTTPS-клиент — сам Chromium (ноль TLS-зависимостей в Rust): GoogleHttp выполняет fetch() в контексте страницы через CDP Runtime.evaluate + awaitPromise; google-браузер живёт на отдельном порту 9223 с флагом --disable-web-security (это API-профиль, не stealth-краулер) и общим --user-data-dir для headless/headed режимов.

# 1) свой OAuth-клиент (5 минут, бесплатно — см. ниже) и одноразовое согласие
poler-engine --google-auth                       # consent в твоём браузере

# 2) почта и диск — нативный синтаксис Gmail
poler-engine --google-gmail "from:me has:attachment newer_than:7d"
poler-engine --google-gmail                      # недавняя почта
poler-engine --google-drive "отчёт"              # файлы по имени
poler-engine --google-status                     # скоупы/срок/email

# 3) сервисы без API (NotebookLM): логин один раз своими руками
poler-engine --google-browse https://notebook.google.com/
poler-engine --google-fetch https://notebook.google.com/notebook/<id>

Своё OAuth-приложение (client_secret.json): console.cloud.google.com → проект → включить Gmail API + Drive API → OAuth consent screen (External, себя в Test users) → Credentials → OAuth client ID (Desktop app) → скачать JSON в ~/.config/poler-engine/client_secret.json. Токены: ~/.config/poler-engine/google_tokens.json (права 0600), refresh — тихо и автоматически; access-токен живёт ~1 час.

MCP: инструменты poler_gmail и poler_drive (итого 6) — любой LLM-агент читает почту/диск владельца через те же readonly-токены.

Аттестация v0.12.0 (живые тесты):

  • мост CDP→HTTPS против реального Google: token endpoint отклоняет мусорный обмен (401 invalid_client), Gmail/Drive API отклоняют фейковый Bearer (401) — TLS/POST/заголовки/статусы проходят честно;
  • полный E2E на фейковых эндпоинтах (6 шагов): consent-URL → 302 → loopback-ловушка (state проверен, чужой state отбрасывается) → обмен кода через браузер → токены 0600 → протухание → тихий refresh → Gmail-запрос идёт с обновлённым токеном (фейк-API принимает только его) → Drive → status с email → --google-fetch--google-browse честно требует оконный Chromium;
  • MCP: tools/list отдаёт 6 инструментов, poler_gmail/poler_drive отвечают живыми данными через refresh-токен.

215 unit + 38 integration тестов зелёные, clippy 0.

v0.11.0: Фразовый поиск — позиционный индекс в веб-поиске

Виток «доработки хренового»: веб-индекс был «мешком слов» — запрос "Rust async runtime" находил страницу, где Rust в первом абзаце, а runtime в футере через 5000 слов. Теперь порядок токенов сохраняется и проверяется по смежности позиций — семантика точных цитат Google.

Что внутри (донорские технологии — Lucene .prx / Tantivy / Google exact-quotes):

Компонент Откуда украдено Что делает
positions BLOB в postings Lucene .prx (positional index) дельта-varint-позиции токенов рядом с (term, page_id, tf) — ~1–2 байта на вхождение
phrase_occurrences() Lucene PhraseScorer вхождение фразы в позиции p ⇔ каждый терм в p+i; бинарный поиск по отсортированным спискам
parse_query() Google-синтаксис "..." сегменты в кавычках "..." и «...» → фразы (стеммингуются!); однотокенная «фраза» деградирует до терма
Proximity-бонус Lucene phraseFreq 0.5 · Σidf(термов) · min(occ, 8) добавляется к BM25 за каждое вхождение
TITLE_GAP=8 фраза не сшивает последнее слово тела с первым словом заголовка
Миграция v1→v2 старые БД v0.9/v0.10 открываются: ALTER TABLE + пересчёт позиций из сохранённого text (content_hash не трогается — Percolator-lite не пострадает)

Семантика: документ обязан содержать КАЖДУЮ фразу запроса целиком (жёсткий фильтр, как точные цитаты Google); свободные термы за кавычками ранжируют как раньше. phrase_occ в JSON/Md-выдаче показывает число вхождений.

# фраза из живой страницы std::mem::swap (свежий краул v2):
$ poler-engine --web-search '"swaps the values"' --format md
## 1. swap in std::mem - Rust
- Score: 0.8000 (bm25=1.402, pagerank=0.15000, title=0.00, ε=0.02484, фраз=·1)
> …pub const fn swap<T>(x: &mut T, y: &mut T) Swaps the values at two mutable…

# переставленные слова — честный ноль:
$ poler-engine --web-search '"values the swaps"'; echo $?
1

# те же слова без кавычек — прежняя OR-семантика (3 хита вместо 1)
$ poler-engine --web-search 'values swaps the'

Живая аттестация: старая БД эпохи v0.9 (8 страниц Rust std, 4532 postings, БЕЗ колонки positions) мигрирована при открытии: все постинги получили позиции, "list of all items" → ровно 1 хит (страница «List of all items in this crate»), без кавычек — 3 хита. Кириллица: «владения память» находит «владение памятью» (стемминг + смежность).

Тесты: 189 unit + 38 integration (+23 к v0.10.0), clippy 0.

v0.10.0: MCP-сервер — poler-engine как нативный инструмент LLM-агентов

v0.9.0 дал движку веб-поиск; v0.10.0 отдаёт его любому LLM-агенту напрямую: poler-engine --mcp поднимает MCP-сервер (Model Context Protocol, stdio JSON-RPC 2.0, ноль новых зависимостей). Claude Desktop, Cursor, Cline, Zed и любой MCP-клиент получает четыре инструмента — и агент сам решает, какие сайты читать и обходить: ни цель, ни тема не фиксированы.

Инструмент Что делает
poler_web_search Поиск по постоянному индексу: WebRank, сниппеты, кириллический стемминг
poler_crawl Обход выбранного агентом сайта в постоянную БД (robots/sitemap/SimHash/PageRank)
poler_fetch «Прочитать любой URL сейчас»: реальный Chromium, SPA/JS рендер, перехват скрытых JSON API
poler_search Локальный резонансный POLER-поиск: ε/R, полные сцены, K-hop граф
// claude_desktop_config.json / mcpServers:
{ "poler-engine": { "command": "/home/user/.local/bin/poler-engine", "args": ["--mcp"] } }

Доработка «хреновых» частей v0.9.0 (по итогам полевого анализа романа)

Проблема (живой баг) Фикс v0.10.0
Морфологическая слепота: «ініціац» → 0 хитов (в тексте «ініціація», «ініціації») web/stem.rs — лёгкий кириллический стеммер (uk/рос): ~60 окончаний, защита коротких слов, 2 прохода. Индексация и запрос — единый путь. Живая проверка: запрос «мови програмування» находит «мова програмування» (score 0.9)
Агент должен сам поднимать браузер web::ensure_chromium — автозапуск: $POLER_CHROME_BIN → PATH → bundle-путь; CLI --web/--crawl и MCP-инструменты поднимают Chromium молча
HeadlessChrome в User-Agent выдаёт автоматизацию CDP-стелс: десктопный UA + navigator.webdriver→undefined + --disable-blink-features=AutomationControlled (техника puppeteer-extra-stealth). Живая проверка: httpbin.org/user-agent видит обычный Chrome/152
SimHash-отпечатки считались от поверхностных форм Единый стемминг-путь: падежный шум уходит из отпечатка — near-дубли ловятся надёжнее

Честная граница: аутентификационные стены не обходятся — логины, платный контент и приватные ноутбуки возвращают то, что видит анонимный браузер (notebook.google.com рендерит оболочку NotebookLM; контент требует Google-логина). Технические барьеры (SPA/JS/бот-фильтры) обходятся рендером реального Chromium.

Живые полевые тесты MCP (реальные сайты, Chromium 152)

Вызов Результат
initialize + tools/list serverInfo v0.10.0, 4 инструмента с inputSchema
poler_fetch example.com Chromium поднялся сам, текст 129 Б, кэш-файл записан
poler_crawl uk.wikipedia.org/wiki/Rust (depth 0) 1 стр, 7.7 с, автозапуск повторно
poler_web_search «мови програмування» Стемминг работает: склонённый запрос → назывной заголовок, score 0.9
poler_search fixtures/example.rs «main» R=585.0, ε=585.0 через MCP
poler_fetch httpbin.org/user-agent UA = Chrome/152.0.7977.54 (не Headless), JSON API перехвачен
litnet.com (прямой рендер) Каталог книг читается полностью + перехвачен внутренний JSON API genres_for_sidebar (54 КБ)

v0.9.0: Web Search for AI — краулер + BM25/PageRank-индекс + --web-search

Полноценный веб-поиск: v0.8.0 умел рендерить страницу, v0.9.0 умеет обходить сайты, строить индекс и отвечать на запросы. Архитектура собрана из проверенных боевых технологий (Google → open source → POLER-модель: те же математические инварианты, вертикальный масштаб вместо 10 000 серверов):

Технология-донор Откуда Реализация в POLER
Googlebot (миллион headless Chromium) Google cdp.rs — один Chromium через CDP (v0.8.0)
robots.txt + Sitemap стандарт вежливости Googlebot robots.rs — парсер групп UA, Crawl-delay, Sitemap:, Allow/Disallow
URL Frontier + Politeness Mercator/Heritrix (краулер, выкормивший Google-конкурентов) crawl.rs — BFS-граница, доменная задержка, cap на хост
SimHash-дедупликация статья Google «Detecting Near-Duplicates for Web Crawling» (Manku et al., 2007) simhash.rs — 64-битные отпечатки, расстояние Хэмминга ≤ 3
Инвертированный индекс Tantivy/Lucene (открытые наследники Google Index) index.rs — SQLite: pages/terms/links/hosts/meta
BM25 (Okapi) до-нейронный Google классический BM25 (k1=1.2, b=0.75) + idf-фильтр стоп-слов
PageRank статья Brin & Page 1998 итеративный по links-таблице, ε-телепорт
Percolator (инкрементальный индекс) Google (Colossus-стек) content-hash skip: повторный краул переиндексирует ТОЛЬКО изменившееся
Scatter-Gather Top-K Google Serving postings scatter → аккумулятор gather → нормированный WebRank

Ранжирование — POLER WebRank v1: 0.55·BM25 + 0.15·PageRank + 0.20·title-match + 0.10·ε-плотность — лексическая точность BM25, ссылочная авторитетность, точность в заголовке и POLER-мера информационной плотности в одной формуле.

# 1. Краулинг сайта (Chromium рендерит каждую страницу, robots/sitemap соблюдаются):
poler-engine "https://nginx.org/en/docs/" --crawl --crawl-depth 2 --crawl-max 50

# 2. Поиск по собранному индексу (AI-ready JSON со сниппетами):
poler-engine --web-search "gzip static" --top 5

# Индекс: $POLER_WEB_DB или ~/.local/share/poler-engine/web-index.db

Полевые замеры (Chromium 152, реальные сайты)

Сайт Загружено Проиндексировано Особенности
doc.rust-lang.org/std/mem 10 стр, 16 с 10 frontier нашёл ещё 230 URL
en.wikipedia.org/wiki/Rust 8 стр, 43 с 8 кросс-доменный поиск «ownership borrow checker»: Википедия #1
nginx.org/en/docs 8 стр, 17 с 5 3 SimHash-дубля поймано (зеркала www/http), 200 URL из sitemap
повторный краул Википедии 5 стр, 27 с 0 (5 unchanged) Percolator-lite: content-hash skip работает

Найден и закрыт живой баг релевантности: на моно-тематическом корпусе (весь сайт про nginx) предметный терм запроса встречается на каждой странице → idf-фильтр стоп-слов убивал его → «gzip» давал 0 результатов. Теперь при пустом после фильтра запросе термы откатываются к полному набору (регрессионный тест monothematic_corpus_subject_term_not_stopped_out).

v0.8.0: Web-Native Retrieval — нативный Chromium CDP

Замена «костыльной» связке Rust → Node.js → CLI → Chromium из super-z-skills (agent-browser --cdp 9222): прямой CDP-клиент на чистом std (src/web/cdp.rs, ~430 строк) — WebSocket RFC 6455 поверх TcpStream, ноль новых зависимостей.

# Chromium headless (chrome-headless-shell, без X11/KDE):
chrome-headless-shell --headless --remote-debugging-port=9222 --no-sandbox &

poler-engine --web "https://en.wikipedia.org/wiki/Rust_(programming_language)" \
    -q "ownership" --web-wait-ms 2500
Возможность Как реализовано
Bypass SPA/Shadow DOM браузер исполняет весь JS; Runtime.evaluate(document.body.innerText) — текст, который видит человек
Перехват скрытых API Network.responseReceived (mimeType=application/json) + Network.getResponseBody — сырой JSON до превращения в HTML, до 64 ответов
Cross-Universe Graph рендер-текст и JSON попадают в веб-кэш → общий K-hop граф с локальным репозиторием
Фильтрация шума реклама/меню/футеры отсеиваются сами: у шаблонного мусора низкая ε-плотность относительно запроса

Полевые замеры (Chromium 152 headless-shell, реальные страницы)

Страница Рендер-текст Результат
example.com 129 Б 3 хита, 4 мс
doc.rust-lang.org/std/mem/fn.swap.html 1 001 Б 8 хитов «swap», ε/R посчитаны
en.wikipedia.org/wiki/Rust_(…) 73 686 Б, 11.5K токенов 9 хитов «ownership», сцена 16 КБ с FFI-контентом, граф 148 узлов
httpbin.org/json 1 JSON API перехвачен (pretty-printed в кэше)
Википедия mmap + локальный allocator.c Cross-Universe: 24 хита из веба и кода в одном K-hop графе

CLI: --web (PATH трактуется как URL), --cdp-port (default 9222), --web-wait-ms (пауза на дочерние XHR, default 1200).

v0.7.0: стриминговый Top-K + кэш локатора сцен

Литературный стресс на LM1B (4 файла, 28 млн токенов, запрос «the» = 1 667 291 совпадений) вскрыл три уровня деградации и закрыл их:

Проблема Исправление Эффект
Аллокация Vec<String> окна (80 строк) на каждый хит calculate_epsilon(&[&str]) — zero-alloc срезы токенов −480 млн аллокаций
SceneInfo (тройки) строилась на каждый хит до отсева стриминговая куча top-N в pass2 (обычный и гигантский пути): лёгкие записи → куча → тяжёлые сцены только для выживших; per-chunk top-N ∪ глобальный top-N (IIR в чанках независим — корректность доказуема) память O(top_n), не O(hits)
locate пересканировал заголовки всего файла на каждом хите (LM1B: 70 ГБ сканирования на чанк); поиск абзаца без ограничения окна — терабайты на корпусах без пустых строк SceneLocator (заголовки один раз, бинарный поиск) + окно абзаца 64 КБ с выравниванием UTF-8 «the»: 8+ мин (смерть процесса) → 53.8 с

Итоги литературного стресса (LM1B, 28 млн токенов, 2 vCPU)

Запрос Хиты Время Пик RSS
«the» (суперчастотный) 1 667 291 53.8 с 420 МБ
«president» 25 950 13.5 с
«United States» (фраза) 13.0 с

Честные границы: temporal-счёт при --metric приближён по top-N записям (полный счёт требует light_meta на каждую сцену — дорого на суперчастотных запросах); сцены гигантов после стриминга строятся только для top-N якорей (граф сущностей massive-hit запросов сужается до выживших сцен).

v0.6.1: кэш reuse + фикс токенизации запроса

Исправление Суть Эффект
--impact-reuse существующая --impact-cache база не перестраивается повторный impact-запрос на подъядре: 39 000 мс → 30 мс (1300×)
Токенизация запроса = токенизация текста запрос разбивается по не-буквам как текст («слайд-шоу» → [слайд, шоу]) дефисные/пунктуационные запросы находились grep'ом, но не движком: «слайд-шоу» 0 → 69 хитов; «Цинк-4», «Тёмное Сердце» работают как фразы

Найдено прогоном хаотичного ассоциативного запроса по Eteryya (многослойная декомпозиция): «кальций»→Chapters_Sfera_Predela (потеря кальция, крошащиеся зубы), «Сейф-Био»→канон EPUB-00, «Тёмное Сердце»→EPUB-04 (пик R=124570), «Цинк-4»→chapter_p01of25 (Т-22). Честные ограничения: точный поиск не нормализует ё/е («крио-шёлк» ≠ «крио-шелк»); «аксональное» в корпусе отсутствует физически (0 файлов по grep).

v0.6.0: пять багфиксов полевой аттестации + SQLite AIDDE

Исправления по отчёту полевых стресс-тестов (Wireshark/PYCCLE)

# Баг Исправление Эффект
1 Квадратичный поиск по noise_spans в AIDDE (O(M×N) на файл) Бинарный поиск по отсортированным спанам O(log K) убраны десятки миллионов итераций на файл Wireshark
2 Однопоточный SymbolTable::build rayon par_iter + слияние в порядке файлов (детерминизм) оба ядра, ~2×
3 Память на многофайловых корпусах с частым словом (PYCCLE «king»: 500K+ HitRecord → 1.4 ГБ) Early Top-K Pruning: per-file только top_n якорей + компактные hit_keys (8 байт/хит) + hits_temporal для честного total_hits; осиротевшие сцены удаляются Eteryya: 168→138 МБ; корректность: глобальный top-N ⊆ ∪ per-file top-N
4 Линейный скан table.calls на каждом шаге BFS в impact Индексы HashMap по callee/caller, O(1) lookup на уровень AIDDE без квадратичности
5 OOM ин-мемори AIDDE на 65K файлах (~5 млн вызовов = 5–8 ГБ) SQLite SymbolStore (--impact-cache path.db): потоковая запись чанками из rayon-воркеров (пик RAM = чанк), нормализация путей (files id/path — сжатие в ~2.5×), B-Tree индексы по callee/caller, BFS только по нужным строкам полное ядро Linux: 61 092 файла, 1.7 млн defs + 6.7 млн вызовов, RSS ~100 МБ (было: OOM-kill при 4 ГБ)

Результаты полного AIDDE на Linux Kernel через SQLite

poler-engine ~/linux-6.12.35 --impact printk --impact-cache /tmp/sym.db
# 61 092 файла | defs=1 712 453 | calls=6 683 592
# RSS ≈ 100 МБ (ин-мемори версия умирала от OOM)
# printk → CRITICAL (44 файла), 200 upstream

v0.5.0: канонический POLER-цикл из P3_Engine + экзамен на Linux Kernel

Канонический POLER-цикл (p3_poler.zig → poler-engine)

Математика взята из серьёзного репозитория P3_Engine (Zig, 36K строк), а не из ранних набросков POLER-Quantum. Каноническое уравнение:

p_new = p − η · Π_Λ(D·p + γ·J·p + ∇F)
D = L·Lᵀ   — диссипатор: энтропийный горел, сжигает внимание без наблюдений
J = A − Aᵀ — резонанс: кососимметричный генератор вращения (частота
             восстанавливается из осцилляций потока ε, не из памяти)
Π_Λ         — каузальный проектор (temporal-фильтр: чужие эпохи запрещают сдвиг)
CORDIC      — ренормализация на S¹ с параметром mix (битовая магия + 3 итерации)
poler-engine ~/code -q "foo" --resonance-mode poler \
    --poler-eta 0.01 --poler-gamma 0.1 --poler-mix 0.1 --poler-dissipator 0.02

Реализация (src/poler.rs): честная 2D-редукция с настоящими матрицами — D = d²·I (изотропное затухание), J = [[0,−ω],[ω,0]], Π_Λ = I − Jcᵀ(JcJcᵀ+δI)⁻¹Jc (идемпотентность доказана тестом), CORDIC 1/√x (точность ~1e-11, тест на 5 порядках). Дефолты η=0.01, γ=0.1, mix=0.1, δ=1e-10 — из P3Node.init / PolerEngine.initDefault p3_poler.zig.

Принципиальное отличие от Ψ-версии: диссипатор гарантирует затухание без наблюдений (D=LLᵀ ≥ 0 по построению), резонанс вращает фазу, а не накапливает историю. Тест poler_dissipator_distinguishes_sparse_and_dense_hits доказывает: плотная серия упоминаний даёт больший POLER-резонанс, чем разовая вспышка.

Экзамен на Linux Kernel 6.12 (1.2 ГБ, 85K файлов)

Полный стресс-тест на исходниках ядра Linux — см. секцию «Производительность».

v0.4.0: POLER[Ψ] + параллельные гиганты + PII-архитектура

Интеграция канонической математики POLER[Ψ]

Математика взята из репозиториев POLER-Quantum / poler-dynamics / dynamis-v1 (Kotokvit) и встроена как третий режим резонанса:

poler-engine ~/Eteryya -q "адамантит" --resonance-mode psi \
    --psi-eta 0.05 --psi-gamma 0.5 --psi-rho 0.9 --psi-depth 8
Формализм POLER[Ψ] Реализация в движке
Ω(o_t) = tanh(o_t) — перцепция наблюдение = ε-плотность окна совпадения
ε = κ Δxᵀ G(p) Δx — энергия значимости calculate_epsilon = κ·Σ(ln N − ln freq)² — квадратичная форма с диагональной метрикой редкости
R[n] = ρᵏ·s_{t−k} — резонанс памяти замкнутая форма = IIR R_t = ε_t + ρ·R_{t−1} (доказано тестом iir_is_degenerate_case_of_psi)
Π_Λ — проектор логики temporal-фильтр: наблюдения чужих эпох не сдвигают внимание
p_{t+1} = p_t + ηΠ(−∇F + γ∇ε) — ψ-поток src/psi.rs: точный порт PsiField.evolve с параметрами по умолчанию из POLER_Psi_v3.py

Найдено при интеграции (честная находка о исходной математике): условие устойчивости ψ-поля γ·Σρᵏ < 2. Дефолтные параметры POLER_Psi_v3 (γ=0.5, ρ=0.9, K=8: β = +0.85) дают расходимость на длинных сериях — в коротких Python-демо она не успевает проявиться. Стабилизация: внимание ограничено перцептивным пространством Ω = tanh ∈ (−1, 1).

Параллельная обработка гигантов

Файлы ≥ 8 МБ режутся на чанки ~2 МБ по границам сцен (заголовки markdown / границы абзацев, выравнивание по UTF-8 и строкам) и обрабатываются в rayon-пуле параллельно. Enclosing scope не рвётся: граница чанка — единственный безопасный разрез. Память ограничена ~2 МБ × потоки. Тест эквивалентности: гигант 9 МБ даёт те же сцены, что и последовательная обработка.

PII-маскирование перенесено на выход (3× ускорение)

Профилирование боевого прогона Eteryya вскрыло: PII-регексы по всему корпусу (92 МБ) стоили 65% времени (5.2 с без PII vs 15.4 с с PII). Архитектурное исправление: токенизация и индексация работают на raw-тексте, маскирование применяется только при материализации выходных сцен (enclosing_scope + метаданные) — ровно там, где текст получает AI-потребитель. Позиции внутри конвейера остаются raw-точными.

Итоги боевого прогона (2 vCPU)

Метрика v0.3.1 v0.4.0
Eteryya полный скан (92 МБ, 8.5 млн токенов) 15–18 с 5.5–6.0 с (3×)
Пиковая RSS 171 МБ 150–168 МБ
Ψ-режим на Eteryya работает (5.2 с)

v0.3.1: полевая аттестация на реальных корпусах

Исправлен критический боевой баг (OOM на Eteryya)

При прогоне на реальном репозитории Eteryya (148 МБ, 305 md-файлов, 8.5 млн токенов) движок погибал от OOM-killer (exit 137, пик 3.6 ГБ). Бисекция привела к файлу с строкой 1.7 МБ без переносов, содержащей «- Персонажи:» в глубине текста с 8763 запятыми: light_meta забирал остаток мегабайтной строки как значение поля → 9315 «субъектов» → каскад троек → OOM. Исправлено многоуровневыми потолками:

  • карточка метаданных ищется только в первых 8 КБ сцены;
  • строка-кандидат обрезается до 512 байт, значение поля — до 256 байт;
  • не более 16 субъектов по 80 байт;
  • не более 256 троек на сцену; enclosing_scope — жёсткий потолок 1 МБ.

Результаты полевой аттестации (2 vCPU)

Корпус Объём Запрос Результат
Eteryya (реальный) 360 файлов, 92 МБ md, 8.5 млн токенов «адамантит» 358 хитов, 171 МБ RSS, 15 с (было: OOM)
Eteryya, фразовый то же «не должна» 194 МБ, 16 с
Eteryya, watcher rescan то же «адамантит» инкремент: 277 мс (54× быстрее полного)
tokio (реальный код) 836 файлов, 678K токенов «spawn_blocking» 282 хита, 12 МБ, 0.4 с
tokio + AIDDE то же --impact spawn_blocking CRITICAL: 79 файлов, 200 upstream, 21 МБ, 1.6 с

Edge-батарея (12 кейсов): пустой файл, бинарник с .md, битый UTF-8, BOM+CRLF, строка 100 КБ, чистая пунктуация, emoji, директория с расширением .md, вложенность 30 уровней, symlink-цикл, .gitignore, файл без прав на чтение — все без паник и зависаний.

Дифф-режим watcher

poler-engine ~/Eteryya -q "адамантит" --watch --diff --interval-secs 2

--diff--watch): инкрементальные прогоны печатают только якоря, которых не было в предыдущем прогоне (новые file+byte_pos) — поток событий для агента вместо повторения всего топа.

v0.3.0: потоковая архитектура памяти + AIDDE

Исправление критического бага памяти (bug#1, v0.2.1)

Прежняя схема удерживала инвертированные индексы всех файлов одновременно (Vec<FileScan>) — на корпусе 340 файлов / 12 млн токенов RSS раздувался до гигабайт. Новая схема никогда не удерживает индексы между фазами:

Проход 1 (rayon):  mmap → литеральный предфильтр (kwset-техника GNU grep)
                    ├─ литерала нет → streaming counts (без построения индекса)
                    └─ литерал есть → временный FileTokens (zero-copy &str-срезы)
                         → глобальные частоты + позиции хитов → освобождение
Проход 2 (rayon):  только hit-файлы → ε/R + лёгкая локализация сцен + тройки
Проход 3:          материализация текстов сцен только для top-N

Замеры на стресс-корпусе (340 файлов, 91 МБ, ~11.5 млн токенов — масштаб репозитория Eteryya), 2 vCPU:

Метрика v0.2.1 v0.3.0
Пиковая RSS (VmHWM) 431 МБ 99 МБ (−77%)
Время полного прогона 53 с 74 с (+40% — цена потоковой схемы)

Память теперь ограничена O(словарь корпуса) + один временный индекс файла; гигантские файлы (≥ 8 МБ, наподобие дампов Прочее_FULL) обрабатываются строго последовательно. Межпроходный текстовый кэш удалён полностью.

AIDDE: AI-Interpreted Dependency & Impact Engine

Ответ на слепоту grep/RAG и «близорукость» линейного интерпретатора (Single-Fault Blindness). Вместо вопроса «компилируется ли?» — вопрос «что сломается, если это изменить?»:

poler-engine ./src --impact scan_path_with_stats
{
  "target_function": "lib::scan_path_with_stats",
  "file": "./src/lib.rs",
  "lines": "1-291",
  "upstream_dependents": [
    {"caller": "lib::scan_path", "file": "./src/lib.rs", "line": 244}
  ],
  "downstream_dependencies": [
    {"callee": "Engine::scan", "file": "./src/engine.rs"}
  ],
  "side_effects": ["Блок unsafe — снятые гарантии безопасности памяти"],
  "danger_level_if_modified": "MEDIUM (затронет 1 файл)"
}

Три уровня: (1) глобальная таблица символов (fn/struct/class/def + импорты, межфайловые связи); (2) call graph с разрешением через таблицу символов, фильтрацией определений и вызовов внутри строк/комментариев; (3) двунаправленный BFS impact-анализ + эвристики сайд-эффектов (unsafe, мьютексы, файловый I/O, сокеты, глобальное состояние) + danger level.

Watcher: инкрементальный рескан по mtime

poler-engine ~/Eteryya -q "адамантит" --watch --interval-secs 2

Первичный полный скан, затем каждый такт сравниваются mtime/size: изменённые и новые файлы обрабатываются заново, неизменённые гигантские дампы не перечитываются и не ретокенизируются. Удалённые файлы исключаются из глобальной статистики. Выход — Ctrl-C (SIGINT).

Математический аппарат

A. Информационная плотность ε(W_t)

ε(W_t) = κ · (1 + ln(1 + count(kw))) ·
         Σ_{w ∈ Unique(W_t) \ {kw}} (ln(N_total) − ln(freq(w)))² +
         Σ_{w ∈ W_t} Bonus_semantic(w)
  • N_total — объём токенов корпуса (или файла при --local-stats);
  • freq(w) — глобальная частота токена;
  • Bonus_semantic — Ахо-Корасик словарь критических маркеров: отрицания (2.0), обязанность (1.5), критичность (1.5), угроза (1.2), код-маркеры (unsafe, deprecated, panic!), метрики;
  • κ — калибровочный масштаб (--kappa).

B. Линейный рекурсивный фильтр резонанса (IIR, O(N))

R_t = ε_t + φ · R_{t-1},    φ ∈ [0.75, 0.90]

Два режима (--resonance-mode):

  • hits (по умолчанию) — IIR по последовательности ε совпадений в документе: повторные упоминания накапливают резонанс;
  • field — ε вычисляется инкрементальным скользящим окном на каждой позиции документа (амортизированно O(1) на сдвиг), IIR прогоняется по всем позициям, сэмплируется в точках совпадений — строго O(N) один проход.

C. K-hop подграф сущностей

SubGraph(E₀, k) = { (u, predicate, v) | dist(E₀, u) < k ∧ (u —predicate→ v) ∈ G }

BFS в обе стороны (outgoing + incoming), узлы сливаются case-insensitive, каждый узел несёт temporal-слой. Тройки извлекаются из текста (SVO-эвристики: Нокс —вонзила_когти→ Солнечное сплетение), из метаданных сцены (Нокс —появляется_в→ Глава 36) и из кода (process_data —вызывает→ compute).

Архитектура

files ──► [Проход A, rayon] mmap → PII-mask → токенизация → inverted index
              │                    (глобальные частоты токенов корпуса)
              ▼
        merge global stats (N_total, freq)
              ▼
        [Проход B, rayon] для hit-файлов:
              HitRecord (лёгкий): окно W_t → ε (+semantic bonus)
                                          → IIR R_t
                                          → локализация сцены (без клонов)
              Уникальные сцены: ScenePayload (сцена+тройки) один раз
              ▼
        EntityGraph (petgraph DiGraph) ── K-hop BFS от корневой сущности
              ▼
        temporal-фильтр → сортировка по R → top_n → материализация ContextAnchor

Ключевые решения производительности:

  • memmap2 — zero-copy чтение (валидный UTF-8 + нет PII → ноль копий файла);
  • PII-маскирование возвращает Cow::Borrowed на чистом тексте; замаскированный текст малых hit-файлов кэшируется между проходами (нет повторного regex-скана);
  • HitRecord / ScenePayload — тяжёлые payload (клон сцены, разбор троек) материализуются один раз на уникальную сцену и только для top-N якорей;
  • Teddy SIMD (v2.0) — мультитокен-литеральный предфильтр и поиск семантических маркеров (LeftmostLongest, эквивалент AC — доказано дифференциальными тестами против crates.io aho-corasick): решёто якорных байтов (pshufb по двум нибблам, AVX2/SSSE3) + адаптивные якоря по априорной частоте байтов; ASCII-запросы — 2.7× быстрее AC (1.6 ГБ/с), кириллица — быстрый фолд D0/D1 вместо to_lowercase (2.15×);
  • лексический сканер кода понимает строки, char-литералы ('{'!), raw-строки Rust (r#"…"#), комментарии и template-литералы JS с ${} — скобки в них не считаются;
  • release-профиль: opt-level=3, lto=true, codegen-units=1, panic="abort", strip.

Происхождение алгоритмов: анализ исходников базовых инструментов

Движок построен на техниках, извлечённых из исходных репозиториев (см. upstream/ в рабочей области):

Источник Файл/модуль Техника Внедрение в poler-engine
GNU grep 3.11 src/kwset.c Commentz-Walter: BM-сдвиги + AC-автомат, выбор исполнителя per-query Литеральный SIMD-предфильтр --fast: Teddy-решёто (класс Hyperscan/ripgrep Teddy, clean-room, задача 2.5 v2.0) по байтам до токенизации, файлы без литерала отбраковываются
ripgrep crates/ignore/src/walk.rs WalkBuilder: параллельный обход с .gitignore/.ignore/hidden Обход каталогов — сам крейт ignore (код BurntSushi): git_ignore, git_global, git_exclude, require_git(false), флаг --hidden
ripgrep regex-automata prefilter literal prefilter: regex не запускается без якорного байта PiiCleaner: отсутствие @/цифр (проверка memchr) пропускает email/IP/phone/card regex
super-z-skills skills/_orchestrator/scripts/memory_graph.py SQLite-схема entities/relations с UNIQUE-констрейнтами --graph-export dump.sql: дамп графа сущностей в этой же схеме, sqlite3 graph.db < dump.sql
GNU grep src/grep.c grep-совместимые коды выхода 0/1/2 (совпадения/нет/ошибка)

Отличие принципиальное: grep после нахождения строки останавливается — poler-engine разворачивает каждое совпадение в полный аналитический контекст (скоуп + ε + резонанс + K-hop подграф).

Установка и сборка

cargo build --release          # бинарник: target/release/poler-engine
cargo test                     # 85 тестов (unit + integration)
cargo run --release --example bench

POSIX-совместимо: Linux/macOS/BSD, только чистый Rust без C-зависимостей.

CLI

poler-engine [OPTIONS] --query <QUERY> <PATH>

-q, --query <QUERY>         слово или фраза (в кавычках: "не должна")
-t, --top <N>               топ-результатов [default: 10]
    --format <FORMAT>       ai-json | md | simple [default: ai-json]
    --phi <PHI>             затухание IIR-резонанса [default: 0.85]
    --kappa <K>             масштаб ε [default: 1.0]
-w, --window <RADIUS>       радиус токенного окна [default: 40]
-k, --k-hop <DEPTH>         глубина обхода графа [default: 2]
    --metric <TAG>          временной фильтр (например Т-23)
    --pii <MODE>            off | mask [default: mask]
    --resonance-mode <MODE> hits | field [default: hits]
    --local-stats           ε по статистикам файла вместо корпуса
    --extensions <LIST>     сканируемые расширения
    --max-file-size <MB>    [default: 32]
    --max-scope <BYTES>     потолок enclosing_scope [default: 16384]
    --max-relations <N>     потолок K-hop связей на якорь [default: 64]
    --impact <SYMBOL>       AIDDE: impact-паспорт символа (upstream/downstream)
    --impact-cache <DB>     disk-backed таблица символов (SQLite): для баз 65K+ файлов
    --impact-reuse          не перестраивать существующую базу (мгновенные повторы)
    --impact-depth <N>      глубина BFS impact-анализа [default: 3]
    --watch                 watcher: инкрементальный рескан по mtime
    --diff                  дифф-режим watcher: только новые якоря
    --interval-secs <N>     интервал watcher-опроса [default: 2]
    --hidden                показывать скрытые файлы (аналог rg --hidden)
    --graph-export <PATH>   дамп графа сущностей в SQL (схема super-z memory_graph)
    --max-graph-triples <N> бюджет рёбер графа [default: 200000]
    --threads <N>           потоки rayon [default: все ядра]
    --google-auth           OAuth 2.0 loopback: согласие Google в твоём браузере
    --google-gmail [Q]      поиск в своём Gmail (синтаксис Gmail; пусто — недавние)
    --google-drive [Q]      файлы Google Drive по имени (пусто — недавние)
    --google-status         состояние токенов: скоупы, срок, email
    --google-browse <URL>   открыть URL в оконном браузере с профилем poler
    --google-fetch <URL>    прочитать URL через персистентный профиль (headless)
    --google-scopes <S>     доп. скоупы OAuth для --google-auth (через пробел)
    --google-max <N>        лимит Gmail/Drive-результатов [default: 10]
    --nlm-notebooks         NotebookLM: все ноутбуки с источниками (batchexecute)
    --nlm-source <NB> <SRC> контент источника: текст ИЛИ URL картинок слайдов
    --nlm-notes <NB>        сохранённые заметки ноутбука
    --nlm-artifacts <NB>    Studio-объекты: аудио-обзоры, отчёты, квизы, миндмэпы
    --nlm-account           email/настройки сессии NotebookLM
    --nlm-chat <NB> <Q>     вопрос к модели ноутбука ПО ЕГО ИСТОЧНИКАМ
    --nlm-media <URL>       скачать медиа профильным Chromium → ~/.cache/poler-engine/nlm/
    --nlm-shot <URL>        скриншот страницы NotebookLM → PNG
    --nlm-sync [NB]         v0.14: залить ВСЕ ноутбуки в web-index.db (nlm:// URL)
    --web <URL>             v0.8: отрендерить страницу через Chromium CDP
    --web-search <Q>        v0.9+: поиск по web-index.db (BM25+PageRank+фразы)
    --web-db <PATH>         путь к индексу [default: ./web-index.db]
    --web-stats             статистика индекса: страницы, ссылки, PageRank
    --crawl <URL>           краулер: BFS от URL (robots.txt + sitemap + SimHash-дедуп)
    --crawl-depth <N>       глубина краула [default: 2]
    --crawl-max <N>         лимит страниц [default: 25]
    --crawl-delay-ms <N>    задержка между запросами [default: 1000]
    --cross-site            разрешить краулу переходы на другие домены
    --cdp-port <N>          порт CDP Chromium [default: 9222]
    --web-wait-ms <N>       ожидание рендера страницы [default: 1200]
    --headless              headless-режим Chromium
    --no-sandbox            отключить sandbox Chromium (для root/CI)
    --remote-debugging-port <N>  явный порт отладки Chromium
    --mcp                   v0.10: MCP-сервер (stdio JSON-RPC 2.0) для LLM-агентов
    --mcp-http [BIND]      v0.17.4: MCP-сервер по HTTP (Streamable HTTP) для
                           УДАЛЁННОГО агента: POST / или /mcp,
                           Authorization: Bearer <токен> [default: 127.0.0.1:8765]
    --mcp-token <TOKEN>    токен для --mcp-http (или env POLER_MCP_TOKEN;
                           без него — автогенерация при старте)
    --shell                 v0.15+: poler-shell REPL
    --tui                   v0.17: 4-панельный TUI (MiMo Code-style)
    --psi-eta <N>           POLER[Ψ]: шаг ψ-потока [default: 0.05]
    --psi-gamma <N>         POLER[Ψ]: наклон потенциала [default: 0.5]
    --psi-rho <N>           POLER[Ψ]: вес резонанса [default: 0.9]
    --psi-depth <N>         POLER[Ψ]: глубина [default: 8]
    --poler-eta <N>         POLER-цикл: learning rate [default: 0.01]
    --poler-gamma <N>       POLER-цикл: γ [default: 0.1]
    --poler-mix <N>         POLER-цикл: микс [default: 0.1]
    --poler-dissipator <N>  POLER-цикл: диссипатор [default: 0.02]
-v, --verbose               статистика прогона в stderr

Коды выхода (grep-совместимые): 0 — есть совпадения, 1 — нет, 2 — ошибка.

Выходной контракт (Context Anchor)

{
  "query": "нокс",
  "total_hits": 3,
  "anchors": [
    {
      "file": "/path/to/chapter_36.md",
      "token": "нокс",
      "epsilon": 2859.53,
      "resonance": 6185.5,
      "scene": {
        "chapter": "Глава 36. Инертный",
        "temporal_metric": "Метрика: Т-23",
        "location": "Локация: Разлом Каньона",
        "subjects": ["Субъекты: Мальчик (гибрид), Соболь (Нокс)"],
        "enclosing_scope": "# Глава 36. Инертный ..."
      },
      "k_hop_relations": [
        ["Нокс", "вонзила_когти", "Солнечное сплетение"],
        ["Шунт", "сбрасывает_тепло", "1300°C"]
      ]
    }
  ]
}

total_hits — все совпадения до усечения по --top; k_hop_relations — подграф связей корневой сущности (первый токен запроса).

Производительность

Синтетический корпус: 400 файлов × 25 абзацев, 3.14 МБ, 264 400 токенов, 10 400 совпадений, 2 потока (vCPU):

Метрика Значение
Полный прогон (hits-режим) ~770 мс
Field-режим (строго O(N)) ~670 мс
Файлов/с ~520
Пиковая память (плотный корпус 3 МБ) 22 МБ
Пиковая память (стресс-корпус 91 МБ) 99 МБ (v0.2.1: 431 МБ)

Для сравнения: ripgrep находит строки в ~100 раз быстрее, но не возвращает скоупов, метрик, троек и K-hop — это цена полной аналитики на каждое совпадение.

Структура проекта

poler-engine/
├── Cargo.toml                  # clap, rayon, memmap2, petgraph, serde, regex, aho-corasick, walkdir
├── FUTURE_ROADMAP.md           # «превзойти Google»: цель записана, срок не определён
└── src/
    ├── main.rs                 # CLI: --format [ai-json|md|simple], grep-совместимые коды выхода
    ├── mcp.rs                  # MCP-сервер (stdio JSON-RPC 2.0) для LLM-агентов
    ├── lib.rs                  # двухпроходный параллельный пайплайн, EngineConfig, ScanStats
    ├── web/                    # v0.8–v0.11: веб-поиск
    │   ├── cdp.rs              # нативный Chromium CDP-клиент (WebSocket RFC 6455)
    │   ├── crawl.rs            # frontier BFS, robots.txt, sitemap, SimHash-дедуп
    │   ├── index.rs            # SQLite-инвертированный индекс + BM25 + PageRank + WebRank
    │   ├── phrase.rs           # v0.11: позиционный кодек (delta-varint) + фразовый поиск
    │   ├── stem.rs             # кириллический стеммер (uk/рос)
    │   └── …                   # robots, simhash, urlnorm, extract
    ├── google/                  # v0.12–v0.13: сервисы Google без пароля
    │   ├── mod.rs              # google-браузер (порт 9223) + персистентный профиль + GoogleHttp
    │   ├── oauth.rs            # OAuth 2.0 loopback (RFC 8252), refresh, хранилище 0600
    │   ├── api.rs              # Gmail/Drive readonly-API + форматтеры
    │   └── nlm.rs              # v0.13: NotebookLM batchexecute-протокол + медиа-канал
    ├── tokenizer/
    │   ├── pii.rs              # zero-copy (Cow) маскирование PII
    │   └── inverted_index.rs   # индекс всех токенов, включая отрицания
    ├── parser/
    │   ├── ast_code.rs         # лекс-сканер: brace/indent enclosing scope (Rust/C/JS/Python)
    │   ├── markdown_scenes.rs  # сцены: главы, метрики, локации, субъекты
    │   └── triples.rs          # SVO-тройки + call graph + co-occurrence
    ├── resonance/
    │   ├── epsilon.rs          # ε + Ахо-Корасик маркеры + скользящее окно O(1)
    │   └── iir_filter.rs       # R_t = ε_t + φ·R_{t-1}
    ├── graph/
    │   └── entity_graph.rs     # DiGraph (petgraph), K-hop BFS, temporal-слои
    └── output/
        └── context_anchor.rs   # AI-Ready JSON + рендеры md/simple

Известные ограничения (честно)

  • SVO-извлечение троек — эвристическое (морфология русского языка без полного парсера); ориентировано на воспроизводимость и полноту связей, не на лингвистическую точность;
  • потоковая схема v0.3 платит ~30–40% времени за двойную токенизацию (проходы 1 и 2) — сознательный размен памяти на скорость;
  • кириллические запросы проходят предфильтр через быстрый фолд ASCII+кириллицы (одна аллокация на файл, байт-в-байт ≈ to_lowercase), ASCII — через Teddy SIMD без аллокаций,
  • AIDDE — лексический уровень (без полного парсера типов): разрешение перегрузок и trait-диспетчеризации недоступно;
  • в field-режиме семантический бонус не начисляется (он определён на уровне совпадений);
  • память: индексы hit-файлов и их кэшированный текст (до 1 МБ на файл) удерживаются до конца прогона; для сверхбольших репозиториев используйте --local-stats и послабление --max-file-size.

Тестирование

143 теста: 105 unit (математика ε/IIR, сканер скобок, raw-строки, PII, разбиение предложений, K-hop, temporal-фильтр) + 38 интеграционных (воспроизведение контракта спецификации на фикстуре главы 36, call graph, PII-маскирование, детерминизм, сортировка, режимы резонанса).

cargo test
cargo clippy --all-targets   # 0 предупреждений

Docker и CI

# Локальная сборка образа (~120 МБ, debian-slim)
docker build -t poler-engine .

# Поиск в смонтированном корпусе
docker run --rm -v ~/Eteryya:/data poler-engine:latest /data -q "адамантит" -t 5

# AIDDE impact-анализ
docker run --rm -v ~/project:/data poler-engine:latest /data --impact main

CI (.github/workflows/ci.yml): матрица ubuntu/macos, clippy с -D warnings, полные тесты, смоук-тесты бинарника и контейнера, микробенчмарк, автосборка релизных tarball с SHA256SUMS по тегам v*.

Лицензия

POLER Custom Source-Available & Modification Disclosure License v1.0 (модель Unreal Engine EULA, с v0.22.0; см. LICENSE.md — юридический инструмент, TERMS.md — практическая сводка):

  • исходники открыты для изучения, локальной сборки и модификации;
  • обязательное уведомление авторов (dev@poler-engine.org, 14 дней) при дистрибуции/деплое продукта на модифицированном ядре (Notification Clause);
  • публичная редистрибуция ядра и форков запрещена;
  • коммерческое использование — тиры Community/Pro/Enterprise + роялти 5% выручки продукта свыше $25 000/квартал (safe harbor $10 000/квартал);
  • обход Ed25519 License Gate — нарушение лицензии (автоматическое прекращение).

Снапшоты до v0.17.7 включительно остаются под MIT OR Apache-2.0. Статус в бинарнике: poler-engine --license.

About

POLER Engine — AI-Native Topographical, Resonant and Graph Search Engine

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages