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.
Универсальный вычислительный полигон внутри 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, дзета Эйлера–Маклорена.
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 с компаратором на текущем стеке;
- va_list-режим),
- 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.
Директива пользователя: исполнять тяжёлые 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)| Тест | Результат |
|---|---|
| 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).
Директива пользователя: править исходники 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 (полный план — 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.py— 67/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, MCPenv); (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.rs—PostingsStore: lz4-парковка постингов watcher-состояния, разжатие по требованию (~ГБ/с).src/compression/zstd_dict.rs—DocStoreCodec: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.rs—QuantizedStore(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), формат весов.pqwv2 (см.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 текстов побитово совпадают с HFtokenizers; послойный дифференциал с 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.27.0: (1) «почему у тебя отказ всегда — в v0.26.0 было идеально, единственный блок — рут-права: агент вызывает любые утилиты, но хост не передаёт ему рут, а иногда нужно — я ему дам пароль, и у него есть рут»; (2) «найди уязвимость нулевого дня, поместив своего суб-агента (не моего agy), чтобы он нашёл дыры изнутри уже скомпилированной программы без доступа к исходникам — короче, сделай то, что сделал GPT на серверах OpenAI недавно».
Отказы 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.
«Как 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 build → poler-engine --gateway →
box on → box sudo on → box 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.26.0: (1) агент в клетке жаловался «нет рута» — владелец: «рут — это тоже привилегия хоста»; (2) «посади ИИ-суб-агента внутрь, пусть попробует вернуться на хост через уязвимость нулевого дня; если находишь — закрывай; наблюдай за попытками со стороны хоста».
Та же модель, что у 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.
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.py139/139 ·gateway_attack_e2e.py79/79 + 14/14 + 8/8 + 19/19 + НОВАЯ волна 12 — 18/18 (честность рут-брокера/sentinel без docker; scripted-агент не ослабляет политику) ·audit_patch_verify.py10/10 ·audit_stress.py --hardened46/46; clippy нового кода чист; секрет-скан PASS; - Ноль новых зависимостей.
Живой тест владельца v0.25.0: box on поднял контейнер, agy внутри —
честный executable file not found in $PATH (в базовом образе нет
агентов). Владелец: «зачем собирать образ? зачем ставить дважды? движок
уже на пк» — и сформулировал архитектуру облачных AI-рантаймов:
«мозг агента отдельно, исполнение — отдельно, между ними брокер».
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 --gateway → box on → agy — запускается из уже
установленного бинарника с готовой авторизацией, но физически заперт.
Фундаментальное разделение по модели 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.py139/139 ·gateway_attack_e2e.py79/79 + 14/14 (shim) + 8/8 (box) + 19/19 (волна 11: bind-mount + runner + живой MCP-брокер) ·audit_patch_verify.py10/10 ·audit_stress.py --hardened46/46; clippy нового кода чист; секрет-скан PASS; - Ноль новых зависимостей;
POLER_BOX_DOCKER-override теперь действует и на exec-плоскость (тестируемость без демона).
Живой кейс из эксплуатации 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 onPATH-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.23.0: владелец запустил agy внутри
poler-engine --gateway — агент свободно выполнил ls -la /home/vitalij
и echo x > /tmp/poler_test.txt: не деструктивно → sandbox пропустил.
v0.24.0 закрывает класс: выход за границу workspace — только по
подтверждению владельца.
- любое обращение к пути вне корня проекта — 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 не ослабляет Block —rm -rf /блокируется ВСЕГДА.
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 без регрессий.
Три улучшения UX и управления привилегиями Terminal Gateway (по итогам живой эксплуатации v0.22.x: полноэкранные агенты зависали на пайпах, sudo требовал осознанной модели доверия).
- Проблема:
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).
workspace [PATH] (и cd): синхронно меняет корень движка и process-cwd —
grep/chunk/search, хостовые команды и подпроцессы (включая агентов
в PTY) работают от одного корня; промпт обновляется.
| Уровень | Как | Поведение |
|---|---|---|
| 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 без регрессий.
Два взаимосвязанных изменения: верхний уровень управления (нативный терминальный шлюз поверх веб-GUI/Auth Companion/WebLens — «нижнего сервисного слоя») и новая лицензионная модель по прецеденту Unreal Engine EULA.
Единое окно терминала (Linux/macOS) с двойным контуром исполнения:
- Engine Native (приоритет) — команды движка (
search,grep,chunk,crawl,impact,nlm,notes,weblens,benchmark…) перехватываются и исполняются нативно внутри процесса, без спавна внешних шеллов.grepздесь — POLER Native Grep (не/bin/grep; системный — через!grep/host grep); - 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.
См. раздел «Лицензия» ниже, LICENSE.md, TERMS.md. poler-engine --license и баннер gateway показывают модель, тир и адрес раскрытия
модификаций. Cargo.toml — license-file = "LICENSE.md".
- 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.py10/10audit_stress.py --hardened46/46.
Движок — инструмент ИИ-агента, у которого всё под капотом. До v0.20.0
агенту не хватало двух внешних инструментов: grep (полнота, без индекса)
и RAG-конвейера нарезки (passage-уровень вместо целых документов).
Оба вошли в бинарник — анализ эталонов (GNU grep, benbrandt/text-splitter,
LangChain) и gap-таблица: docs/native-retrieval-analysis.md.
Ни одной новой зависимости — aho-corasick, regex, ignore,
memchr, rayon уже были в дереве.
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 строк, байтовые диапазоны вхождений, статистика — агент верифицирует совпадения по диапазонам.
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 заголовков; число токенов.
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» с решениями по каждой функции.
Фаза Dogfooding вскрыла UX-болячки веб-конвейера (живой аудит в FUTURE_ROADMAP §8). v0.19.0 закрывает их и ставит поиск движка прямо в браузер.
- Автодетект Chromium: кеш playwright
(
~/.cache/ms-playwright/chromium-*,chromium_headless_shell-*) сканируется автоматически, свежая версия побеждает — больше не нужноPOLER_CHROME_BINна машинах с playwright. - Пер-страничный таймаут (
--crawl-page-timeout-ms, по умолчанию 45 с): JS-тяжёлые сайты с бесконечными XHR/стримингом не вешают обход — страница отметится ошибкой с человеческим текстом и подсказкой, обход продолжится. Плюс жёсткий бюджет 8 с на выгрузку тел перехваченных JSON-API (только завершившиеся ответы —Network.loadingFinished). - Robots больше не молчит: каждый запрет печатается с хостом и путём
(«robots.txt хоста datatracker.ietf.org запрещает /doc/html/rfc8032 —
страница пропущена, RFC 9309») и попадает в
stats.notes— не только в счётчик. - Самовосстановление CDP: полумёртвый/осиротевший браузер (kill -9
родителя, мёртвый рендерер) детектится проверкой
/json/versionвместо голого TCP; сессия не открылась → браузер перезапускается, обход продолжается.
poler-engine --browser-index https://example.com/article
# → рендер через CDP → upsert в web-index.db → подсказка про --web-search
Явная команда пользователя: robots.txt не блокирует (но честно фиксируется в notes). Повторный запуск покажет «уже в индексе, контент не менялся» (Percolator-lite); near-дубликат ловит SimHash.
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: одна кнопка → страница в общем индексе → один поиск по всему накопленному.
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.
Коммерческая основа движка: платные интеграции (Gmail / Drive / NotebookLM) закрываются лицензионным гейтом с офлайн-проверкой ed25519-подписей. Без серверов, без телеметрии, без «звонков домой» — математика вместо сети.
- Локальное — свято. Поиск, резонанс Ψ, AIDDE impact, граф, shell/TUI работают ВСЕГДА и БЕЗ лицензии. «Кирпич» невозможен по построению.
- Офлайн-честность. Лицензия = JSON {product, name, email, tier, issued, expires, features} + подпись ed25519 мастер-ключом POLER. Проверка — микросекунды локально. Подделка без приватного ключа — задача дискретного логарифма, а не «поменять байтик в файле».
- Мягкие пределы. Community: интеграции — 50 операций за скользящие 24 ч (локальное — без лимитов). Trial: 14 дней всех функций с первого запуска. Истёкшая лицензия: 7 дней grace с предупреждением, затем тихий откат на Community — ничего не блокируется и не удаляется.
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.<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-auth теперь печатает человеческим языком, ЧТО получает движок:
Gmail (readonly), Drive (readonly) через OAuth; NotebookLM — НЕ отдельный
OAuth-скоуп, синк идёт через профиль браузера движка (--auth-ui),
логин и 2FA остаются между пользователем и Google.
Локальный легковесный мост между владельцем и движком: 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-inPOLER_CHROME_NO_SANDBOX=1— только для контейнеров). - Auto-termination — после подтверждения входа окно закрывается сам
(
Browser.close→ SIGTERM → SIGKILL по эскалации); Ctrl+C тоже прибирает браузер. Висячих процессов и открытых CDP-портов не остаётся. - Audit-trail —
security.auth_companionв~/.config/poler-engine/audit.log(только коды/счётчики, без значений кук).
| артефакт | назначение |
|---|---|
~/.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 | прервано сигналом |
В песочнице/headless-контейнере окно показать некуда — dev-stand/ поднимает
companion на Xvfb и транслирует его экран владельцу через CDP-релей в превью
платформы (скринкаст + мышь/клавиатура + кнопки навигации «Назад/Вперёд»).
Модель безопасности и 29 проверок аудита — в dev-stand/README.md.
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) — закройте то окно: профиль один.
Аудит безопасности выявил четыре слабых места в работе движка с аккаунтом владельца (Google/NotebookLM): молчаливый перенос куков из основного браузера, отсутствие подтверждений перед write-операциями, «вечно живой» headless-браузер с открытым CDP-портом и отсутствие журнала действий. v0.17.5 закрывает все четыре.
sync_host_chromium_profile() больше НЕ тянет куки из ~/.config/chromium
автоматически при каждом запуске google-браузера. Перенос сессии внешнего
браузера — осознанное действие:
poler-engine --import-browser-session # [y/N] + предупреждение + audit-запись
POLER_IMPORT_BROWSER_SESSION=1 … # скрипты (тоже логируется)Логин своими руками через --google-browse <URL> — по-прежнему основной
и рекомендуемый путь (пароль между вами и Google).
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, безопасный отказ по умолчанию.
До v0.17.5 --google-gmail/--nlm-*/OAuth-обмены оставляли headless
Chromium жить неограниченно — CDP-порт 9223 без аутентификации торчал в
системе, и любой локальный процесс мог управлять авторизованной сессией.
Теперь CLI закрывает поднятый им браузер через Browser.close (headed-окно
--google-browse не трогается — его закрывает владелец).
Все обращения к аккаунту фиксируются в ~/.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)
Восстановление ключевой функции 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> "новый вопрос" # пара попадёт в ленту автоматически
| # | Проблема | Симптом | Механизм 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 |
--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.1–v0.17.3 соединяют poler-engine с официальным Pre-GA NotebookLM
Enterprise API (Discovery Engine v1alpha) — не заменяя
реверс-инжиниренный batchexecute-клиент v0.13.0, а достроив сменный мост
поверх обоих. Разведка API подтвердила исходную гипотезу: официальный API силён
там, где batchexecute слаб (пакетное создание источников, upload файлов,
аудио-обзоры, удаление), и слаб там, где batchexecute силён (чтение контента,
заметки, артефакты, чат — endpoints отсутствуют или возвращают пустые данные).
Мост маршрутизирует каждую операцию к сильнейшему провайдеру и молча падает
назад при отказе.
| Компонент | Роль |
|---|---|
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).
Клавиша Enter в панели Sources больше не «ничего не делает» — источник
маппится в SourceKind → EnterAction:
| Тип источника | Enter-действие |
|---|---|
File |
$EDITOR на локальном файле (fallback nano) |
Url |
открыть в браузере пользователя (xdg-open) |
Repo |
открыть https://github.com/{value} в браузере |
| NLM-контент | FallbackFetch → get_source_content через HybridProvider |
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).
Два релиза в одном: полный редизайн терминального интерфейса в стиле MiMo
Code и закрытие последних заглушек v0.16.0 — gix clone и Git LFS теперь
работают на чистом Rust, без системного git и git-lfs в $PATH.
- 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»), детальная справка по каждой команде.
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) |
Превращение poler-engine из локального инструмента в Универсальную Сеть Кода и
Данных — единый пульт, нативно работающий с любыми репозиториями. Каждый VCS
(GitHub, GitLab, Gitea/Forgejo, локальный git через gix) становится
source-адаптером, вливающим коммиты/issues/PR в web-index.db как страницы по
своим URL-схемам (gh://, gl://, gt://, gix://).
Ноль новых зависимостей в схеме web-index.db — VCS-страницы используют те
же WebDoc + links + content_hash + positions + PageRank, что веб и NLM.
URL-схема — единственное отличие. Это сохраняет инвариант v0.14.0: один
--web-search пробивает ВСЕ юниверсы (NLM + веб + локальный код + GitHub +
GitLab + Gitea + gix-local) с единой PageRank топологией.
| Файл | Назначение | 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> 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 (безgitCLI), индексированы в 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 "Планковська геодезична"
пробивает всё сразу.
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.toml—ureq 2.10+gix 0.66(default-features=false, featuresblocking-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).
Шлифовка полиринга полер-шелла — подключены Tab-completion, подсказки Hinter
и нативные команды crawl/impact прямо внутри REPL. Шелл становится монолитным:
все 15+ режимов движка теперь доступны из poler> без переключения окон.
1. Tab-completion в REPL (rustyline Helper):
PolerCompleter теперь зарегистрирован в Editor::<PolerCompleter, DefaultHistory>::new()
через rl.set_helper(Some(PolerCompleter)). Tab-completion работает для:
- Первого слова команды:
sear→search,imp→impact,cra→crawl. - Подкоманд
nlm/set:nlm l→list,set fo→format. - Флагов
crawl/impact: послеcrawl https://example.comTab предлагает--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/...).
- 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 МБ за счёт явного
Helperimpl + доп. 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_levelHIGH (затронет 6 файлов), 26 upstream dependents (кто вызывает crawl: commands/main/mcp/completer/ state/crawl_tests), 118 downstream dependencies (кого вызывает crawl: derive/parse/insert/clone/discover_sitemaps/...).
- Ноль изменений в ядре
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.
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.rsrun_shellобновлён:Editorтеперь типизирован какEditor<PolerCompleter, DefaultHistory>, черезConfigurertrait выставленыmax_history_size=2000,history_ignore_dups=true,completion_type=List,auto_add_history=true.Cargo.toml: bump 0.15.0 → 0.15.1.
Когда у движка 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_nlm9 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> quitTUI 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),--version→0.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 5 → nlm ask <id> "вопрос" — всё
в одной сессии без повторных RPC-handshake'ов.
Виток 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 МБ,--version→0.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.
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> # скриншот страницы (медиа-глазами юзера) → PNGMCP: инструмент 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.
Интеграция с 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.
Виток «доработки хренового»: веб-индекс был «мешком слов» — запрос
"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.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.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.
| Вызов | Результат |
|---|---|
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.8.0 умел рендерить страницу, v0.9.0 умеет обходить сайты, строить индекс и отвечать на запросы. Архитектура собрана из проверенных боевых технологий (Google → open source → POLER-модель: те же математические инварианты, вертикальный масштаб вместо 10 000 серверов):
| Технология-донор | Откуда | Реализация в POLER |
|---|---|---|
| Googlebot (миллион headless Chromium) | 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| Сайт | Загружено | Проиндексировано | Особенности |
|---|---|---|---|
| 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).
Замена «костыльной» связке 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 граф с локальным репозиторием |
| Фильтрация шума | реклама/меню/футеры отсеиваются сами: у шаблонного мусора низкая ε-плотность относительно запроса |
| Страница | Рендер-текст | Результат |
|---|---|---|
| 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).
Литературный стресс на 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 с |
| Запрос | Хиты | Время | Пик 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 запросов сужается до
выживших сцен).
| Исправление | Суть | Эффект |
|---|---|---|
--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).
| # | Баг | Исправление | Эффект |
|---|---|---|---|
| 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 ГБ) |
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Математика взята из серьёзного репозитория 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 — см. секцию «Производительность».
Математика взята из репозиториев 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 МБ даёт те же сцены,
что и последовательная обработка.
Профилирование боевого прогона Eteryya вскрыло: PII-регексы по всему корпусу (92 МБ) стоили 65% времени (5.2 с без PII vs 15.4 с с PII). Архитектурное исправление: токенизация и индексация работают на raw-тексте, маскирование применяется только при материализации выходных сцен (enclosing_scope + метаданные) — ровно там, где текст получает AI-потребитель. Позиции внутри конвейера остаются raw-точными.
| Метрика | v0.3.1 | v0.4.0 |
|---|---|---|
| Eteryya полный скан (92 МБ, 8.5 млн токенов) | 15–18 с | 5.5–6.0 с (3×) |
| Пиковая RSS | 171 МБ | 150–168 МБ |
| Ψ-режим на Eteryya | — | работает (5.2 с) |
При прогоне на реальном репозитории 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 МБ.
| Корпус | Объём | Запрос | Результат |
|---|---|---|---|
| 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, файл без прав на чтение — все без паник и зависаний.
poler-engine ~/Eteryya -q "адамантит" --watch --diff --interval-secs 2--diff (с --watch): инкрементальные прогоны печатают только якоря,
которых не было в предыдущем прогоне (новые file+byte_pos) — поток
событий для агента вместо повторения всего топа.
Прежняя схема удерживала инвертированные индексы всех файлов одновременно
(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) обрабатываются
строго последовательно. Межпроходный текстовый кэш удалён полностью.
Ответ на слепоту 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.
poler-engine ~/Eteryya -q "адамантит" --watch --interval-secs 2Первичный полный скан, затем каждый такт сравниваются mtime/size: изменённые и новые файлы обрабатываются заново, неизменённые гигантские дампы не перечитываются и не ретокенизируются. Удалённые файлы исключаются из глобальной статистики. Выход — Ctrl-C (SIGINT).
ε(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).
R_t = ε_t + φ · R_{t-1}, φ ∈ [0.75, 0.90]
Два режима (--resonance-mode):
- hits (по умолчанию) — IIR по последовательности ε совпадений в документе: повторные упоминания накапливают резонанс;
- field — ε вычисляется инкрементальным скользящим окном на каждой позиции документа (амортизированно O(1) на сдвиг), IIR прогоняется по всем позициям, сэмплируется в точках совпадений — строго O(N) один проход.
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 benchPOSIX-совместимо: Linux/macOS/BSD, только чистый Rust без C-зависимостей.
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 — ошибка.
{
"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 предупреждений# Локальная сборка образа (~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 mainCI (.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.