diff --git a/personal/plans/hermes-whale-system-prompt.md b/personal/plans/hermes-whale-system-prompt.md index 54c37e97..9162fa7c 100644 --- a/personal/plans/hermes-whale-system-prompt.md +++ b/personal/plans/hermes-whale-system-prompt.md @@ -83,22 +83,29 @@ SOUL.md из HERMES_HOME независим и не участвует в это --- -## Отсутствующие возможности (opportunity) +## Реализовано: prompt_overrides (2026-09-15) -Алекс предложил сделать стабильный слой конфигурируемым через файлы так же, как `memory_prompt.md`: -- Вынести каждый хардкод-блок (hermes help, task completion, memory guidance, session search, skills guidance и т.д.) в отдельный `.md` файл -- Добавить `prompt_overrides` секцию в config.yaml, где указывать пути к файлам -- Если файла нет — fallback на хардкод +Возможность, описанная здесь ранее как opportunity, **реализована**. Стабильный слой +теперь конфигурируемый: каждый хардкод-блок вынесен в `.md` файл, маппинг — в +`agent.prompt_overrides`. Если файла нет — fallback на хардкод. -Связанные файлы Hermes Agent: -- `agent/system_prompt.py` — сборка system prompt, управляющая логика -- `agent/prompt_builder.py` — константы и функции загрузки -- `agent/agent_init.py` — чтение конфига и проброс +Полное описание реализации: [[hermes-agent-improvements]] → секция «Вынос хардкода промптов в файлы». ---- +Кратко: +- `agent/system_prompt.py` — функция `_load_prompt_block(agent, block_name, default)`, 7 констант заменены на её вызовы +- `agent/agent_init.py` — чтение `agent.prompt_overrides` из конфига в `agent._prompt_overrides` +- `~/.hermes/hermes-whale/config.yaml` — секция `agent.prompt_overrides` (7 путей) +- `~/.hermes/hermes-whale/review/` — 7 файлов блоков + `memory_prompt.md`, `skill_prompt.md` + +Итого вместо хардкода (см. таблицу stable-слоя выше) блоки #2–#8 читаются из файлов. +Осталось в коде: `DEFAULT_AGENT_IDENTITY` (fallback), `GOOGLE_MODEL_OPERATIONAL_GUIDANCE`, +`COMPUTER_USE_GUIDANCE`, `KANBAN_GUIDANCE`, `PLATFORM_HINTS`. + +**Коммиты:** `hermes-agent` `207a1de16`, `~/.hermes` `7fd200c`. +**Осталось:** перезапуск webhook — правки вступят в силу. ## Текущие ограничения - webhook не имеет platform hint → может быть полезно добавить -- Всё хардкоженое в `prompt_builder.py` нельзя переопределить без редактирования кода +- Google-specific и kanban-блоки остаются хардкодом (неактуальны для Whale) - Нет `.hermes.md` / `AGENTS.md` в `/Users/admin/` — контекстный слой пуст diff --git a/personal/projects/personal-os/hermes-agent-improvements.md b/personal/projects/personal-os/hermes-agent-improvements.md index dccc666a..da56a2fa 100644 --- a/personal/projects/personal-os/hermes-agent-improvements.md +++ b/personal/projects/personal-os/hermes-agent-improvements.md @@ -187,8 +187,8 @@ agent: 2. ✅ Пропатчить `agent/system_prompt.py` — добавить `_load_prompt_block()` и заменить 7 констант 3. ✅ Пропатчить `agent/agent_init.py` — пробросить `prompt_overrides` из конфига 4. ✅ Обновить `config.yaml` — добавить `agent.prompt_overrides` (через копию, т.к. patch блокирован TIRITH) -5. ⏸️ Перезапустить webhook (ждёт команды) -6. ⏸️ Обновить Obsidian docs (этот шаг — выполнен) +5. ✅ Закоммитить в git (2026-09-15): `hermes-agent` `207a1de16`, `~/.hermes` `7fd200c` +6. ✅ Обновить Obsidian docs ### Проверка @@ -201,6 +201,49 @@ agent: - **Конфиг всё равно просит аппрув** — при `cp` обратно TIRITH запрашивает подтверждение (overwrite project env/config file). Это нормально, нужно подтвердить. - **SOUL.md уже существует** — в Whale он лежит в `/Users/admin/.hermes/hermes-whale/SOUL.md`. Он НЕ выносится через prompt_overrides, т.к. уже является файлом и загружается отдельно через `load_soul_md()`. - **webhook не в PLATFORM_HINTS** — webhook нет в словаре `PLATFORM_HINTS` в `prompt_builder.py`, поэтому блок platform hints пустой. Это не менялось. +- **Файлы review/*.md обязаны быть в git** — если они не закоммичены, подмены промпта теряются при любом восстановлении репо. Коммитить вместе с конфигом. + +--- + +## Git-гигиена репозитория (2026-09-15) + +При коммите состояния после prompt_overrides обнаружена утечка секретов и наведён порядок. +Полное описание структуры и правил: [[hermes-git-repo]]. + +### Утечка, которую починили + +`.env.bak` и `.env.new` (в корне и в `hermes-whale/`) содержали живые токены +(TELEGRAM_BOT_TOKEN, OPENROUTER_API_KEY, DISCORD_BOT_TOKEN, ZULIP_API_KEY, DEEPSEEK_API_KEY), +но `.gitignore` покрывал только `.env`. Один `git add .` затащил бы их в историю. + +Также не игнорировался `webhook_subscriptions.json` — там per-endpoint секреты (16 символов). + +**Fix:** расширены правила `.gitignore` (`.env` варианты, `webhook_subscriptions.json`, +`config.yaml.bak*`, `node_modules/`). Проверка — `git check-ignore -v `. + +### Коммиты (2026-09-15) + +| Репо | SHA | Сообщение | +|------|-----|-----------| +| `hermes-agent` | `207a1de16` | feat: load stable system-prompt blocks from config prompt_overrides | +| `~/.hermes` | `7fd200c` | feat: wire prompt_overrides + whale-thread-guard multi-bot routing | +| `~/.hermes` | `7893c35` | chore: sync skills — curator consolidation | +| `~/.hermes` | `bdf84da` | chore(lsp): add vue/dockerfile/pyright/yaml language servers | +| `~/.hermes` | `176ce9c` | chore: thread-scoped memory store + claude token refresh script | + +Всё локально, **не запушено** (на момент коммита: `~/.hermes` ahead 8, `hermes-agent` ahead 20). + +### Долги репозитория + +1. `hermes-whale/lsp/node_modules/` — 39M бинарников в git, `.gitignore` не действует на отслеживаемые файлы. +2. `channel_directory.json` — в `.gitignore`, но уже отслеживается. + +### Плагин whale-thread-guard в том же коммите + +Расширен с роутинга «Eagle/Whale» на **любого бота**: `@**BotName**` не Eagle → владение топиком +переходит этому боту. Добавлен regex `_MENTION_RE` для детекции меншенов, словари имён +(`WHALE_BOT_NAMES = {"Whale", "Кит"}`, `BALDA_BOT_NAMES = {"Балда", "Валера", "balda"}`). +Стейт — `~/.hermes/thread_ownership.json`. --- diff --git a/personal/tech/hermes-git-repo.md b/personal/tech/hermes-git-repo.md new file mode 100644 index 00000000..3137c241 --- /dev/null +++ b/personal/tech/hermes-git-repo.md @@ -0,0 +1,138 @@ +# Hermes — git-репозиторий конфига и агента + +**Дата:** 2026-09-15 +**Статус:** актуально + +## Структура (важно!) + +Git-репозиторий лежит **в `~/.hermes/`**, а НЕ в `~/.hermes/hermes-whale/`. +Это главный подвох: `git status` из `hermes-whale/` показывает пути вида `../config.yaml`, +`../skills/` — потому что корень на уровень выше. + +``` +~/.hermes/ ← git repo root (branch: main, origin/main) +├── .gitignore +├── .gitmodules +├── config.yaml ← глобальный конфиг +├── SOUL.md +├── channel_directory.json ← в .gitignore, но УЖЕ ЗАКОММИЧЕН (см. долги) +├── thread_ownership.json ← состояние whale-thread-guard плагина +├── webhook_subscriptions.json ← секреты! в .gitignore (не коммитить) +├── hermes-agent/ ← SUBMODULE: исходники Hermes Agent (Python) +├── hermes-whale/ ← профиль Whale (конфиг + память + промпты) +│ ├── config.yaml ← конфиг профиля Whale +│ ├── SOUL.md ← идентичность Кита +│ ├── review/ ← prompt_overrides: 7 .md блоков + memory/skill prompt +│ ├── memories/ +│ │ ├── MEMORY.md, USER.md ← глобальные +│ │ └── threads//MEMORY.md ← thread-scoped память +│ ├── cron/jobs.json +│ └── lsp/ ← ⚠️ 39M бинарников закоммичено (долг) +├── memories/ ← глобальные (не профиля) +├── plugins/whale-thread-guard/ ← роутинг ботов в Zulip +├── scripts/refresh-claude-token.sh +└── skills/ ← курируемые скиллы +``` + +## Два репозитория + +| Репо | Что | Ветка | +|------|-----|-------| +| `~/.hermes/` | Конфиг Whale, скиллы, память, плагины, пин сабмодуля | main | +| `~/.hermes/hermes-agent/` | Исходники Hermes Agent (Python) — сабмодуль | main | + +Правки Python-кода Hermes (`agent/*.py`) коммитятся **в сабмодуле**, +затем в родительском репо обновляется пин: `git add hermes-agent`. +Если пин не обновить — в родительском `git status` видно +`hermes-agent (new commits, modified content)`, а сам сабмодуль чист. + +**Как проверить, всё ли закоммичено:** +```bash +cd ~/.hermes && git status --porcelain | head +cd ~/.hermes/hermes-agent && git status --porcelain # должно быть пусто +``` + +## Правила .gitignore (обновлено 2026-09-15) + +До 2026-09-15 игнорировался только `.env` — вариант `**`.env.bak`/`.env.new` НЕ покрывался, +хотя в них лежали живые токены. Один `git add .` затащил бы их в историю. + +Добавлены секции: + +```gitignore +# Secrets +.env / .env.bak / .env.new / .env.local / .env.*.bak +**/.env / **/.env.bak / **/.env.new + +# Webhook subscriptions (содержат per-endpoint secret, 16 символов) +webhook_subscriptions.json +**/webhook_subscriptions.json + +# Config/state бэкапы (могут содержать секреты) +config.yaml.bak* / config.yaml.*.bak / config.yaml.whale-bak* +config.yaml.work / config.yaml.transitional / config.yaml.corrupt* +**/config.yaml.bak* / **/config.yaml.*.bak / **/config.yaml.whale-bak* +*.tmp + +# Node +node_modules/ / **/node_modules/ +``` + +### Проверка игнора (обязательно после правки .gitignore) + +```bash +cd ~/.hermes && git check-ignore -v .env.bak .env.new \ + hermes-whale/.env.bak hermes-whale/.env.new \ + webhook_subscriptions.json config.yaml.bak +``` + +Каждый файл должен вывести строку `.gitignore::\t`. + +**Pitfall:** `.gitignore` НЕ влияет на уже отслеживаемые файлы. +`hermes-whale/lsp/node_modules/` остаётся в репо несмотря на правило `node_modules/`. +Вывод — применять `git add` по именам файлов, а не `git add .`, иначе новое правило +может дать ложное чувство защиты. + +## Что коммитить / что не коммитить + +**Коммитим:** +- `config.yaml`, `hermes-whale/config.yaml` — конфиги +- `hermes-whale/review/*.md` — prompt_overrides (без них подмены промпта теряются!) +- `hermes-whale/memories/`, `memories/` — память +- `plugins/`, `skills/`, `scripts/` +- `hermes-agent` (пин сабмодуля) + +**Не коммитим (в .gitignore):** +- `.env*` любые варианты +- `webhook_subscriptions.json` — секреты +- `config.yaml*.bak*` — 10+ бэкапов могут содержать секреты +- `node_modules/`, `.restart_last_processed.json`, `config.yaml.zulip-router` +- `lsp/`, `review/` в корне — дубликаты `hermes-whale/lsp/` и `hermes-whale/review/` + +## Долги (не исправлено, требуют решения Alex) + +1. **`hermes-whale/lsp/node_modules/` в git** — 39M бинарников + `gopls` 41MB. + `.gitignore` не помогает (файлы уже отслеживаются). Чистка = `git rm -r --cached` + rewrite истории. +2. **`channel_directory.json` в .gitignore, но отслеживается** — правило добавлено после коммита. + В коммите `7fd200c` из него удалено 869 строк (файл в основном генерируемый). + +## Команды + +```bash +# Статус обоих репо +cd ~/.hermes && git status -sb +cd ~/.hermes/hermes-agent && git status -sb + +# Закоммитить Python-правки Hermes +cd ~/.hermes/hermes-agent && git add agent/*.py && git commit -m "..." +cd ~/.hermes && git add hermes-agent && git commit -m "chore: bump submodule" + +# Проверить, что секреты не в истории +cd ~/.hermes && git log --all --diff-filter=A --name-only --pretty=format: | grep '\.env' +``` + +## Связанное + +- [[hermes-whale-system-prompt]] — состав system prompt, prompt_overrides +- [[hermes-agent-improvements]] — вынос хардкода промптов в файлы +- [[hermes-memory-architecture]] — thread-scoped память diff --git a/work/projects/cpm-web-extension-breakage-validation.md b/work/projects/cpm-web-extension-breakage-validation.md index 18101ded..4841991a 100644 --- a/work/projects/cpm-web-extension-breakage-validation.md +++ b/work/projects/cpm-web-extension-breakage-validation.md @@ -1,166 +1,71 @@ -# CPM Web Extension Breakage — статус и план +# CPM Web Extension Breakage — статус и PR-план -> Короткая актуальная версия на 2026-09-14. Полная трассировка остаётся в [[cpm-web-extension-breakage-findings]]. +> Актуально на 2026-09-15. Полная трассировка: [[cpm-web-extension-breakage-findings]]. ## Итог -Есть две разные группы багов, их нельзя смешивать: +Есть два независимых класса проблем: -1. **All-tabs:** смерть background WebContent process может сломать CPM во всех вкладках, включая новые. Это главный кандидат для telemetry → graveyard → A/B. -2. **Per-tab:** приложение сообщает WebKit, что ещё живая вкладка закрыта. Ломается только конкретная вкладка или shared pinned tabs. Это отдельные фиксы и отдельные PR. +- **All-tabs:** после смерти background WebContent process CPM может перестать работать во всех вкладках. Нужна последовательность telemetry → production validation → bounded graveyard → A/B. +- **Per-tab:** ошибки lifecycle заставляют WebKit забыть ещё живую вкладку. Исправляются независимо от graveyard. -## Статус проблем +## Статус гипотез -| Проблема | Статус | Что известно / что делать | +| Проблема | Статус | Результат | |---|---|---| -| Смерть background WebContent process → page-less service-worker process → BackgroundContentFailedToLoad loop | **Подтверждено воспроизведением** | 100% repro через kill -9 background PID. CPM не работает во всех вкладках, reload/new tab не помогают, Fire помогает. Естественная частота и реальные production reasons пока неизвестны. Это scope graveyard experiment. | -| Закрытие окна забывает shared pinned tabs в WebKit | **Подтверждено вручную** | didCloseWindow закрывает/forget-ит shared pinned Tab, хотя он остаётся в других окнах. Нужен отдельный per-tab lifecycle PR. | -| Close Other Tabs forget-ит оставленную вкладку | **Подтверждено по коду** | removeAll(andAppend:) посылает didCloseTab и для retained tab, затем возвращает тот же объект без didOpenTab. Нужен ручной regression repro и отдельный fix PR. | -| Duplicate Tab из popup переносит оригинальный Tab вместо копии | **Подтверждено по коду** | При закрытии popup WebKit забывает вкладку, оставшуюся в main window. Нужен ручной repro и отдельный fix PR. | -| Пропуск CPM content script при startup/session restore | **Подтверждено как transient** | Только текущий document; следующая навигация восстанавливает работу. Не объясняет устойчивый all-tabs outage. | -| Однократный failed background load остаётся в m_backgroundWebView | **Механизм подтверждён, исходный вывод отвергнут** | Самовосстановление есть: второй wake-up ставит 30 s eviction, затем возможна повторная загрузка. Устойчивый outage требует повторяемой причины или постоянного traffic. | -| Natural background-process death в production (crash / memory / CPU / jetsam) | **Не проверено в поле** | Forced crash доказывает цепочку, но не частоту и не распределение termination reasons. Нужна телеметрия до фикса. | -| Event starvation / JSC hang / stale listener bookkeeping | **Не проверено, низкий приоритет** | Состояния возможны, но стабильного repro и production transition нет. Оставить как residual diagnostic buckets. | -| Native messaging stall / malformed CPM config | **Не проверено, низкий приоритет** | Возможный all-tabs симптом, но подтверждённого production path нет. | -| Critical memory pressure как конкретный триггер | **Не воспроизведено** | Ручной memory_pressure CPM не сломал. Не использовать как доказанный root cause. | -| Kill/restart Network process как триггер | **Отвергнуто на проверенной сборке** | WebKit восстановился, CPM продолжил работать. | -| removeAllUserScripts() удаляет WebExtension scripts | **Отвергнуто** | WebKit использует RemoveWebExtensions::No. | -| Feature-flag transition, scriptlet reload, обычный long-running JS страницы | **Отвергнуто как объяснение all-tabs outage** | Другой extension context или tab-local execution. | -| Safari SW registration DB fix | **Нерелевантно** | Extension SW registration не хранится как обычная persistent registration. | +| Forced death background process → persistent all-tabs CPM outage | **Подтверждено воспроизведением** | Kill background PID ломает CPM; reload и новая вкладка не помогают, Fire восстанавливает. | +| Natural crash / memory / CPU termination в production | **Не проверено** | Механизм доказан, реальная частота и связь с outage требуют M3/M4. | +| Close Other Tabs forget-ит retained tab | **Подтверждено и исправлено в M1** | Сохраняется тот же экземпляр Tab без ложного didCloseTab. | +| Duplicate Tab из popup переносит original | **Подтверждено и исправлено в M1** | В main window уходит копия, source остаётся отдельным объектом. | +| Закрытие окна forget-ит shared pinned/other-owned tabs | **Подтверждено и исправлено в M1** | Закрываются только tabs, реально принадлежащие закрываемому окну. | +| Нестабильный WebExtension base URL | **Исправлено и локально проверено в M2** | URL детерминирован по identifier и сохраняется между load/reload; production migration/storage validation остаётся rollout-задачей. | +| Startup/session-restore content-script miss | **Подтверждено как transient** | Не объясняет устойчивый all-tabs outage. | +| Failed background load навсегда застревает в m_backgroundWebView | **Исходный вывод отвергнут** | WebKit способен повторно загрузить background; устойчивый outage требует повторяемой причины/traffic. | +| Event starvation / JSC hang / native messaging stall | **Не проверено** | Оставлено в diagnostic buckets M3. | +| Critical memory pressure как конкретный trigger | **Не воспроизведено** | Не считать root cause без production correlation. | +| Network process restart как trigger | **Отвергнуто** | На проверенной сборке CPM восстановился. | +| removeAllUserScripts удаляет WebExtension scripts | **Отвергнуто** | WebKit использует RemoveWebExtensions::No. | +| Feature-flag transition / scriptlet reload / обычный page JS как all-tabs cause | **Отвергнуто** | Это другой context или tab-local execution. | -## Текущий worktree +## Worktrees -Сейчас в одной незавершённой пачке смешаны: +Все созданы от локального `origin/main` `570c2f8ef4`. Коммитов нет. Исходный `cpm-breakage-pixel` не изменялся. -- diagnostic model/recorder, pixel parameters и hooks в health monitor/manager; -- background WKNavigationDelegate proxy, process-death/hang timeline; -- graveyard treatment и два feature flags/cohorts; -- debug notifications/menu, crash simulations, большие UI tests и локальная repro HTML; -- посторонние/временные изменения (NSObject deallocation assertion выключен, Package.resolved bump, правило UI tests, accessibility refactor). - -**Состояние:** код не считать готовым к PR; в этой сессии он не собирался и тесты не запускались. Отдельный blocker: proxy уже сам менял lifetime/timing и в наблюдении предотвращал repro. В текущем коде install сохраняет associated object на original delegate, а isInstalled/uninstall ищут его на webView — это надо исправить и покрыть тестом до PR. Поэтому текущий control не является доказанно нейтральным baseline. - -## Milestones - -Два трека: - -- **Correctness:** M1 tabs и M2 stable base URL независимы друг от друга и от graveyard. -- **All-tabs:** M3 telemetry → M4 production validation → M5 graveyard → M6 A/B выполняются последовательно; M5/M6 лучше делать stacked на M3. - -### M0 — Разобрать worktree - -**Цель:** получить чистые группы изменений без изменения поведения. - -- Убрать из feature stack выключенный NSObject assertion, случайный Package.resolved bump и изменение repo rule. -- Debug menu/notifications/repro HTML оставить локально или вынести в отдельный debug-tooling PR. -- Большие AutoconsentUITests, accessibility cleanup и restart(beforeLaunch:) вынести в test-infrastructure PR; оставить только детерминированные regression tests. - -**Done:** каждый оставшийся diff однозначно относится к одному milestone; нет смешанных production/debug/test-infrastructure изменений. - -### M1 — Исправить tab lifecycle - -Один общий PR допустим только при введении общего lifecycle API; иначе лучше три небольших feature PR. - -1. **Close Other Tabs:** заменить перегруженный removeAll(andAppend:) на removeAll(except:); retained Tab не получает didCloseTab. -2. **Duplicate Tab from popup:** создавать новый Tab/tabCopy и переносить копию; один Tab/WKWebView не должен принадлежать двум коллекциям. -3. **Shared pinned tabs on window close / Merge All Windows:** didCloseWindow не должен закрывать shared pinned Tab, который остаётся видимым в другом окне. - -**Validation:** - -- Unit tests проверяют identity и точную последовательность didOpenTab/didCloseTab/didCloseWindow, а не только массив и selection. -- Close Other Tabs: retained loaded tab остаётся известен WebKit; CMP работает после последующей navigation/reload. -- Popup Duplicate: source и duplicate — разные объекты; закрытие popup не ломает CPM в main window. -- Shared pinned: закрытие второго окна и Merge All Windows не дают tab not found; pinned tab работает без открытия нового окна. -- Negative coverage: реально закрытые tabs по-прежнему получают ровно один didCloseTab. -- Diagnostic signature после фикса: tab_known_to_webkit=true, tab_controller_matches_context=true, нет content_script_send_error=tab_not_found. - -**Done:** три repro проходят; обычные open/close/move/Fire сценарии не меняют баланс lifecycle events. - -### M2 — Stable WebExtension base URL - -**Цель:** задать context.baseURL = webkit-extension:/// до WKWebExtensionController.load(context), чтобы после первой миграции WebKit больше не запускал rename origin на каждом load. - -**Validation matrix:** - -- Fresh install: сразу используется stable base URL; CPM/background/content scripts загружаются. -- Existing install с предыдущим random base URL: ровно одна migration/rename, затем stable URL сохраняется между relaunch/reload. -- Повторные cold launches: rename origin больше не вызывается; context достигает safe-to-inject раньше. -- storage.local и extension preferences переживают миграцию; отдельно проверить, что web-origin storage либо переносится, либо осознанно не используется. -- Extension update/reinstall, explicit reload и Fire: identifier/base URL соответствуют ожидаемой semantics и не сталкиваются между extension contexts. -- Session restoration: детерминированный lifecycle test подтверждает, что load завершён до addInjectedContent; небольшой manual stress run подтверждает отсутствие пропуска CPM на restored document. Не делать flaky тест с десятками случайных relaunch обязательным CI gate. - -**Telemetry/validation:** временно логировать или считать reason/duration base-URL migration только в internal build; сравнить cold-load timing до/после. Не отправлять URL или identifier в pixel. - -**Done:** one-time migration подтверждена, повторных rename нет, storage не потерян, restored CMP page работает без ручного reload. - -### M3 — CPM failure-attribution telemetry - -Самостоятельный production PR без graveyard и без recovery behavior. - -- CPMMessagingDiagnostics с короткими PII-free/bucketed параметрами. -- Diagnostics на cpmInitializationFailed и cpmMessagingStuck. -- Passive facts: context errors, background view count/alive, WebProcess/NetworkProcess state, tab known to WebKit, native handler и controller/context match. -- Ordered background_events только если observation не меняет lifetime. -- Manager hooks, macOS wiring, pixel definitions/privacy review и unit tests на bucketing/sanitization. - -**Критический design constraint:** delegate proxy не должен сам удерживать WKWebView/WebPageProxy. В текущем WIP proxy уже менял timing и скрывал repro; кроме того, associated object сохраняется на original delegate, а ищется на webView. - -**Done:** forced crash классифицируется как died_* → error_background_failed_to_load при network_process_restarted=false; tab repro классифицируется отдельно как tab_not_found. - -### M4 — Production validation milestone - -Без feature fix. Дождаться достаточного объёма одного релиза telemetry. - -Ответить данными: - -- встречаются ли реальные died_memory_limit / died_cpu_limit / died_crash; -- следует ли за ними BackgroundContentFailedToLoad/stuck signature; -- какова доля per-tab tab_not_found относительно all-tabs failures; -- есть ли другие dominant buckets, которые graveyard не исправит. - -**Go/No-Go:** переходить к graveyard rollout только если в production видна связка process death → all-tabs failure. Forced kill подтверждает механизм, но сам по себе не доказывает ценность rollout. - -### M5 — Background WebView graveyard mitigation - -Stacked на M3; implementation/failsafe PR без включённого массового эксперимента. - -- Forwarding delegate proxy для termination callback. -- Bounded hold dead WKWebView, затем гарантированный release. -- Независимый runtime kill switch, сначала выключен. -- Не более одного retained view на context; cleanup при unload/reload; защита от повторных deaths и retain cycles. -- Tests: public/private termination callback forwarding, reason mapping, immediate control release, treatment timeout, unload cleanup, repeated termination. - -**Обязательная валидация:** proxy-only control должен стабильно воспроизводить исходный kill -9 failure. Если proxy сам лечит race, control не нейтрален и реализацию нельзя использовать для A/B. - -**Done:** с одинаковым observation path control ломается, treatment восстанавливается; разница создаётся только hold duration. - -### M6 — Graveyard A/B experiment - -Отдельный stacked PR: cohorts, outcome measurement и rollout. - -- control: тот же proxy/observation path, немедленный release. -- treatment: bounded graveyard hold. -- Stable cohort assignment; kill switch не зависит от cohort. -- Denominator pixel на каждую background-process termination: cohort + bucketed reason. -- Outcome после следующей CPM navigation или ограниченного окна: healthy, background_failed_to_load, stuck, recovered. -- Primary metric: доля background_failed_to_load|stuck после termination по cohort. -- Guardrails: app/WebKit crashes, retained-view count, recovery latency, repeated termination loops. - -background_events на stuck pixel полезен для диагностики, но не является A/B denominator: успешный treatment иначе не попадёт в выборку. - -**Rollout:** internal/review → небольшой 50/50 production sample → проверить denominator и cohort balance → расширять только после достаточного числа termination events. - -**Done:** статистически и технически подтверждено снижение post-termination failures без ухудшения guardrails; после решения о rollout эксперимент удаляется или treatment превращается в обычный remotely controlled fix. - -## Предлагаемая PR-карта - -| PR | Milestone | Зависимость | +| Milestone | Branch / worktree | Статус | |---|---|---| -| Tab lifecycle API + Close Other Tabs | M1 | independent | -| Popup Duplicate Tab identity fix | M1 | возможно stacked на lifecycle API | -| Shared pinned window-close fix | M1 | возможно stacked на lifecycle API | -| Stable WebExtension base URL | M2 | independent | -| CPM attribution telemetry | M3 | independent | -| Graveyard mitigation/failsafe | M5 | stacked на telemetry | -| Graveyard A/B wiring | M6 | stacked на mitigation | -| Debug/repro tooling | M0 | отдельный optional PR или local-only | +| M0 debug/repro | `codex/cpm-debug-tooling` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-debug-tooling` | **Готово:** local-only HTTP iframe churn fixture; localhost HTTP 200. | +| M1 tab lifecycle | `codex/cpm-tab-lifecycle-fixes` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-tab-lifecycle-fixes` | **Готово:** три lifecycle fix; 4 targeted tests и macOS build проходят. | +| M2 stable base URL | `codex/cpm-stable-base-url` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-stable-base-url` | **Готово:** 11/11 tests; iOS/macOS builds проходят. | +| M3 attribution telemetry | `codex/cpm-failure-telemetry` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-failure-telemetry` | **Готово к PR:** passive PII-free facts, forwarding proxy, runtime kill switch; 23/23 tests; schemas iOS/macOS валидны. | +| M4 production validation | `codex/cpm-production-validation` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-production-validation` | **Ожидает production data:** runbook готов; начинать после одного telemetry release. | +| M5 graveyard mitigation | `codex/cpm-graveyard-mitigation` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-graveyard-mitigation` | **Готово к PR, выключено по умолчанию:** максимум один WKWebView, bounded 3 s hold, unload/reload cleanup, runtime disable; 18/18 tests; iOS/macOS builds проходят. | +| M6 graveyard A/B | `codex/cpm-graveyard-ab` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-graveyard-ab` | **Готово к PR:** control/treatment, stable cohort, denominator/outcome pixels, bounded pending queue; 30/30 tests; iOS/macOS builds и pixel validators проходят. | + +Во всех семи worktree проходит `git diff --check`; случайный `Package.resolved` churn удалён. + +## PR topology + +Независимые PR от `origin/main`: + +1. M0 — optional/local tooling. +2. M1 — tab lifecycle fixes. +3. M2 — stable base URL. +4. M3 — production telemetry. + +Последовательный feature stack: + +``` +M3 telemetry +├── M4 production validation gate +└── M5 graveyard mitigation + └── M6 A/B experiment +``` + +Поскольку коммитов пока нет, refs зависимых branches указывают на тот же base SHA, а зависимость представлена накопленным dirty diff. При оформлении: commit M3 → перенести/перебазировать M5 на M3 → M6 на M5. M4 фиксирует Go/No-Go после production данных. + +## Rollout gates + +- M3: privacy review, schema review, затем один production release. +- M4: отдельно по iOS/macOS проверить termination reason → initialization failure/stuck и долю per-tab failures. +- M5: включать только если M4 подтверждает process-death hypothesis; kill switch и graveyard по умолчанию выключены. +- M6: internal/review → небольшой 50/50 sample → проверить cohort balance, denominator/outcome coverage и crash/retention guardrails.