diff --git a/work/projects/cpm-episode-telemetry-phase2-plan-2026-10-20.md b/work/projects/cpm-episode-telemetry-phase2-plan-2026-10-20.md index 2044acf5..f46974a7 100644 --- a/work/projects/cpm-episode-telemetry-phase2-plan-2026-10-20.md +++ b/work/projects/cpm-episode-telemetry-phase2-plan-2026-10-20.md @@ -9,9 +9,27 @@ tags: --- # CPM episode telemetry — фаза 2 -Статус: **план, не реализовано.** Фаза 1 — PR #7376 (`web_extension_cpm_episode`, `was_stuck` = любой поздний не-restoration таймаут). Состав отчёта фазы 1: [[cpm-pr0-episode-telemetry-summary-2026-10-16]]. Общий контекст: [[cpm-status-2026-10-15]]. +Статус (21 октября 2026): **реализовано и смержено в оба релиза** (см. «Что ушло в релиз»). Фаза 1 — PR #7376 (`web_extension_cpm_episode`). Состав отчёта фазы 1: [[cpm-pr0-episode-telemetry-summary-2026-10-16]]. Общий контекст: [[cpm-status-2026-10-15]]. -Работа ведётся отдельной веткой поверх #7376 (`alex/cpm-episode-background-pause`, стекнутый PR). Obsidian MCP не подключался, файл записан напрямую. +Паузы по фону/сну и новый смысл `was_stuck` вошли в сам #7376 (коммит `88154a4c4a`), а не в отдельную ветку, как планировалось 20 октября. Obsidian MCP не подключался, файл записан напрямую. + +## Что ушло в релиз + +Проверено 21 октября 2026 по tip релизных веток: все 8 PR смержены (squash), результат совпадает с запушенными ветками по CPM-путям, CPM-файлы пакета идентичны в двух релизах (отличаются только файлы с контентом релиза: remote-disable-enforcer и т. п.), тесты пакета зелёные (iOS 934, macOS 965), `validate-pixel-defs` для iOS и macOS = 0. + +| Что | iOS `release/ios/7.243.0` | macOS `release/macos/1.213.0` | +|---|---|---| +| Эпизодный пиксель, паузы, `was_stuck` (2+ вкладки) | #7376 `5dca645528` | #7484 `bd68e42a15` | +| `tabs_affected`, `repeat_failures`, `interrupted`, токен `interruption` в `bg_events` | #7520 `507567edaf` | #7521 `8e6b98ae3b` | +| Network process recovery (флаг `cpmNetworkProcessRecovery`) | #7459 `77d7589b79` | #7531 `5512905ae2` | +| P1/P2/P4: старт и пробуждение фона (7379) | #7379 `543b073685` | #7536 `12ebc16fcd` | + +Флаги, по умолчанию: ON — `cpmNetworkProcessRecovery`, `cpmBackgroundLoadRecovery`, `cpmMessagingHangRecovery`, `cpmBackgroundDelegateProxy`, `cpmDiagnosticsRecorder`; OFF — `cpmLateBackgroundDocumentRecovery`, `cpmBackgroundGraveyardTreatment`. При отсутствии записи в remote config считаются значения по умолчанию, то есть ON-флаги работают сразу, выключаются только записью `disabled`. + +**Отличия от плана ниже:** +- Post-reload grace 8 с (пункт в «Таймауты») **не реализован**: после перезагрузки расширения первый замер по-прежнему 4 с. 8 с применяется только к ожиданиям, прерванным паузой. +- «Новых параметров нет» относится к фазе A. В релиз ушли ещё три параметра (`tabs_affected`: 1/2/3+, `repeat_failures`: 0/1/2+, `interrupted`) через #7520/#7521 с отдельным privacy-триажем. Читать данные надо уже с ними (см. «Как читать данные»). +- `interrupted` = первое сработавшее ожидание (замер, открывший эпизод) было перезапущено паузой, включая ожидание загрузки расширения (30 с). Приостановка позже внутри эпизода флаг не ставит. ## Зачем @@ -90,6 +108,46 @@ Outcome `no_response_before_deadline` is never proof of permanent failure. `relo - P2 retry идёт под общим флагом `cpmMessagingHangRecovery` (по умолчанию ON), своего kill switch нет. Повторный error 6 через ~30 с попадает в cooldown 60 с. - Доказательства fix: P1/P4 — red/green на harness; P2 — частично (последний полный ON-прогон 8 PASS / 1 P2 FAIL), iOS-часть (основная доля error 6 в stuck) тестами не покрыта. +### #7379 × #7459: смысловые пересечения (в релиз ушли как есть) и что смотреть в данных + +Обе фичи в релизе, оба включены по умолчанию, текстово слиты (аудит 20 октября, слияния 21 октября), но **смысловые пересечения не устранены** и на реальном WebKit вместе не проверялись (тесты с заглушками). Это сознательное решение: правка ниже узкая, меняет поведение в уже одобренных PR и без данных не оправдана. + +Пересечения: +1. Рестарт #7459 (`_killWebContentProcessAndResetState`, без события жизненного цикла) невидим для записи о старте #7379. Если у менеджера в этот момент висит старт, он остаётся привязан к убитому view; следующая навигация получает старый старт и новый `loadBackgroundContent` не вызывается, пока не сработает колбэк убитого view. +2. Ошибка 6 от самого убийства может запустить повтор загрузки #7379 и потратить его бюджет (не чаще раза в 60 с); от этого повтора может прийти «ложный» `background_load_retry`. +3. Флаг «идёт восстановление» у #7379 защищает от гонки только с перезагрузкой по зависанию, не с рестартом #7459; проверка «приложение в фоне» есть у #7379, нет у #7459 (его 5-секундный опрос может сработать в фоне). +4. Окна #7379 (30 с) на реальных часах и о паузах монитора не знают (после сна истекают сами). +5. Три независимых вызова `loadBackgroundContent`: наблюдатель готовности (#7376), стартовый путь и навигационный гейт (#7379). + +Что смотреть в данных (общего идентификатора у пикселей нет, только по дню и платформе): +- `web_extension_cpm_network_process_recovery_applied`: `_count` сильно больше `_daily` значит цикл рестартов. +- `web_extension_cpm_startup_response_missing` в дни с большим `recovery_applied`; рост указывает на пересечение 1 или 3. +- Эпизоды: доля `no_response_before_deadline` и `bg_events` с `died_client` после рестарта (неудачное восстановление); `reload_state` как запасная перезагрузка по зависанию. +- Success-пиксели #7379 (`outcome=background_ready`) вместе с `recovery_applied`: резкий рост повторов после рестартов указывает на пункт 2. + +**Отложенная правка** (комментарий 21 октября 2026): подписать рестарт #7459 на сброс состояния #7379 только для перезапущенного контекста. Лежит **незакоммиченной** в worktree `alex/cpm-fix-init-loss-production` (ветка `alex/cpm-fix-init-loss`) и `alex/cpm-fix-init-loss-macos` (ветка `alex/cpm-fix-init-loss-macos-release`); обе ветки уже смержены в релизы, патч рассчитан на текущие файлы. Оценка: польза узкая (работает, только если на момент рестарта старт ещё не завершён; обычно он уже завершён), побочные эффекты (ждущие навигации отпускаются раньше 5 с, теряется замер первой загрузки и install/upgrade для перезапущенной попытки). Ревью дважды (независимо): безопасно, крашей и рекурсии нет; нужно добавить тест менеджера (открыть `cpmNetworkProcessRecovery` internal) и записать ограничения в описание PR. Отдельно отвергнута первая версия, которая сбрасывала состояние на каждый возврат приложения: на iOS это каждое переключение приложений и сброс легитимного старта. + +Ключевая часть патча (плюс два теста: «хук вызывается один раз с идентификатором» и «не вызывается при неудачном рестарте»): + +```diff +--- CPMNetworkProcessRecovery.swift ++ /// Called with the context identifier after a restart, so other recoveries can drop state tied to the replaced background. ++ var onBackgroundRestarted: (@MainActor (String) -> Void)? + ... + removeWatch(identifier: identifier, matching: watch) + recovered = true ++ onBackgroundRestarted?(identifier) + pixelFiring.fire(.cpmNetworkProcessRecoveryApplied) +--- WebExtensionManager.swift (после настройки healthMonitor) ++ cpmNetworkProcessRecovery.onBackgroundRestarted = { [weak self] identifier in ++ guard let self, self.embeddedBackgroundStartupContext?.uniqueIdentifier == identifier else { return } ++ self.backgroundRecovery?.invalidate() ++ self.clearEmbeddedBackgroundStartup(identifier: identifier) ++ } +``` + +Не покрыто и не сделано: подавление ошибки 6 от самого убийства (пересечение 2), учёт события рестарта во флаге «идёт восстановление», тест связки менеджера. Если данные покажут пересечения 1–3, начинать с этого патча. + ## Ограничения и риски - Если iOS убьёт приложение в фоне, незавершённый эпизод теряется (принято в фазе 1). - Пропущенное событие паузы ломает учёт; начальное состояние (запуск в фоне) брать из системы, не считать «активным».