Files
obsidian-vault/work/projects/cpm-web-extension-public-attribution-2026-10-05.md
T

129 lines
37 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CPM Web Extension: attribution и сравнение релизов — 5 октября 2026
**Статус:** первый завершённый UTC-день с данными версий iOS 7.240.0 и macOS 1.210.0. Главный сигнал — `initialization.failed.after.*.daily`; `messaging.stuck.*.daily` проверен отдельно как более поздний, менее точный сигнал. Выгрузка сделана 6 октября из Grafana ClickHouse `metrics.pixels`, интервал `[2026-10-05 00:00, 2026-10-06 00:00) UTC`, `agent` и `appVersion` зафиксированы. **Основные таблицы ниже включают только события после публикации release tag каждой платформы**; отдельный раздел сохраняет всё попавшее в выгрузку до публикации. [Dashboard](https://grafana.duckduckgo.com/d/dfon7whlilnggb/cpm-weird-error-investigation?orgId=1).
**Вывод:** после публикации macOS tag есть **77** первых init failures и **35** stuck с несовпадающим controller вкладки и отсутствующими extension scripts; **до публикации таких строк нет**. iOS session restoration по-прежнему даёт 952 первых сбоя, из них 604 со совпадающим controller, но без scripts и background view; у macOS после публикации таких session-restoration сбоев 68, из них 12 с той же сигнатурой. **Крупнейшая группа с «исправным native-снимком» не доказывает нормальную загрузку или потерю `initResp`: health monitor измеряет отдельный вызов background → native `refreshCpmDashboardState`.** После reload этот вызов вновь наблюдается гораздо чаще, но восстановление фактического `initResp` из пикселей не следует. Эффект исправлений tab lifecycle в совокупном тренде не выделяется.
## 1. Охват и контрольные суммы
| После release tag: независимые имена `.daily` пикселей | iOS 7.240 | macOS 1.210 |
|---|---:|---:|
| Первое `initialization.failed.after.*` | **6 624** | **1 377** |
| Позднее `messaging.stuck.*` | 1 440 | 487 |
| Первый неудачный post-reload CPM probe | 670 | 232 |
| CPM ответ после reload, оба `recovery_from` | 963 = 916 stuck + 47 init | 362 = 354 stuck + 8 init |
| CPM ответ без reload, `initialization_failed` | 1 555 | 346 |
| **Сумма записей CPM daily по именам пикселей** | **11 252** | **2 804** |
Контроль после release tag: `6 624 + 1 377 = 8 001` первичных строк; `1 440 + 487 = 1 927` вторичных строк; `11 252 + 2 804 = 14 056` CPM daily записей по именам пикселей. До tag есть ещё 28 init, 2 stuck и 39 CPM daily записей, все на macOS; полные файлы поэтому содержат `8 029` init, `1 929` stuck и `14 095` CPM daily. Разделение по времени и pixel name воспроизводит `release_cutover_daily.tsv`, полный день сходится с `inventory.tsv` и `release_cpm_daily_trends.tsv`. Включать `debug.web.extension.reload.failed.daily` в сумму CPM нельзя: это отдельное семейство операций Web Extensions. Внутри CPM `initialization.failed.after.extension.reload.daily` и `messaging.extension.reload.failed.daily` обычно посылаются вместе на одной неудачной post-reload проверке: их суммы не обозначают разные эпизоды. `.daily` ограничен одним событием **на имя пикселя** за UTC-день на установку; разные имена могут относиться к одному пользователю и эпизоду. Переход версии внутри дня тоже меняет наблюдаемые суммы. Здесь нет числа уникальных пользователей, DAU той же версии и идентификатора для связывания событий.
Публикация release tag состоялась 5 октября в 15:18 UTC для [iOS 7.240](https://github.com/duckduckgo/apple-browsers/releases/tag/7.240.0%2Bios) и 09:46 UTC для [macOS 1.210](https://github.com/duckduckgo/apple-browsers/releases/tag/1.210.0%2Bmacos). Время после tag — доступная граница, **не доказательство public-канала у каждого отправителя**: пиксель не содержит channel/rollout cohort, и internal testing могло продолжаться после публикации.
### До публикации release tag: отдельный срез
В пределах выгрузки за 5 октября до публикации iOS tag нет CPM daily событий версии 7.240. До публикации macOS tag у версии 1.210 есть **28 init failure.daily, 2 stuck.daily, 2 post-reload failed.daily, 7 recovered.without.reload.daily, 0 recovered.after.reload.daily** — всего **39 CPM daily**. В этих 28 init: 12 native snapshot без видимой ошибки, 7 background state, 5 только context error history, 3 tab attachment/injection (2 без scripts/zero views и 1 tab-not-in-context), 1 с неполными tab facts. В двух stuck нет отсутствующих scripts или controller mismatch. `debug.web.extension.reload.failed.daily` до tag нет. Более ранние internal-testing данные 28 сентября – 4 октября сохранены в [[cpm-web-extension-breakage-validation]] как архив и **не смешиваются** с таблицами после публикации.
## 2. Первичный сигнал: группы и события рядом с `init failure.daily`
Каждая строка попадает ровно в одну диагностическую группу по приоритету: tab scripts/controller → явные признаки background → история ошибок → неполные поля → видимо исправный native snapshot. Это **классификация снимка**, а не доказательство первопричины. Полные пересекающиеся признаки сохранены отдельно.
| После release tag: диагностическая группа | iOS init, из 6 624 | macOS init, из 1 377 | iOS stuck, из 1 440 | macOS stuck, из 487 |
|---|---:|---:|---:|---:|
| Native snapshot без видимой ошибки | 3 717 (56,1%) | 911 (66,2%) | 789 (54,8%) | 335 (68,8%) |
| Tab attachment / injection | 931 (14,1%) | 116 (8,4%) | 53 (3,7%) | 38 (7,8%) |
| Background state | 768 (11,6%) | 158 (11,5%) | 457 (31,7%) | 35 (7,2%) |
| Только история ошибок context | 343 (5,2%) | 158 (11,5%) | 87 (6,0%) | 79 (16,2%) |
| Недостаточно tab facts | 865 (13,1%) | 34 (2,5%) | 54 (3,8%) | 0 |
Контроль групп после tag: iOS init `3717+931+768+343+865=6624`; macOS init `911+116+158+158+34=1377`; iOS stuck `789+53+457+87+54=1440`; macOS stuck `335+38+35+79=487`. Контроль причин первого сбоя: iOS `other 4803 + session.restoration 952 + extension.reload 670 + tab.crash 199 = 6624`; macOS `1050+68+232+27=1377`. Это `reason` навигации/эпизода, **не причина поломки**.
**Окружающие события по главным цепочкам:**
1. **iOS session restoration, 952.** У 604 (63,4%) `tab_controller_match=true`, scripts отсутствуют, `bg_view_creations=0`; у 607 хвост `bg_events` содержит только `load`, ещё у 283 — `load → view → proxy_on`. 754 первых сбоя попали в bucket первых 30 секунд запуска. Код ожидает начальный `loadAndSync` и загрузку background content перед первой HTTP(S)-навигацией restored tab, но общий дедлайн ожидания — 5 секунд; неоконченная загрузка и более позднее внедрение script всё ещё способны дать временный timeout. 260 снимков уже выглядят исправно и требуют timings сообщения, а не ещё одного снимка состояния.
2. **iOS `other`, 4 803.** 2 921 выглядят исправно; 831 имеют неполные tab facts; 334 содержат background load error code 6; 239 — scripts отсутствуют при нулевом числе background views. Частые хвосты `load → view → proxy_on` (1 450) и `load → view → proxy_on → dealloc → view → proxy_on` (934). `dealloc` фиксирует освобождение background view, не падение процесса; только 85 первичных iOS строк имеют явный `died_*` в доступном хвосте.
3. **Повторная проверка после extension reload, iOS 670 / macOS 232.** Это именно первый CPM init timeout после reload, вместе с `messaging.extension.reload.failed.daily`. Типичный хвост iOS `load → base_url_changed → view → proxy_on` (483); macOS `load → base_url_changed → view → proxy_on → dealloc → view → proxy_on` (87). `base_url_changed` здесь совпадает с reload и сам по себе не доказывает ошибку URL. Native snapshot уже выглядит исправно у 470 iOS / 125 macOS; причина отсутствия ответа там не локализована.
4. **macOS `other`, 1 050.** 713 выглядят исправно, 132 не имеют живого background view, 110 имеют только историческую context error, 47 — неверный controller без scripts. За все причины init неверный controller без scripts есть у **77/1 377 после tag и 0/28 до tag**; в stuck — **35/487 после tag и 0/2 до tag**. Init-сигнатура встречается с 10:34 до 23:21 UTC, а не только сразу после публикации в 09:46. Из 77 после tag 45 имеют `app_version_change=updated`. App update сам по себе не сохраняет старый `WKWebView`; эти строки не доказывают сценарий «вкладка осталась у старого контроллера после обновления». Требуется событие смены manager/controller при живой вкладке.
5. **iOS `tab.crash`, 199.** 47 имеют code 6, 37 — явную смерть background процесса, 35 — scripts отсутствуют и background view не создан. Название reason означает навигацию после crash вкладки, а не установленную смерть CPM worker.
Вторичный stuck сдвигает iOS-картину: background state 768/6 624 (11,6%) на первом сбое против 457/1 440 (31,7%) на stuck; no-scripts/zero-view 917/6 624 против 52/1 440. Это согласуется с временными startup misses и более устойчивой background-проблемой, **но это не переходная вероятность**: имена `.daily` считаются независимо. У iOS code 6 находится в 364 stuck снимках (в 363 `dealloc` раньше первой ошибки); WebKit регистрирует code 6 как `BackgroundContentFailedToLoad` после неудачной загрузки service worker. Причина отказа регистрации из пикселя неизвестна. Хвост `bg_events` ограничен 12 событиями и может обрезать старые события.
### Проверка по коду: что означает «исправный native-снимок»
1. **Пиксель измеряет отсутствие отдельного native-вызова.** После `navigationDidFinish` при наличии context (или после lifecycle `loaded`) monitor ждёт до 4 секунд `dashboardResponse`, который поступает только из `AutoconsentWebExtensionMessageHandler.handleRefreshCpmDashboardState` при вызове `refreshCpmDashboardState`. Если на `navigationDidFinish` extension отсутствует, действует отдельное 30-секундное ожидание с отменой измерения. По истечении 4-секундного таймера `reportFailure` собирает снимок **на тот момент**. `isAutoconsentExtensionLoaded` проверяет наличие embedded context в controller, не готовность CPM JS.
2. **`context_loaded=true` не означает завершённый startup.** В просмотренном `WebKit_sparse` `isLoaded()` означает наличие `m_extensionController`; он назначается до асинхронного `moveLocalStorageIfNeeded`, создания background view, регистрации scripts и `addInjectedContent()`. `bg_view_alive=true` означает живой `WKWebView`, а `bg_process_responsive=true` — что WebKit не признал его зависшим; это не проверка выполненного service worker listener. `tab_has_ext_scripts=true` проверяет список зарегистрированных `WKUserScript`, но не их выполнение в текущем документе. `handler_registered=true` проверяет запись в native router, но не факт обработки сообщения.
3. **`initResp` и наблюдаемый monitor сигнал расходятся в release bundle.** Content script отправляет `init` в background. В успешной ветке background делает `updateCpmDashboardState(...).then(refreshDashboardState(...))` **без await**, затем отдельно вызывает `chrome.tabs.sendMessage(..., {type: "initResp"})`. В ветках `config_unavailable`, `settings_missing`, `setting_disabled`, `site_disabled` он отправляет dashboard refresh и возвращается **без `initResp`**. Значит, `init failure.daily` возможен даже после отправки `initResp`, если dashboard refresh задержался или не сопоставился с документом; обратное тоже возможно: успешный health-сигнал без `initResp`.
4. **Есть конкретные места задержки и ложной атрибуции.** Background перед успешной веткой ждёт remote config и native setting/site checks; native request timeout в bundled JS — 20 секунд против 4-секундного окна monitor. Обновление dashboard state проходит через последовательную async-очередь и session storage. Monitor сопоставляет URL refresh с текущим документом по scheme/host/port/path/**query**; несоответствие или опоздание оставит timeout даже при работающем JS. Сколько случаев пришло из каждого участка, текущие пиксели не показывают.
Эта проверка проведена именно на bundle из release tags `1.210.0+macos` и `7.240.0+ios` (zip-файлы совпадают); текущий `main` уже содержит другой bundle. Приоритет следующей telemetry — отдельные времена `content init sent`, `background init received`, `initResp sent/received`, `dashboard refresh sent/received` и результат сопоставления URL.
## 3. Session restoration: ушли ли временные сбои?
**Нет.** После release tag есть 952 iOS и 68 macOS первых `session.restoration` failures. Соответствующий поздний `stuck.session.restoration.daily` встречается 118 / 8 раз, но без связи событий нельзя сказать, какие именно первые сбои восстановились. `recovered.without.extension.reload.daily` (1 555 iOS / 346 macOS) не содержит `reason`, поэтому доля временных session-restoration эпизодов также неизвестна.
Сравнение по имени пикселя на одинаковых пятидневных окнах после предыдущих release tags: iOS 7.238 (23–27 сентября) — 3 372 386 session restoration из 5 821 858 init daily (57,9%); iOS 7.239 (30 сентября – 4 октября) — 3 014 131 из 5 390 550 (55,9%). У iOS 7.240 за **первый неполный день** — 952 из 6 624 (14,4%). Это сильное изменение состава сигналов, совместимое с readiness gate, вошедшим в 7.240, но не оценка снижения риска на пользователя: новая версия имеет иной ранний cohort и нет DAU. macOS 1.208 (23–27 сентября) — 4 531/76 336 (5,94%); 1.209 (30 сентября – 4 октября) — 7 425/114 075 (6,51%); 1.210 после публикации — 68/1 377 (4,94%). На macOS явного снижения по этому proxy пока нет. Gate входит в macOS 1.210 / iOS 7.240 и по умолчанию включён, но фактическое состояние remote flag у отправителей не передано.
## 4. Исправления tab lifecycle: виден ли эффект?
[M1 tab identity fix](https://github.com/duckduckgo/apple-browsers/pull/6827) вошёл в macOS 1.209, а в 1.208 его нет; PR менял только macOS. Старые версии не посылали новые поля controller/scripts, поэтому сравним лишь общий proxy `stuck.other.daily / initialization.failed.after.other.daily`, **не частоту tab bugs**. В сопоставимых пятидневных окнах он равен 27 493/69 565 = **39,52%** на macOS 1.208 и 40 030/103 135 = **38,81%** на 1.209. Изменение на 0,71 процентного пункта мало и может отражать состав cohort/нагрузки; это не измеренный эффект фикса. Новый 1.210 после публикации даёт 418/1 050 = **39,81%**. Следовательно, по агрегатному тренду исчезновение tab breakage не подтверждено и не опровергнуто; именно относящиеся к нему события старых версий нельзя выделить без attribution.
## 5. After-stuck recovery mitigation: работает ли?
**Узкий функциональный сигнал есть.** [M4](https://github.com/duckduckgo/apple-browsers/commit/41656f8d17) входит в macOS 1.210 / iOS 7.240 и при подтверждённом hang пытается reload embedded extension (remote flag по умолчанию enabled). `recovered.after.extension.reload.daily` означает, что после reload текущего поколения снова поступил и был сопоставлен **native dashboard refresh**; получение `initResp` content script этот пиксель не проверяет. После публикации таких событий от `messaging_stuck` — **916 iOS / 354 macOS**. Все `recovered.after` вместе дают 963/1 440 = **66,9%** от числа stuck daily на iOS и 362/487 = **74,3%** на macOS, тогда как в предшествующих пятидневных окнах число пикселей этого имени относительно stuck daily составляло **3,23%** (iOS 7.239) и **2,70%** (macOS 1.209). Это большой скачок, согласующийся с автоматическим reload. Эти отношения **не success rate**: имена пикселей независимо throttled, эпизоды не соединены, одни установки могли дать несколько разных сигналов.
**Ограничение эффекта:** после публикации первый post-reload CPM probe всё ещё завершился timeout 670 раз на iOS и 232 на macOS, поэтому reload не гарантирует немедленного восстановления даже измеряемого native-вызова. Это не сбой самого API reload. Отдельный `debug.web.extension.reload.failed.daily` после публикации содержит один macOS error для `embedded / cpm_messaging_hung / load` и 223 iOS errors для **другого** `adBlocking / scriptlet_update / unload` extension; до публикации этих operation errors нет. На текущих данных M4 восстанавливает измеряемый background → native путь у части установок; нельзя вычислить долю stuck-эпизодов с полноценным `initResp` и CPM-функцией, которые остались невосстановленными, или изменение общей частоты init failures.
## 6. Другие CPM / Web Extension пиксели: что реально видно в системе
Сверка сделана по тем же версиям, полному UTC-дню 5 октября и с разделением по времени release tag. Полный наблюдаемый перечень `autoconsent` и `web.extension` с `pixel_id`, именем, platform suffix, частотой и числом записей — `related_pixels.tsv` (329 сгруппированных строк, включая отдельный pre-tag срез). `autoconsent_sources.tsv` делит каждый daily-пиксель по `fromExtension=1`, `0` и отсутствующему параметру; `autoconsent_summary.tsv` делает то же для standard summary и раскрывает счётчики событий внутри него. В таблице ниже только **после release tag**; записи до tag остаются в TSV отдельно.
| Сигнал после tag | iOS 7.240 | macOS 1.210 | Что он проверяет |
|---|---:|---:|---|
| `autoconsent_init.daily`, только `fromExtension=1` | **56 906** | **2 825** | Background Web Extension получил `init`, дошёл до разрешённой ветки CPM и отправил native pixel. Это происходит до отдельной попытки `chrome.tabs.sendMessage(initResp)` и не подтверждает доставку ответа. |
| `autoconsent_summary`, только `fromExtension=1` | **324 549** standard-отчётов; **1 760 177** накопленных `init` | **54 421** отчёт; **213 878** `init` | Фоновый скрипт накопил события и позже отправил сводку через native. Это наблюдение многих успешных background→native проходов, но не denominator для `init failure.daily`: другое окно, другой тип частоты, эпизоды не соединены. |
| `web_extension_loaded.daily` | 61 902 | 2 870 | Manager загрузил хотя бы одно extension; имя не указывает, что это CPM, и ничего не говорит о конкретной вкладке. |
| `web_extension_state_checked.daily` | 62 004 | 2 805 | Lifecycle coordinator проверил набор ожидаемых/загруженных extension в manager. |
| `web_extension_embedded_not_loaded.daily` | **3 389** | **1** | Во время хотя бы одной проверки embedded extension ожидался, но отсутствовал в manager. Не проверяет controller/scripts конкретной вкладки. |
| `web_extension_load_error.daily` | 4 | 4 | Ошибка общей загрузки extension. Это отдельный сигнал от CPM timeout; по одному имени тип extension не известен. |
**Почему `autoconsent_init` нужно делить по источнику.** Одинаковое имя пикселя используют Web Extension и старый Autoconsent user script. Всего `init.daily` после tag пришло 60 720 на iOS и 3 351 на macOS; из них `fromExtension=1` — 56 906/2 825, `fromExtension=0` — 1 100/1, без параметра — 2 714/525. В release-коде user script ставит `0` только при доступном Web Extensions API, но недоступном autoconsent extension; отсутствие параметра также не доказывает работу extension. `.daily` ограничен по общему имени, поэтому нельзя считать этот split долей всех init-вызовов. Standard summary не имеет такого daily throttle: extension-источник дал 324 549/54 421 сводок с `init` счётчиками, но сводка запускается двухминутным alarm и может прийти существенно позже навигации.
**Функциональные CPM-события тоже есть:** с `fromExtension=1` после tag получены `autoconsent_popup-found.daily` 31 363 iOS / 1 918 macOS и `autoconsent_done.daily` 23 050 / 1 578; `disabled-for-site.daily` 4 763 / 694. Они показывают, что Web Extension действительно обнаруживает и обрабатывает popup на части навигаций. Ошибки `error_optout.daily` 513/107 и `error_reload-loop.daily` 280/49 относятся к обработке popup, а не к доставке `initResp`. `self-test-fail.daily` 153/97 **не имеет `fromExtension=1`** в этих данных и потому не служит сигналом ошибки extension. Все эти имена общие с legacy user script, поэтому исходный `fromExtension` обязателен.
**Lifecycle и соседние семейства:** `web_extension_embedded_installed.daily` 51 958/1 756 и `embedded_upgraded.daily` 804/741 отражают операции установки, а не исполнение content script. Есть отдельные пиксели install/uninstall, ad-blocking, dark-reader, scriptlet fetch/validation/install и `web_extension_ad_blocking_not_loaded`; полный набор реально поступивших имён и чисел в `related_pixels.tsv`. Они полезны для общей работоспособности Web Extensions, но не локализуют CPM `init`-timeout. Все debug CPM health-пиксели и actual `web_extension_reload_failed` разобраны выше в разделах 1–5.
**Вывод для крупнейшей группы init failure:** в совокупности данные подтверждают, что Web Extension широко запускается и обрабатывает CPM на этих билдах. Они **не подтверждают**, что в 3 717 iOS / 911 macOS строках с исправным native-снимком background получил именно тот `init`, отправил `initResp` и content script его принял. Нет event/pixel для `content init sent`, `background init received` с общим navigation ID, `initResp sent/received`, начала/результата `refreshCpmDashboardState` или исхода сопоставления URL. Сопоставить `autoconsent_init`/`summary` с конкретным `initialization.failed` по текущей анонимной телеметрии нельзя. На macOS лишь 1 общий `embedded_not_loaded.daily` при 77 init failures с controller mismatch без scripts: это согласуется с тем, что глобальный manager может быть исправен, пока отдельная вкладка прикреплена неверно.
Код release tags: `WebExtensionPixelFiring.swift` (семейства и частоты CPM); `WebExtensionManager.swift` (`.loaded` после общего load); `WebExtensionLifecycleCoordinator.swift` (`stateChecked`/`embeddedNotLoaded`); платформенные `AutoconsentMessageHandlerDelegate` (пересылка Web Extension пикселей); `AutoconsentUserScript.swift` (то же имя с `fromExtension=0` или без параметра); release bundle background `handleAutoConsentMessage`, `firePixel`, `sendSummaryPixel` (`init`, `initResp` и двухминутная сводка).
## 7. Общие `bg_events` у init failure и последующего stuck
**Кодовая связь сильнее совпадения строк.** Внутри `CPMMessagingHealthMonitor.reportFailure` первый timeout создаёт `Episode`, а `messaging.stuck` отправляется только после ещё одного timeout проверки, начатой позже первого failure (для confirmation — не session-restoration). Episode живёт не более пяти минут. Имя stuck наследует `failureReason` **первого** timeout; диагностика `stuck` снимается с вкладки **второго** timeout. `CPMMessagingDiagnosticsRecorder` сохраняет общую историю background context между этими снимками и при каждом берёт последние 12 событий; первый failure не очищает её. Поэтому `stuck.bg_events` действительно может содержать события, случившиеся до первого init failure. Новый context или вытеснение старых событий обрывает это наблюдение.
**Проверка на выгрузке после release tag.** Для каждого stuck искался более ранний init failure той же платформы и с тем же `reason` в окне пяти минут. Требовалось точное совпадение порядка токенов `bg_events` без возрастных суффиксов и совместимость всех возрастных buckets после прибавления разницы timestamps (допуск ±30 с). Для сильного сигнала оставлены цепочки длиной **не менее шести** событий. Контроль: timestamps stuck сдвинуты на +10 и −10 минут внутри post-tag интервала; все прочие правила те же. Это тест **обогащения совпадениями**, не восстановление анонимного reporter ID. Время пикселя может отличаться от времени снимка из-за доставки.
| После tag | Stuck daily | С цепочкой ≥6 | Совпало с более ранним init | Контроль +10 / −10 мин | Ровно один кандидат в выгрузке |
|---|---:|---:|---:|---:|---:|
| iOS 7.240 | 1 440 | 1 025 | **693** | 255 / 270 | 467 |
| macOS 1.210 | 487 | 368 | **168** | 29 / 19 | 148 |
Иными словами, длинные цепочки у последующих stuck совпадают с цепочками более ранних init failures значительно чаще, чем при соседнем сдвинутом времени. Но «ровно один кандидат» означает один подходящий **анонимный пиксель** в этой выгрузке, а не доказанный один пользователь. `.daily` мог подавить настоящий первый пиксель, а часто встречающиеся цепочки способны совпасть у разных пользователей.
**Какие группы дают эту связь:**
| Группа stuck после tag | Stuck всего | Длинная цепочка совпала | Контроль +10 мин | Уникальный кандидат с той же группой init |
|---|---:|---:|---:|---:|
| iOS, исправный native-снимок | 789 | **437** | 199 | 249 |
| iOS, background load error 6 | 338 | **152** | 20 | 142 |
| macOS, исправный native-снимок | 335 | **138** | 21 | 120 |
| macOS, только история context error | 79 | **20** | 6 | 14 |
Пример совместимой пары iOS с одним кандидатом: в init-хвосте после повторных `dealloc→view→proxy_on` стоит `error_wkwebextensioncontexterrordomain:6@30s`; 41 секунду спустя stuck содержит **тот же порядок 12 токенов**, а последний error уже помечен `@2m`. Это согласуется с одним сохраняющимся background-сбоем через два timeout, но без reporter/episode ID остаётся вероятностным сопоставлением. В группе с исправным native-снимком найдены аналогичные пары с тем же длинным `load→view→proxy_on→dealloc→view→proxy_on...` и более старшими age buckets на stuck: два timeout могут происходить без нового видимого WebKit lifecycle-события.
Метод намеренно слаб для коротких хвостов `load` и `load→view→proxy_on`, в том числе startup `no scripts/no view` и macOS controller mismatch: у 35 macOS stuck с неверным controller только 3 хвоста длиной ≥6, поэтому отсутствие длинного match здесь ничего не опровергает. Из этих таблиц **нельзя вычислять вероятность перехода отдельного init failure в stuck**: нет идентификатора установки или episode, а `.daily` дедуплицирует имена. Тем не менее сочетание state machine в коде и превышения над контролем подтверждает, что stuck-пиксели отражают повторные timeout-эпизоды, а не просто новое имя для одиночного init failure.
Воспроизведение: `match_event_chains.py` → `chain_match_summary.tsv`, `chain_match_transitions.tsv` в каталоге выгрузки. Исходные строки: `initialization_daily.tsv` и `events_daily.tsv`. Числа выше используют только after-tag строки; до публикации остаются отдельно.
## Источники и воспроизведение сверки
Локальная выгрузка и код подсчёта: `cpm-public-attribution-2026-10-06/` в `/Users/admin/DuckDuckGo/apple-browsers.git`. `inventory.tsv` — полный CPM daily/count перечень и actual reload errors; `release_cutover_daily.tsv` — все CPM daily и actual reload daily по обе стороны release-tag timestamp; `initialization_daily.tsv` и `events_daily.tsv` — PII-free строки attribution с точным timestamp; `release_cutover_groups.tsv` и `release_cutover_reasons.tsv` — группы и причины по обе стороны tag, воспроизводятся `analyze_cutover.py`; `daily_groups_exclusive.tsv`, `daily_families.tsv`, `daily_reasons.tsv`, `daily_signatures.tsv` — сводки **полного дня, включая pre-tag**; `release_cpm_daily_trends.tsv` — 982 строки дневных трендов шести версий. Код поведения: `CPMMessagingHealthMonitor.swift` (`reportFailure`, `reportSuccess`), `WebExtensionManager.swift` (`onConfirmedHang`, `reloadEmbeddedExtensionAfterCPMMessagingHang`), `WebExtensionLifecycleCoordinator.swift` (navigation gate), `PixelKit.swift` (лимит `.daily` по имени и UTC). Снимок законченного дня; для вывода о динамике incidence нужен следующий полный UTC-день новой версии и знаменатель активных установок той же версии.