150 lines
20 KiB
Markdown
Executable File
150 lines
20 KiB
Markdown
Executable File
---
|
||
updated: '2026-10-20'
|
||
---
|
||
# CPM Web Extension Breakage — статус и проверка причин
|
||
|
||
> **Сверено 20 октября 2026.** Актуальный реестр PR/CI — [[cpm-status-2026-10-15#2. PR и quality gates]], телеметрия/границы диагностики — §6, исходники — [[cpm-web-extension-code-guide-2026-10-09#0. Текущая карта worktree и точек входа]].
|
||
>
|
||
> #7376 `5d1cb4abd2` опубликован, CI зелёный, но закрыт без merge. #7484 — старый macOS payload с conflict. #7379 имеет failed BSK crash gate; #7459, #7493 и #7473 имеют зелёный exact-head CI. Новая телеметрия измеряет completed episodes, не совпадает со старой системой event totals.
|
||
>
|
||
> Таблицы M2/M5/M6, «Network restart отвергнут», «всё local» и старые OPEN/CI ниже — датированная история. P3 stable-origin policy теперь в #7493, Network fix — #7459 с отдельным applied pixel; это не полная causal attribution или доказанный production recovery rate.
|
||
|
||
## Исторический контекст — 15 октября 2026
|
||
|
||
> **Актуализировано 15 октября 2026.** Текущий реестр PR/CI, данные 12–14 октября, исходы и ограничения всех новых P5/P6 tests — [[cpm-status-2026-10-15]]. Worktree/команды — [[cpm-web-extension-code-guide-2026-10-09]] (§0). Таблицы milestones, готовность M2/M5/M6 и «Network restart отвергнут» ниже — исторические оценки сентября; они не являются текущим статусом.
|
||
|
||
## Историческая проверка — 15 октября
|
||
|
||
- P5/tab=false: source-linked shared-pinned window close → WebKit внутренний didCloseTab → tab-not-found до wake. Exact stuck-сигнатура воспроизведена; unrelated-window control здоров. Только browser contract + shared classes/system WebKit, не полный AppKit E2E.
|
||
- P6: main flag true→false→true через production handler воспроизводит wrong-controller/no-scripts; факт переключения в production не установлен. nil-controller варианты остаются controlled. Обычный startup с correct controller здоров, embedded-only cycle сохраняет controller.
|
||
- Embedded-only sync до background ready потерял первый init (первый red control), что соответствует известному A/P4. После explicit ready wait тот же binding control здоров; это не фиксация production-поведения.
|
||
- Listener metadata с первого старта — controlled P5, естественный вход неизвестен. Один пропуск listener, длинные документы и main-actor stall P5 не дали. Реальный iOS suspend/resume и происхождение P5/tab=true остаются непроверенными.
|
||
- 14 актуальных методов green по нескольким прогонам; последние обычные tests: 478 XCTest, 28 skipped, 0 failures, 10.469 с; strict SwiftLint 8 файлов — 0. Исторические счётчики tests ниже относятся к иным bases/worktrees и не являются этим прогоном.
|
||
- Текущая сверка GitHub: #7376/#7378/#7379 и JS #3727 без failing/pending checks; #7384/#7385 — Danger=FAILURE; #7381 — красный remote head `39f94e12eaa4`, локальный fix `f1085846e7ff` не запушен. Все эти PR OPEN; подробности в основном статусе.
|
||
- M1-фильтрация явных callbacks уже есть в `1.210.0+macos`, но не предотвращает закрытие shared tabs внутри didCloseWindow. M2 stable-base-url остаётся старым экспериментом без нужного полного integration прогона. B/Network crash воспроизведён позднее сентябрьского отрицательного прогона; прежнее «отвергнуто» не универсально.
|
||
|
||
Дальнейшие app fixes, telemetry changes, commit/push или remote flags этим обновлением не менялись. Ниже сохранены исходные датированные срезы, тесты и планы.
|
||
|
||
|
||
|
||
> **Исторический отчёт за завершённый 5 октября 2026 UTC:** [[cpm-web-extension-public-attribution-2026-10-05]]. Он сверяет все `init failure.daily`, диагностические группы, соседние события, тренды до релиза и восстановление после stuck. Исторические internal-testing проверки и PR-план ниже сохранены как архив.
|
||
|
||
## Исторический срез 5 октября — только после публикации release tag
|
||
|
||
- Основной сигнал: 6 624 iOS и 1 377 macOS `initialization.failed.after.*.daily`; группы и контрольные суммы — в [[cpm-web-extension-public-attribution-2026-10-05]].
|
||
- На macOS есть 77 `init failure.daily` и 35 `stuck.daily` с controller mismatch и отсутствующими scripts **после** публикации tag; **до** публикации таких строк нет. Пиксели не содержат channel, поэтому отдельное событие нельзя строго назвать public-user report.
|
||
- Session restoration failure сохраняется: 952 iOS / 68 macOS после tag. Долю временно восстановившихся по этому reason без связывания событий посчитать нельзя.
|
||
- Крупнейшая группа (3 717 iOS / 911 macOS init) имеет исправный native-снимок, но monitor не получил вовремя `refreshCpmDashboardState`. Это не доказывает потерю `initResp`: фактическая доставка в content script не измеряется. M4 после stuck дал 916 iOS / 354 macOS `recovered.after.extension.reload.daily`, подтверждая возобновление измеряемого background → native пути, но не всей CPM-функции.
|
||
- До публикации macOS tag 5 октября было 28 init и 2 stuck; они вынесены в отдельный раздел отчёта вместе со ссылкой на более ранний internal-testing архив.
|
||
|
||
## Исторические проверки и планы
|
||
|
||
## Как получить и проверить attribution data
|
||
|
||
**Тулсет:** `/Users/admin/Developer/grafana-data-tools/` — `query.py`, `queries/coverage.sql`, `queries/tab-signatures.sql`, `queries/tab-chains.sql`, `README.md`. Это постоянные файлы; `/tmp/cpm-*` из прошлой сессии не нужны. Runner импортирует точный helper `/Users/admin/DuckDuckGo/grafana-dashboards/tools/grafana-query/read-dashboard.py`; тот вызывает Grafana `/api/ds/query` и авторизуется через `tools/grafana-auth/grafana-auth` (SSO) либо `GS_GRAFANA_API_KEY`. Datasource: ClickHouse UID `dekdbd6bqq1hcd`, type `grafana-clickhouse-datasource`, таблица `metrics.pixels`. Токены не записывать в заметки или логи.
|
||
|
||
**Проверенные команды** (2026-10-05; `--start` включительно, `--end` исключительно, UTC):
|
||
|
||
```bash
|
||
cd ~/Developer/grafana-data-tools
|
||
uv run --with requests query.py coverage --start 2026-09-28 --end 2026-10-05
|
||
uv run --with requests query.py tab-signatures --start 2026-09-28 --end 2026-10-05 --agent ddg_mac_desktop --version 1.210
|
||
uv run --with requests query.py tab-chains --start 2026-09-28 --end 2026-10-05 --agent ddg_mac_desktop --version 1.210
|
||
uv run --with requests query.py tab-signatures --start 2026-09-28 --end 2026-10-05 --agent ddg_ios --version 7.240
|
||
```
|
||
|
||
`coverage` проверяет **все** версии и оба суффикса `.count`/`.daily` до фильтрации по attribution (`attributed` = есть live-поле `context_loaded`; `chain_present` = есть `bg_events`). Ожидаемая историческая проверка: macOS 1.210 за 28 сентября — 4 октября даёт 131 `.count` и 82 `.daily` attributed; iOS 7.240 — 6 и 5. `tab-signatures` даёт `131 / 88 / 24 / 17 / 10 / 7 / 6` для macOS и `6 / 5 / 0 / 0 / 0 / 0 / 0` для iOS (`reports / repeated_dealloc / scripts_absent / controller_mismatch / mismatch_after_update / same_controller_zero_views / zero_views_after_update`). `tab-chains` показывает ordered `bg_events` **до** stuck с группировкой по reason, controller, view и update. Все четыре команды реально выполнены через API; SQL лежит в `queries/`, а не только в этой заметке.
|
||
|
||
**Методика:** сначала `coverage` сверяет `date`, `agent`, `appVersion`, `.count`/`.daily` и наличие attribution с Kibana; огромный legacy iOS-поток без `context_loaded` не включать в анализ новых параметров. Затем `tab-signatures` проверяет совместные признаки, `tab-chains` — порядок событий. В коде `apple-browsers.git/main` сверять их с `CPMMessagingDiagnosticsRecorder` и созданием `WebExtensionManager`, а не со старым M3 worktree. При пустом ответе Grafana без data frame повторить запрос: это не подтверждённый ноль. `.count` — репорты, `.daily` — дневные пиксели, не уникальные пользователи; `bg_events` — ограниченный хвост с грубыми age buckets, `dealloc` не доказывает process death. Internal forced-failure testing может смещать все сигнатуры. Для public заменить даты на фактический rollout и брать только завершённые UTC-дни, отдельно iOS/macOS и app version; affected-user rate и recovered/permanent требуют отдельного user-level числителя и DAU **той же версии** — эти команды их пока не считают.
|
||
|
||
## Исторический статус и PR-план (2026-09-23—09-25)
|
||
|
||
> Ниже сохранён прежний план. Его формулировки о shipped status, готовности PR и дизайне graveyard A/B не являются текущим release/experiment contract; сверять с `apple-browsers.git/main` и актуальной privacy config.
|
||
|
||
## Решение
|
||
|
||
Есть два независимых класса проблем:
|
||
|
||
- **Per-tab lifecycle** исправлен в M1 и уже merged.
|
||
- **All-tabs CPM outage:** M3 telemetry merged; M4 hung recovery и объединённый M5+M6 graveyard A/B — независимые follow-up PRs.
|
||
|
||
M4 перезагружает Web Extensions при первом сигнале `hung`, который подтверждает global CPM outage после предшествующего `messaging init failure`. Сам `messaging init failure` остаётся диагностическим: он не меняет состояние расширений.
|
||
|
||
## Статус проблем
|
||
|
||
| Проблема | Статус | Что делаем |
|
||
|---|---|---|
|
||
| Forced background-process death → persistent all-tabs CPM outage | **Подтверждено** | M3 измеряет production incidence; объединённый M5+M6 PR лечит и сравнивает control/treatment. |
|
||
| Natural crash/memory/CPU termination → production outage | **Не проверено в production** | Один production release M3 до включения M5/M6. |
|
||
| Close Other Tabs, popup duplicate, pin/unpin/reorder/move/merge/window close | **Исправлено, merged** | M1 PR #6827. |
|
||
| Нестабильный Web Extension base URL | **Исправлено локально** | M2 задаёт deterministic base URL до load/reload и отвергает malformed identifier без crash. |
|
||
| Startup/session-restore content-script miss | **Подтверждено как transient** | Не объясняет persistent all-tabs outage. |
|
||
| Failed background load навсегда блокирует WebKit | **Отвергнуто** | WebKit повторяет load; нужна повторяемая причина/traffic. |
|
||
| Critical memory pressure как root cause | **Не воспроизведено** | Остаётся diagnostic correlation M3. |
|
||
| Event starvation / JSC hang / native messaging stall | **Не проверено** | Остаётся в M3 diagnostic buckets. |
|
||
| Network process restart | **Отвергнуто** | На проверенной сборке CPM восстановился. |
|
||
| removeAllUserScripts / flag transition / scriptlet reload / page JS как all-tabs cause | **Отвергнуто** | Это не persistent global failure mode. |
|
||
|
||
## Milestones
|
||
|
||
| Milestone | Branch / worktree | Готовность |
|
||
|---|---|---|
|
||
| M1 tab lifecycle | `codex/cpm-tab-lifecycle-fixes` | **Merged:** [PR #6827](https://github.com/duckduckgo/apple-browsers/pull/6827), CI green. |
|
||
| M2 stable base URL | `alex/cpm-stable-base-url` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-stable-base-url` | **Production-ready:** 2 files; 11/11 targeted tests. Добавлена safe URL construction и negative validation вместо force-unwrap. |
|
||
| M3 attribution telemetry | `alex/cpm-failure-telemetry` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-failure-telemetry` | **Merged 2026-09-23:** [PR #6844](https://github.com/duckduckgo/apple-browsers/pull/6844), CI green. Bucketed diagnostics, app launch/version attribution, `extension_updated`, `base_url_changed`, `recovery_from`, reload-error pixel; recorder и proxy имеют отдельные flags. [[cpm-m3-telemetry-privacy-triage]]. |
|
||
| M4 hung recovery | `codex/cpm-hung-recovery` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-hung-recovery` | **In progress:** первый `messaging init failure` открывает diagnostic episode; первый последующий `hung` перезагружает Web Extensions. |
|
||
| M5+M6 graveyard mitigation + A/B | `alex/cpm-graveyard-mitigation` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-graveyard-mitigation` | **Production-ready, default off; one PR over merged M3:** one retained view maximum, 3-second bounded hold, weak scheduled release, runtime cleanup, lazy stable cohort enrollment, control/treatment isolation, bounded pending outcomes, operational and experiment pixels. 431/431 WebExtensions tests; iOS/macOS builds and pixel validators pass; earlier real forced-kill iOS E2E recovered with release at ≈3.15 s. |
|
||
| M7 state-restoration readiness gate | `alex/cpm-state-restoration-race` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-state-restoration-race` | **Implemented on macOS and iOS from current `origin/main` (`b93091e769`):** initial main-frame HTTP(S) navigation waits for Web Extension `loadAndSync`, bounded by shared `withTimeout(3)`. Timeout stops only the cancellation-aware waiter; the underlying load continues and its retained task is cleared on completion/cancellation. WebExtensions lifecycle suite: 20/20. iOS Swift compilation reached the app target successfully; the generic-simulator build then stopped in existing environment-dependent scripts because `TARGET_DEVICE_OS_VERSION` and a signing identity were unavailable. The 100-cycle macOS app-restart/session-restore UI regression was imported from the WIP worktree; its run was skipped by request. SwiftLint and diff/project-file checks pass. |
|
||
|
||
All milestone diffs pass `git diff --check`; changed Swift lines are within 150 columns.
|
||
|
||
## M7 plan — state restoration / Web Extension load race
|
||
|
||
**Confirmed race:** `setupWebExtensions()` creates the manager/controller before state restoration, but starts `WebExtensionLifecycleCoordinator.loadAndSync()` without awaiting it. `stateRestorationManager.applicationDidFinishLaunching()` runs immediately afterward. A restored tab can therefore begin its main-frame navigation while the CPM `WKWebExtensionContext` is not loaded, so WebKit cannot inject the CPM content script at document start. The existing Content Blocking navigation responder already waits for internal user scripts and rule lists; M7 adds the equivalent readiness boundary for Web Extensions.
|
||
|
||
1. Retain only the first coalesced `loadAndSync` task in `WebExtensionLifecycleCoordinator`, clear it on completion/cancellation, and expose a cancellation-aware waiter that never cancels the underlying load. **Done.**
|
||
2. Inject the waiter into both macOS `PrivacyDashboardTabExtension` and iOS `TabViewController`. Gate initial main-frame HTTP(S) navigation with the shared `WebExtensionNavigationGate`, which uses project `withTimeout(3)`. **Done.**
|
||
3. Keep the wait startup-only: after initial `loadAndSync`, the provider returns `nil`, so later navigations avoid an async hop. Non-web and subframe navigations do not wait. **Done.**
|
||
4. Cover blocking/release/cancellation, timeout without cancellation of the underlying load, late completion, default timeout, and navigation scope. WebExtensions lifecycle suite is 20/20; SwiftLint and structural checks pass. **Done.**
|
||
5. Add the existing WIP UI regression that restarts the macOS app 100 times, restores the Autoconsent page, and verifies CPM injection after every restart. **Included; execution intentionally skipped by request.**
|
||
|
||
Technical decision: awaiting `initialLoadAndSync.value` directly inside structured `withTimeout` was hang-tested and rejected. The timeout cancels its child operation, but a child suspended on `Task.value` does not finish from that cancellation, so the task group cannot return. The small cancellation-aware waiter gives `withTimeout` a cancellable wait without cancelling the retained load task.
|
||
|
||
## Stack and Git state
|
||
|
||
M3 merged: [PR #6844](https://github.com/duckduckgo/apple-browsers/pull/6844), merge commit `2d58ad8cf4fae728a92e2aae0d3ec6073ddf49f2`.
|
||
|
||
M2 — independent stable-base-URL fix. Вошедший в M3 `base_url_changed` только сообщает о смене URL между загрузками контекста с одинаковым identifier; URL и identifier не отправляются. Это не фикс M2.
|
||
|
||
Stacked PRs:
|
||
|
||
```
|
||
M3 telemetry — merged into origin/main
|
||
├── M4 hung recovery
|
||
└── M5+M6 graveyard mitigation + A/B — one PR
|
||
```
|
||
|
||
M5+M6 объединены на `origin/main` (`2d58ad8cf4fae728a92e2aae0d3ec6073ddf49f2`) без commit: в worktree лежат 19 unstaged файлов mitigation и A/B. Старый M6 worktree сохранён только как источник/backup и не является отдельным PR. M4 не изменён.
|
||
|
||
Не включать generated `Package.resolved` churn.
|
||
|
||
## Experiment contract
|
||
|
||
- Experiment: `cpmBackgroundGraveyardExperiment`.
|
||
- Cohorts: `control`, `treatment`.
|
||
- Enrollment: only on an actual eligible CPM background-process termination.
|
||
- Primary framework metric: `cpmBackgroundGraveyardOutcome`, window `0...1`, values `healthy`, `initialization_failed`, `not_measured`.
|
||
- Rollback: disable both `cpmBackgroundGraveyardExperiment` and `cpmBackgroundGraveyard`.
|
||
|
||
Full definition and config draft: [[cpm-background-graveyard-experiment-definition]].
|
||
|
||
## Remaining gates
|
||
|
||
1. Обновить и проверить M4 поверх merged M3. Открыть M2, M4 и объединённый M5+M6 PRs в topology выше.
|
||
2. Create separate iOS and macOS experiment-log tasks.
|
||
3. Create Privacy Configuration + privacy/schema review tasks.
|
||
4. Create Data Science sample-size/design and Native Apps Experiment Analysis task.
|
||
5. Ship M3 first; enable M5/M6 only if production telemetry confirms the process-death hypothesis.
|
||
|
||
M3 merged, но ещё не подтверждён как shipped production release. Feature flag registry tasks созданы отдельно для iOS/macOS; ссылки — в [[cpm-m3-telemetry-privacy-triage]]. Это обновление затрагивает только Obsidian.
|