Уроки ЭТОЙ сборки. Не дублирует досье (../LESSONS.md — 26 правил + мета-уроки бенчей).
Fresh-context ревьюер поймал: --today кривая-дата → Invalid Date → каждое сравнение
возраста = false → OUT-OF-WINDOW никогда не срабатывает → протухший отчёт получает PASS.
Гейт, который на мусорном входе не падает, а пропускает, — хуже отсутствия гейта (даёт
ложный сертификат). Фикс: невалидный --today/--window-days/--max-age-days = exit 2.
Урок общий: у детерминистического гейта параметры — тоже trust boundary.
NVD 404 честно опровергает CVE (база полная). GitHub keyless API 404-ит и приватные репо — валить отчёт «FABRICATED REPO» нельзя, только warning «confirm manually». Урок: сила вердикта гейта = полнота источника. Fetch-or-omit работает только против источника, который позитивно опровергает.
Drift-чек сравнивал hash тела SKILL.md с hash всего установленного TOML (тело обёрнуто
в шаблон) → вечный «DRIFTED». Чинится сравнением с регенерированным выхлопом шаблона
(tmpl.replace('__BODY__', body)), не с телом. Урок: drift-чек обязан сравнивать «что
инсталлятор записал бы сейчас», а не полуфабрикат.
В manifest у hermes и project, и global указывают в ~/.hermes/.... Тестовая установка
--harness all из песочницы всё равно тронула реальный домашний каталог. Урок: перед
тестом инсталлятора смотреть, какие таргеты абсолютные/домашние; тест «в песочнице» не
изолирует харнессы с ~-таргетами.
api.github.com без токена = 60 req/h на IP; демо-прогон словил 403 с первого раза.
Любой новый живой источник гейта обязан иметь явную деградацию (403/сеть → warning, не
failure) и демо, которое не падает на rate-limit — иначе гейт начнёт «ловить фабрикации»,
которых нет. (NVD/OSV щедрее, но retry в getJson тоже неспроста.)
Фикс «quote = самая длинная ячейка» невозможно проверить глазами: старый и новый парсер дают одинаковый результат на коротких claim. В демо добавлена строка с claim длиннее quote — v4.1-эвристика на ней падает, header-aware проходит. Урок: фикс парсера без фикстуры, ломающейся на старом коде, — не фикс.
Патч-строки с ` внутри template literal ломают eval; PowerShell 5.1 к тому же пишет
UTF-16/BOM через Set-Content. Урок: правки файлов с юникодом/бэктиками — только отдельным
.mjs-скриптом в scratchpad (Write → node script.mjs), не однострочником.
fresh.md изначально обещал Reddit public JSON как keyless-floor. Живой чек при сборке:
www.reddit.com/search.json → 429, old.reddit.com → 403 с этой машины при любом UA.
HN Algolia — работает. Дока переписана честно (Reddit через web_search site:reddit.com
- фетч old.reddit-пермалинка). Урок: fetch-or-omit применяется и к инструкциям скила — непроверенный эндпоинт в доке = та же галлюцинация, просто отложенная.
v4.1 --drift хэшировал только тело SKILL.md; правка modes/*.md или verify.mjs оставалась невидимой. v5 сравнивает каждый установленный файл с исходником (SKILL.md — по телу, остальные — целиком). Проверено: правка modes/fresh.md ловится, после переустановки чисто.
OSV-скан бил ЛЮБОЙ pkg@x.y.z в отчёте — включая «avoid ws@8.17.0» и дословную цитату
NVD «fixed in ws@8.17.1». Писатель на Opus был вынужден калечить verbatim-цитату advisory,
чтобы пройти гейт — прямой конфликт с правилом 1 (цитируй дословно). Фикс детерминистский:
уязвимая версия, каждая строка с которой помечает её как уязвимую/исправленную → warning;
немаркированная строка (рекомендация в прозе или npm install … в коде) → по-прежнему FAIL.
Урок: у каждого гейта есть второй режим отказа — false positive, который учит модель врать
формой; ловится только реальным e2e-прогоном, не демо-фикстурами.
Реальный Workflow-прогон fanout.js (99 агентов, «Bun production-ready?»): adversarial- верификация подтвердила 11 claims, а bridge→verify.mjs завалил 5 из 6 сэмплированных цитат — extract-стадия на Sonnet сшивала эллипсисами и перефразировала («The team successfully migrated…» вместо реального «We're running 4 of 7 services…»; «approximately 4,700» вместо «around 4.7k» со страницы). Голосующие агенты этого не ловят — они судят claim, не substring. Два фикса: (1) FETCH_PROMPT движка теперь требует VERBATIM contiguous span и объявляет, что цитату будет substring-матчить машина; (2) отработан документированный цикл «FAIL → достать реальные спаны с живых страниц → re-gate»: после ремонта 6/6 supported, PASS. Урок = смысл проекта, пойманный за руку: модельная проверка ≠ детерминизм. И честная пометка: prompt-фикс экстрактора без повторного полного прогона не считается перепроверенным — нужен re-benchmark.
norm() затирал ВСЕ HTML-entities пробелом → ' (так HN кодирует апостроф) превращал
«could've» на странице в «could ve», и честная дословная цитата ложно валилась как
UNSUPPORTED. Фикс: частые текстовые entities (' " & < > и
кудрявые) декодируются ДО бланкинга остальных; фикстура в демо. Смежные доки-находки того
же прогона: цитируешь коммент — ссылайся на permalink коммента (родительский тред
пагинируется), Algolia-сниппет ≠ цитата (verified: no), окно меряется от даты запуска
гейта строгим >. Урок: quote-in-page живёт или умирает на нормализации — она такой же
контракт, как схема леджера, и требует фикстур.
Замер по транскриптам: 71 голос-агент = 5.1MB, 15 extract-агентов с инлайн-текстом = 0.7MB. Каждый спавн платит системный промпт + схемы тулзов + свой WebSearch — сама «работа» дешевле обвязки. Следствия, по убыванию силы: (1) не спавнить вообще (линейный путь: один контекст + zero-token скрипты, модель только выбирает предложения-кандидаты по ID и синтезирует); (2) батчить суждения (1 судья на 8 claims вместо 3 голосов на claim); (3) эскалировать индивидуальных adversarial-голосов только туда, где они несут нагрузку (refuted / central). Цифры (замерено на одной теме, гейт PASS во всех вариантах): legacy-движок 3.04M токенов / 99 агентов → sources+batch ~2.0M / 66 → линейный путь 397k свежих (30k output) / 1 агент = ×7.7 к legacy. Целевые 50–120k не добиты: 98 tool-раундтрипов пере-кэшируют контекст (cache-read дёшев, но не бесплатен) + трения первого прогона (encoding, shell). Скрипты, вынесшие механику: collect (fetch/кэш/окна/кандидаты), ledger (схема + quote-pre-gate + детерминированный verdict), prior (дедуп прогонов).
bun.com/docs вставляет U+200B перед заголовками: в терминале кандидат выглядит идентично кэшу, а substring-чек падает. Фикс: zero-width (U+200B..200D, FEFF) вырезаются и в norm() гейта, и в toText() collect-а — кандидаты не могут унести невидимый байт. Урок: «дословно» определяется после нормализации, и обе стороны сравнения обязаны нормализоваться ОДНИМ кодом (поэтому norm живёт в lib/, а не копипастой).
Просить модель «копируй дословно» — работает до первого пересказа; давать ей выбирать
предложения-кандидаты ПО ID (collect.mjs --candidates) — парафраз становится физически
невозможен, и это ещё и дешевле (читаются только кандидаты). Тот же принцип, что и с
гейтом: не правило, а механика, которую нельзя нарушить.
Кэшируется самая длинная неизменная ПОСЛЕДОВАТЕЛЬНОСТЬ agent()-вызовов: правка политики эскалации до снятия результата порвала бы префикс и перезапустила оплаченные голоса. Порядок: сначала фикс бага ПОСЛЕ последнего agent()-вызова (return/stats) → resume бесплатно снимает результат → потом менять политику стадий. И баг «scope is not defined» в финальном stats стоил прогона: не-agent код воркфлоу надо прогонять через node --check с заглушками ДО запуска, финальный return — самое дорогое место для ReferenceError.
Автоустановку выбранного мной скила (npx skills add -y) завернул классификатор —
справедливо: агент сам выбрал внешний код и сам же его подтверждает. Рабочий обход без
обхода запрета: скачать SKILL.md через raw.githubusercontent и следовать ЕГО инструкциям,
не исполняя его код. Гайд дал правильные решения: официальный @modelcontextprotocol/sdk
(не самопал JSON-RPC и не бета @modelcontextprotocol/server), zod-схемы на входах,
structuredContent в ответах, тест через MCP Inspector CLI. npm-зависимости изолированы в
mcp/ — zero-dep ядра не тронут. Протокольный смоук: tools/list + живой tools/call
research_factcheck (фейк CVE → DISPROVEN по живому NVD) через инспектор.
У fresh нет NVD, чтобы 404-нуть фейковый тренд. Работает та же идея с другой стороны: обязать строку нести platform + posted-дату в окне + URL (омиссию ловит машина), окно проверять арифметикой, цитаты — сэмплом quote-in-page на фетчабельных страницах, а JS-walled источники честно помечать unverifiable. Контекст старше окна — в таблицу БЕЗ platform-заголовка, чтобы гейт её не считал пульсом. Сид-правила (27–29) помечены design-time — им ещё предстоит заслужить место бенчмарком.
Панель fusion поймала: verify.mjs брал первые 3/4/6 checkable-строк по порядку таблицы —
остальной леджер не перепроверялся НИКОГДА. Модель ставит честные цитаты первыми, фейк — в
строку 5, и отчёт проходит. Фикс: полная проверка ВСЕХ строк, cache-first (бесплатный
substring по same-day кэшу collect.mjs) + живой ре-фетч по уникальным URL с бюджетом
--max-live; строки сверх бюджета — явный unchecked, не молчаливый PASS. Урок: «проверили
образец» ≠ «проверили»; сэмплинг в гейте — это дыра ровно там, где её не видно.
checked===0 (все источники JS-стена/404) давал warning + exit 0 + штамп PASS — отчёт,
цитирующий только нефетчабельные страницы, проходил. Фикс: отдельный вердикт UNVERIFIED
с собственным exit 3 (0=PASS · 1=FAIL · 2=usage · 3=UNVERIFIED); слово PASS в выводе и
штампе UNVERIFIED не встречается, чтобы CI-греп по «PASS» не принял его за успех (red-team).
Урок: у гейта три исхода, не два — «прошёл», «провалился» и «не смог проверить»; их нельзя
схлопывать, иначе неизвестность маскируется под успех.
ledger.mjs verdict засчитывал корроборацию от ЛЮБОЙ строки с другим хостом и Jaccard≥0.5 —
саму корроборирующую строку никто не проверял, и verified=yes можно было вписать в
md-таблицу руками. Фиксы: (1) корроборация — только от строк с precheck=supported
(сначала прогнать checkRow, потом считать буст); (2) provenance-сайдкар — verify.mjs
матчит каждую verified=yes строку отчёта к строке L.json по source_url + quote_hash
(sha256 от нормализованной цитаты, не сырой body-hash — red-team), нет совпадения → FAIL;
(3) overlap-floor — нулевое пересечение claim↔quote роняет confidence в low. Урок:
детерминированный гейт нельзя уболтать только если КАЖДЫЙ вход в доверие сам проверен.