[QA-trace][#119 1/7] Процесс-отчёт прогона web-test-orchestrator (git-sync): агенты, инструменты, фолс-позитивы #235
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Процесс-трейс QA-отчёт — PR #119 «native two-way git-sync» (Docmost/gitmost fork)
1. Сводка прогона
Стадии (по скиллу): recon → mapping → charters → execution (6 персон-слайсов) → oracle-evaluation → independent verifier-проход → синтез.
Агентов задействовано: 1 recon + 6 исполняющих слайсов + 1 verifier = 8 ролей.
Воронка находок:
Подтверждённые баги по тяжести: 4×high (freeze всего пространства; push-endpoint забрикован; non-idempotent дублирование; отсутствие сигнала/recovery — этот medium, см. ниже), несколько medium/low. Ключевой кластер — семейство git-sync conflict-freeze (один same-line конфликт навсегда останавливает синк всего space в обе стороны, без UI-сигнала и без product-recovery).
2. Какие агенты/инструменты отработали
recon
Покрыл: структуру vault через
git clonesmart-HTTP, сверку имён файлов с деревом SPA, базовую auth-персистентность.Инструменты: Playwright (headless chromium-1148, explicit executable_path + --no-sandbox; console + response≥400 listeners, full-page screenshots через Read, aria-label enumeration), Bash (curl health, git clone, find/ls/cat-диф файлов), Grep/Read по фронту (edit-space-form.tsx).
Как проявил себя: силён в out-of-band оракуле (clone vault → diff имён) и в честности — сам же написал disconfirm своей же auth-гонки (401-storm) как tooling-race, а не баг приложения. НО: одна содержательная находка (обрезанные тайтлы) оказалась мисатрибуцией (см. §4).
editor-content QA
Покрыл: ввод контента через реальную клавиатуру/мышь, slash-меню, аплоад картинки через file-chooser, reload-персистентность, и — главное — впервые применил git-export-оракул (clone/pull/log vault) как кросс-проверку записи.
Инструменты: Playwright sync_api (per-page console/pageerror, network status≥400 / /files / /pages, full-page screenshots-as-images, hard-reload round-trips, синтез крошечного PNG для аплоада), Bash git +
ps aux/ss -ltnp(инспекция серверных процессов).Как проявил себя: самый ценный методологически — именно reload-round-trip + git-pull-после-создания-страницы вскрыл outbound-stall (подтверждён) и flushSync-console-error (подтверждён). 0 ложных. Свою speculative-формулировку push-stall честно отдал verifier'у, тот её апгрейднул до verified.
tree-nav QA (organizer persona)
Покрыл: большое дерево (~315 узлов), drag-n-drop reparent, move/trash/restore, cursor-пагинацию sidebar.
Инструменты: Playwright async (storage_state reuse, in-page
fetch()к /api/pages/sidebar-pages|info|move как оракул, aria-tree DOM-дампы a[role=treeitem], синтетический mousedown→stepped-move→mouseup для HTML5 DnD), Bash git-clone как ground-truth, триангуляция трёх оракулов (UI-tree / API / fresh clone).Как проявил себя: дисциплинирован по триангуляции и честен про tooling (сам пометил DnD-overshoot и crash long-lived контекстов как env). НО его главная содержательная находка (outbound git-sync stalled) — ложная: снят transient-снимок дивергенции (см. §4).
share-search-settings QA (power-user)
Покрыл: Share-popover, публичный шеринг, Ctrl+K поиск, настройки space/workspace, SEO-роут :3000, /l/:alias.
Инструменты: Playwright (incognito public-share render, toggle switches, requestfailed capture), Bash/curl детерминированные пробы /api/search и /api/shares/*, docker psql к gitmost-test-pg (ground truth: text_content length, page_permissions, share_aliases), статический grep/Read контроллеров.
Как проявил себя: сильный на code-recon-before-drive (grep call-sites → нашёл dead-feature «disable public sharing» без UI — подтверждено decisive zero-call-site-доказательством). Но поисковую находку выдал без контрольной группы → verifier её убил (см. §4). FP ≈ 50%.
ai-chat-ui QA (adversarial slice)
Покрыл: AI-чат send/reopen-from-history, классификацию ошибок, персистентность сообщений.
Инструменты: Playwright (cookie-injected authToken в обход rate-limited login, real role-pick+send+reload+open-from-history), network/console/DOM capture, Bash+curl (z.ai egress-проба), docker psql (ai_provider_credentials / ai_chats / ai_chat_messages), чтение live server stdout через /proc//fd, source review.
Как проявил себя: образцовый adversarial-слайс — 3/3 подтверждено. Особо ценный приём: psql по ai_chat_messages показал orphaned user-row без assistant-row (data-loss-on-reopen), а чтение q119-server.log через /proc дало бонус-находку про застрявший git-sync orchestrator. 0 ложных.
git-roundtrip tester
Покрыл: персону «целевой пользователь правит vault через git» — clone/edit/push, идемпотентность экспорта.
Инструменты: Bash+git (log/show/diff/reflog/status, per-revision подсчёт маркеров заголовков по истории «Docmost Sync» коммитов), urllib-login с уважением retry-after (обход rate-limit), Playwright (cookie через document.cookie — add_cookies крашился «Invalid cookie fields»), чтение src (cycle.ts, page-change.listener.ts).
Как проявил себя: лучший по idempotency-классу — git-archaeology (монотонный рост 0→159 копий блока по 162 sync-коммитам, всё авторства бота «Docmost Sync») + свежий live-repro доказали non-idempotent push. 2/2 подтверждено, 0 ложных.
dual-edit git-sync tester
Покрыл: ядро PR — одновременные правки UI vs git на одной строке; состояние серверного vault, push-endpoint, recovery-поверхность.
Инструменты: Code trace (Read/grep всего пайплайна: collaboration.handler, yjs-body-merge, three-way-merge, orchestrator, engine cycle/pull/git), Bash/git (clone, реальные push, инспекция СЕРВЕРНОГО vault /tmp/gitmost-vaults/: git status, MERGE_HEAD, config, hooks), docker psql (DB text_content vs .md), server-log timeline grep, curl POST /api/git-sync/trigger, Playwright cookie-injected.
Как проявил себя: самый глубокий по git-sync — 4/5 подтверждено (freeze, забрикованный push-endpoint, отсутствие сигнала/recovery, throttler-misconfig). Метод «предсказал из кода → подтвердил живьём на серверном vault» очень силён. Единственная понижённая — честно помеченный coverage-gap (см. §4).
verifier (независимый проход)
Покрыл: все 16 содержательных находок, каждую — независимым repro с нуля.
Инструменты: повторный git-clone+archaeology,
nodeдля запуска реальной buildTsQuery(), docker psql, curl-репро, чтение src по точным строкам, route-пробы (404/401 для несуществующих endpoint), индукция свежего конфликта в throwaway-ветках vault с последующимgit merge --abort+ проверкой recovery.Как проявил себя: критически полезен — убил 4 ложных (titles, outbound-stall, search, convergence-premise), причём не отписками, а контрольными экспериментами (запуск buildTsQuery в node; свежий probe-page с propagation <1мин; landing реальных push после rebase). Заодно апгрейднул editor-content push-stall speculative→verified, докопавшись до истинной причины (wedged docmost→main merge).
3. Найденные баги — по КЛАССУ
A. data-sync-freeze / unrecoverable-conflict (HIGH) — флагман
Проявление: один same-line dual-edit (UI-правка vs git-push в ту же строку) → PULL-фаза делает
git merge docmost→main, конфликт остаётся неразрешённым в working tree серверного vault; дальше КАЖДЫЙ ~15с циклrunCycleупирается вisMergeInProgress()и возвращает skipped:'merge-in-progress'. Синк всего space умирает в обе стороны, переживает рестарт сервера.Как/когда/кем найден: сначала dual-edit tester предсказал из code-trace (cycle.ts L88-98 возвращает skip ДО pull/push, без abort), затем подтвердил живьём — инспекция серверного vault (git status = UU, MERGE_HEAD), grep q119-server.log (CONFLICTED → непрерывный skip-loop), POST /git-sync/trigger → {ran:false,skipped:'merge-in-progress'}. Параллельно git-roundtrip независимо словил тот же wedge через stale clone HEAD + /proc-лог. Verifier воспроизвёл с нуля (repro.sh: clone → same-line edit на main → REST HUMAN2 → немедленный push в окно pull) — конфликт с 1 попытки.
B. availability / push-endpoint-bricked (HIGH)
Проявление: vault обслуживается как non-bare repo с
receive.denyCurrentBranch=updateInstead; грязное (конфликтное) working tree → push-to-checkout отклоняет ЛЮБОЙ ref-update, включая абсолютно несвязанный новый файл:! [remote rejected] main -> main (Up-to-date check failed). Один конфликт одной страницы брикует push всего space.Как/когда/кем найден: dual-edit tester, изолированным repro вне браузера — fresh clone, commit нового PUSHDIAG-файла поверх origin/main, push → reject,
git ls-remoteбез изменений; затем инспекция config (is-bare=false, updateInstead). Verifier повторил живьём + офлайн-репликой с теми же 4 config.C. duplication / non-idempotent-push (HIGH)
Проявление: push на последующих циклах не no-op, а APPEND полной копии тела страницы. Один файл удвоился между двумя «Docmost Sync» коммитами за 14с; худшие — рост 0→159 копий блока за 162 цикла (12.8KB), 315 копий/57KB. ≥9 файлов vault с повторяющимися блоками.
Как/когда/кем найден: git-roundtrip tester, git-archaeology — per-revision
grep -c '^#'по истории бот-коммитов показал монотонный рост;git diff <c1>..<c2>показал чистый append-дубликат, автор = «Docmost Sync» (не человек). Verifier подтвердил свежим live-repro (создал страницу в UI → следующий же цикл удвоил без второй правки).D. silent-failure / no-surfacing / no-recovery-path (MEDIUM/LOW)
Проявление: замёрзший синк сигналится ТОЛЬКО INFO-логом (117–1537 повторов «unresolved merge…skipping cycle»), GET /api/git-sync/status отдаёт лишь статический конфиг (никакого per-space health), нет ни UI-индикации, ни API abort/resolve; recovery требует shell-доступа к
git merge --abortв /tmp/gitmost-vaults/.Как/когда/кем найден: ai-chat QA наткнулся побочно (читал server.log через /proc, диагностируя AI-500). dual-edit tester оформил как отдельный класс из чтения git-sync.controller.ts + route-проб (POST /abort, /resolve → 404). Verifier подтвердил всеми четырьмя линиями (source, 404/401-пробы, engine-механизм, grep клиента = ноль consumer'ов /status).
E. silent-failure / error-misclassification (MEDIUM) — AI-чат
Проявление: отправка сообщения → HTTP 500 {"error":"Internal server error"} + дженерик-баннер «Something went wrong, please try again», тогда как реальная причина — z.ai 401 (классифицируемая). Сбой после
res.hijack()→ bare-500, а providerDetail() дропает строку «internal server error» в дженерик.Как/когда/кем найден: ai-chat QA — драйв реального send в headless, network-listener поймал 500+body, DOM-query — баннер, кросс-чтение error-message.ts + ai-chat.controller.ts, docker psql (провайдер ДЕЙСТВИТЕЛЬНО сконфигурён), curl (z.ai реально 401). Verifier воспроизвёл с нуля (redis-cli DEL throttle-ключей перед login).
F. data-loss-on-reopen / silent-failure (MEDIUM) — AI-чат
Проявление: после неудачного хода в БД остаётся orphaned user-message без assistant/error-row; после reload + reopen из history — вопрос без ответа и без индикации ошибки (live-баннер только в useChat().error, теряется на remount).
Как/когда/кем найден: ai-chat QA — docker psql по ai_chat_messages (одна user-row, err/finishReason NULL), затем Playwright reopen-from-history → баннеров ноль. Verifier подтвердил (8 orphan-чатов в БД + repro).
G. rendering / console-error (LOW)
Проявление: на каждом hard-reload страницы с node-view контентом 2–4× error-level
flushSync was called from inside a lifecycle method(стек PureEditorContent→EditorProvider→PageEditor). Функционал не ломается — шум, но error-level на ядре редактора.Как/когда/кем найден: editor-content QA — console-capture в reload.py round-trip. Verifier уточнил корреляцию: nv=0 страницы flush=0, страницы с react-renderer node — flush=2 (изначальный repro дал false-negative на plain-text странице, что и спровоцировало правильную node-view-гипотезу).
H. throttling-misconfig / leak (LOW)
Проявление: POST /api/auth/login возвращает 429 с
retry-after-public-share-ai, при том что x-ratelimit-remaining-auth ещё 9/10 — анонимный public-share-AI лимитер (5/min) матчит login-роут; SPA-login делает ~3 вызова → реальные юзеры могут периодически не залогиниться.Как/когда/кем найден: dual-edit tester — Playwright-login споткнулся о 429, curl -i показал хедеры, чтение throttle.module.ts. Verifier подтвердил curl-циклом из 7 POST + чтением @SkipThrottle на auth.controller.ts.
I. dead-feature / missing-control (MEDIUM)
Проявление: «Disable public sharing» полностью разведён на бэке (+audit+cascade) и Share-popover рендерит lock-state, но НИ ОДНОГО UI-контрола, который SET'ит флаг; достижимо только прямым POST.
Как/когда/кем найден: share-search QA — grep disablePublicSharing по apps/client = только 2 type-def, ноль call-sites. Verifier подтвердил decisive (контрол неизбежно сослался бы на это имя поля).
3b. Кто что нашёл (finding → агент → класс → вердикт)
4. ФОЛС-ПОЗИТИВЫ — кто и насколько (ключевой раздел)
Доля считается по содержательным находкам (env/self-disconfirm не штрафуем — это корректное поведение).
Системно фолс-позитивят (требуют ужесточения гейтов):
recon — класс data-corruption-on-roundtrip. Увидел в vault имена
ntent Test.md/ent Test.mdрядом сContent Test.md, построил эффектную «итеративную mangling»-нарратив про порчу на git-roundtrip. Verifier git-archaeology'ей показал: это ЧЕТЫРЕ РАЗНЫЕ страницы с разными gitmost_id, добавленные на разных sync (269/273/277/278); rename'ы byte-identical, filename==DB-title → git-sync ВЕРНО деривит имя из тайтла, а порча — upstream на ВВОДЕ тайтла (fast-typing race, в именах буквально «FAST», тела не обрезаны). Урок: агент перепутал «увидел странные имена» с «git-sync их испортил», не проверив upstream-причину.tree-nav QA — класс outbound-sync-stall. Снял снимок: clone HEAD 11ч stale, его дерево отсутствует в vault → объявил полный outbound-stall. На деле — transient-снимок дивергенции на shared-стенде; verifier создал свежий probe-page + body-marker и в poll-reclone-цикле увидел propagation <1 минуты (sync 341). Урок: на живом многоагентном стенде нельзя делать вывод «навсегда сломано» из одного снимка — нужен active-probe «создал-подождал-переклонил». (Иронично: реальный freeze СУЩЕСТВОВАЛ, но по другой причине — wedged merge — и его поймали другие агенты; tree-nav угадал симптом, но ошибся в механизме и в перманентности.)
share-search QA — класс search-operator-leak. Выдал «поиск деградирует до всего space при FTS-операторах» на основании 'a:b'→50, 'a<->b'→50. Verifier запустил реальную buildTsQuery() в node: операторы flatten'ятся в whitespace ИДЕНТИЧНО операторо-свободному 'a b', а 'a b'/'b' тоже →50. Премиса ложна — не было контрольной группы (операторо-свободный запрос с тем же числом результатов).
dual-edit — coverage-gap (понижен, не классический FP). Честно пометил «не смог проверить convergence-broadcast, т.к. freeze блокирует все push». Verifier ОПРОВЕРГ саму премису: после
git fetch+rebaseзалендил 4+ push'а — то есть «freeze блокирует все push» спутали с transient 503 (lock) / non-fast-forward под многоагентной нагрузкой. Это не выдуманный баг, а неверная атрибуция блокера — но всё равно показывает ту же ловушку shared-стенда.Надёжные агенты (0% FP, высокая ценность): ai-chat QA (3/3, образцовый adversarial + psql+/proc-лог), git-roundtrip (2/2, git-archaeology), editor-content (2/2, reload+git-export-оракул).
Главный паттерн ложных: 3 из 4 ложных — это git-sync/sync-состояние, неверно прочитанное на shared-многоагентном стенде: transient-снимок принят за перманентный баг (#6), transient lock/non-FF — за freeze (#20), а у #1 vault-артефакт от чужих тест-страниц атрибутирован git-sync'у. Recon-data-corruption (#1) и search (#10) объединяет отсутствие контрольной группы / upstream-проверки перед обвинением.
5. Где скилл сработал / провалился
Сработало (поймал именно ПРОЦЕСС):
Чуть не упустили:
Ложные срабатывания процесса: см. §4 — все коренятся в (а) shared-многоагентном стенде, где transient git-состояние выглядит как баг, и (б) отсутствии контрольной группы перед обвинением (search, titles).
6. Предложения по улучшению скилла
Гейт «active-probe вместо снимка» для всех sync/async-находок. Запретить вердикт «навсегда сломано» из ОДНОГО снимка clone/HEAD. Обязать паттерн: создать уникальный маркер → ждать > (debounce+poll) → переклонить → grep маркера, минимум 2 итерации. Это убило бы FP #6 и #20 на корню.
Обязательная контрольная группа для relevance/leak-находок. Прежде чем заявлять «X ломает поиск», прогнать операторо-свободный эквивалент с тем же ожидаемым числом и/или запустить реальную query-builder-функцию (как сделал verifier с buildTsQuery в node). Без baseline — находка не выходит из слайса. Убрало бы FP #10.
Upstream-чек перед обвинением слоя (для data-corruption). Если артефакт виден И в UI/DB, И в git одинаково — это НЕ roundtrip-порча (источники согласованы), а upstream-ввод. Добавить в oracles.md явное правило «согласие источников оправдывает транспорт, ищи выше по потоку». Убрало бы FP #1.
Маркировать стенд как shared/multi-agent в charter и дисконтировать находки про чужие артефакты. Половина FP — это страницы/конфликты, созданные ПАРАЛЛЕЛЬНЫМИ агентами владельца (ConfAsym, DualEditTest). Charter должен требовать namespace-префикс для своих фикстур и игнорировать чужие при атрибуции.
Дедуп семейства findings. Находки #3/#6/#15/#17/#20 — это РАЗНЫЕ наблюдения ОДНОГО корня (wedged docmost→main merge → freeze). Сейчас 5 агентов независимо описали его как 5 находок разной точности. Нужен пост-execution дедуп-шаг, кластеризующий по root-cause до verifier, чтобы verifier тратился на корень, а не на 5 симптомов (и чтобы один и тот же баг не считался и подтверждённым, и ложным в разных формулировках).
Поднять git-export-оракул и /proc-log-оракул в ОБЯЗАТЕЛЬНЫЕ для любого «sync/persistence» PR. Они дали все high-баги; recon/tree-nav, кто их не довёл до active-probe, и дали FP. Зашить в charter git-sync: «три оракула (UI/API/fresh-clone) + active-probe + serverside vault git status, иначе находка неполная».
Verifier-гейт «опровергни премису блокера». dual-edit #20 списал coverage на «freeze блокирует push» — verifier показал, что блокер был transient. Добавить verifier'у обязательный шаг: если агент списывает непокрытие на инфраструктурный блокер — независимо проверить, что блокер реален и перманентен (здесь: retry/rebase/повтор).
Сохранить и поощрять честную self-disconfirm-практику. 6/22 находок агенты сами пометили env/tooling (401-storm, chromium-crash, DnD-overshoot) — это отлично и экономит verifier. Вынести в SKILL как пример: «прежде чем винить приложение — воспроизведи свою же гонку».
Дать слабым слайсам (recon, tree-nav, share-search) чек-лист «перед эскалацией наружу»: (a) есть контрольная группа? (b) сделан active-probe, не снимок? (c) источники согласованы → значит транспорт ок? (d) это не чужая фикстура? Три из четырёх FP прошли бы этот фильтр и не покинули слайс.
⚠️ Мета-фидбэк по скиллу (важно): этот прогон ЗАВИС — один verifier-агент висел ~5.5ч и блокировал весь воркфлоу (parallel ждёт всех). Спасло только stop+resume (кэш отработавших агентов). Нужен per-agent таймаут в оркестрации, иначе один залипший агент кладёт весь прогон на часы.
🤖 web-test-orchestrator (process-trace)