Files
obsidian-vault/work/projects/cpm-web-extension-breakage-validation.md
T

93 lines
10 KiB
Markdown

# CPM Web Extension Breakage — статус и PR-план
> Актуально на 2026-09-23. Полная трассировка: [[cpm-web-extension-breakage-findings]].
> Experiment Definition: [[cpm-background-graveyard-experiment-definition]].
> **2026-09-25 source audit:** [[cpm-service-worker-architecture-and-diagnostics]]. Earlier readiness/test results below are historical, not validation of the newly identified gaps. Additional gates: M2 stable-origin + M5/M6 reload/Fire interaction; worker exception contract; distinguish dashboard response from actual page initialization. No new device tests were run for this audit.
## Решение
Есть два независимых класса проблем:
- **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.