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

16 KiB
Executable File
Raw Blame History

CPM Web Extension Breakage — статус и проверка причин

Актуальный отчёт за завершённый 5 октября 2026 UTC: cpm-web-extension-public-attribution-2026-10-05. Он сверяет все init failure.daily, диагностические группы, соседние события, тренды до релиза и восстановление после stuck. Исторические internal-testing проверки и PR-план ниже сохранены как архив.

Текущий статус — только после публикации 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):

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, 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, 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, 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.