[2026-09-23] eagle: work/projects/cpm-m3-telemetry-privacy-triage.md work/projects/cpm-web-extension-breakage-validation.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-23 15:12:47 +06:00
1 parent 34f4731e72
commit b994c2479c
2 files changed
+36 -21

No files matched your search

@@ -1,10 +1,13 @@
# TD: Pixel Definition: CPM Failure Diagnostics (macOS/iOS)
**Author:** Alex M
**Reviewer:** TBD
**Reviewer:** Michal
**Stakeholders:** Michal, Konrad, Russell (from the first Tech Design; confirm for this proposal)
**Project:** [CPM: root-cause the stuck embedded extension messaging (macOS/iOS)](https://app.asana.com/1/137249556945/task/1216761517055116)
**Previous Tech Design:** [CPM breakage pixels](https://app.asana.com/1/137249556945/task/1218005985662983)
**Tech Design:** [Asana](https://app.asana.com/1/137249556945/task/1218619301158188)
**Privacy Triage:** [Asana](https://app.asana.com/1/137249556945/task/1218627883499612)
**Status:** [PR #6844](https://github.com/duckduckgo/apple-browsers/pull/6844) merged 2026-09-23; CI green. Merge commit `2d58ad8cf4fae728a92e2aae0d3ec6073ddf49f2`. Production rollout not yet confirmed.
## Background & Requirements
@@ -22,7 +25,9 @@ This proposal covers diagnostic collection only; it does not change recovery beh
Keep the existing failure/stuck detection. A shared recorder holds lifecycle and critical-memory-pressure events in memory. When a pixel fires, it captures current WebKit/tab state synchronously on the main actor. Earlier context loads cannot add events to this timeline. There are no JavaScript probes or periodic uploads.
A delegate proxy records process termination and responsiveness, then forwards callbacks to WebKit. WebKit's delegate owns the proxy; the proxy holds it weakly. The remote flag enables the proxy on the current view. Disabling restores original delegates on all surviving views, including after unload; other diagnostics remain enabled.
`cpmDiagnosticsRecorder` controls recorder creation when the extension manager is created; changes apply to the next manager instance, normally after app restart. Disabled: no recorder or proxy, and existing failure/stuck pixels carry no recorder diagnostics.
A delegate proxy records process termination and responsiveness, then forwards callbacks to WebKit. WebKit's delegate owns the proxy; the proxy holds it weakly. The separate `cpmBackgroundDelegateProxy` flag applies at runtime: enabling installs the proxy on the current view; disabling restores original delegates on all surviving views, including after unload. Other recorder diagnostics remain available. Both flags default to enabled and support local overrides.
| Pixel | Trigger | Frequency |
|---|---|---|
@@ -32,7 +37,9 @@ A delegate proxy records process termination and responsiveness, then forwards c
`<reason>`: `session_restoration`, `tab_crash`, `extension_reload`, `other`. PixelKit applies standard frequency/platform suffixes. Standard metadata includes `appVersion`; macOS also declares `pixelSource` and `channel`.
Recovery pixels remain unchanged. The new reload-failure pixel reports a failed reload operation, unlike the existing `cpm_messaging_extension_reload_failed`, which reports CPM failure **after a successful reload**.
Both existing recovery pixels (`debug_web_extension_cpm_messaging_recovered_without_extension_reload` and `debug_web_extension_cpm_messaging_recovered_after_extension_reload`) gain `recovery_from`: `initialization_failed` or `messaging_stuck`, based on the episode state before recovery. Names, triggers and daily + count frequency stay unchanged.
The new reload-failure pixel reports a failed reload operation, unlike the existing `cpm_messaging_extension_reload_failed`, which reports CPM failure **after a successful reload**.
### Failure / stuck parameters
@@ -54,10 +61,17 @@ Unavailable optional facts are omitted. The first nine parameters are booleans (
| `bg_view_leaked_count` | Retained views beyond the expected current view; same buckets. Not proof of a leak. |
| `context_errors` | `none` or accumulated error descriptors: raw domain/code pairs, including the immediate underlying error. No localized text; no explicit payload cap. |
| `bg_events` | Recent lifecycle sequence with bucketed ages relative to the snapshot. |
| `app_version_change` | `updated` or `downgraded`; omitted if unchanged or no previous value exists. Fixed for the app session. Version/build values are stored locally, not sent in this parameter. |
| `app_launch_age` | Time since app startup using the same age buckets as `bg_events`. Foregrounding and recorder recreation do not reset it. |
The recorder keeps 40 events. `bg_events` sends up to the last 12, within 255 characters, dropping older entries first. Format: `event_name@age_bucket`. Buckets: `30s` (<30s), `1m` (30–<60s), `2m` (1–<2 min), `5m` (2–<5 min), `15m` (5–<15 min), `over_15` (≥15 min). Events remain oldest first; order and repeated events within a bucket are preserved. Exact ages are not sent.
Events: `load`, `view`, `dealloc`, `unresponsive`, `responsive`, `proxy_on`, `proxy_off`, `died_<reason>`, `error_<descriptor>`, `context_unload_failed_<descriptor>`, `extension_files_remove_failed_<descriptor>`.
Events: `load`, `view`, `dealloc`, `unresponsive`, `responsive`, `proxy_on`, `proxy_off`, `died_<reason>`, `error_<descriptor>`, `context_unload_failed_<descriptor>`, `extension_files_remove_failed_<descriptor>`, `extension_updated`, `base_url_changed`.
- `extension_updated`: successful replacement of an installed CPM extension, not first installation or ordinary reload. The latest update and its original timestamp survive context reloads.
- `base_url_changed`: base URL differs from the previous context load with the same `uniqueIdentifier` in this recorder's lifetime. No event on first load or identifier change. Neither URL nor identifier is sent. This observes URL changes; it does not implement the stable-base-URL fix.
Future `hold` / `release` events belong to the graveyard mitigation and are not part of this PR.
Termination reasons: `memory`, `cpu`, `client`, `crash`, `crash_limit`, `unknown`. The `error_` events record background-load failures. CPM unload failures (reload, data clearing or uninstall) and uninstall file-removal failures are also recorded. Failed unloads keep the context; file-removal errors can still be recorded after unload. These entries reach the server only with a later failure/stuck pixel; existing standalone error pixels are unchanged.
@@ -112,7 +126,16 @@ None. Recovery changes and experiments are outside this proposal.
**Operational**
Update the existing CPM Grafana board for the new fields and reload failures. Agree a diagnostic removal/review date; none is enforced currently. Failure-only snapshots cannot measure the failure rate across all process deaths or prove that all tabs failed. Older events, including the trigger, can drop out of the timeline.
Update the existing CPM Grafana board for the new fields and reload failures. Diagnostic removal is planned for approximately November 2026; no removal date is enforced in code. Feature flag review is due October 23, 2026. Failure-only snapshots cannot measure the failure rate across all process deaths or prove that all tabs failed. Older events, including the trigger, can drop out of the timeline.
Feature flag registry tasks (For review, assigned to Alex M):
| Flag | iOS | macOS |
|---|---|---|
| `cpmDiagnosticsRecorder` | [Task](https://app.asana.com/1/137249556945/project/1211834678943996/task/1218785444896174) | [Task](https://app.asana.com/1/137249556945/project/1211834678943996/task/1218781680888931) |
| `cpmBackgroundDelegateProxy` | [Task](https://app.asana.com/1/137249556945/project/1211834678943996/task/1218782530802146) | [Task](https://app.asana.com/1/137249556945/project/1211834678943996/task/1218785444683902) |
Task descriptions contain the template values; registry custom fields still require Platform, Is failsafe=True and Is permanent=False unless filled manually since creation.
**Localization / Internationalization**
@@ -1,6 +1,6 @@
# CPM Web Extension Breakage — статус и PR-план
> Актуально на 2026-09-22. Полная трассировка: [[cpm-web-extension-breakage-findings]].
> Актуально на 2026-09-23. Полная трассировка: [[cpm-web-extension-breakage-findings]].
> Experiment Definition: [[cpm-background-graveyard-experiment-definition]].
## Решение
@@ -33,7 +33,7 @@ M4 перезагружает Web Extensions при первом сигнале
|---|---|---|
| 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` | **Production-ready code:** passive PII-free buckets, app-update/launch attribution, stable-base-URL transition fact, iOS/macOS schema validation green. Privacy triage: [[cpm-m3-telemetry-privacy-triage]]. |
| 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 graveyard mitigation | `alex/cpm-graveyard-mitigation` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-graveyard-mitigation` | **Production-ready, default off:** one retained view maximum, 3-second bounded hold, stale release cancellation, unload/reload/runtime kill-switch cleanup. 24/24 targeted tests; earlier real forced-kill iOS E2E recovered with release at ≈3.15 s. |
| M6 graveyard A/B | `alex/cpm-graveyard-ab` — `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-graveyard-ab` | **Production-ready code:** lazy exposure enrollment, stable cohorts, control/treatment isolation, bounded pending queue, operational pixels and standard `experiment_metrics` outcome. 421/421 WebExtensions tests; iOS/macOS app builds; iOS/macOS pixel validators. |
@@ -42,11 +42,9 @@ All milestone diffs pass `git diff --check`; changed Swift lines are within 150
## Stack and Git state
Independent PRs:
M3 merged: [PR #6844](https://github.com/duckduckgo/apple-browsers/pull/6844), merge commit `2d58ad8cf4fae728a92e2aae0d3ec6073ddf49f2`.
1. M2 — stable base URL.
2. M3 — production telemetry.
3. M4 — reload on the first `hung`, after `messaging init failure`.
M2 — independent stable-base-URL fix. Вошедший в M3 `base_url_changed` только сообщает о смене URL между загрузками контекста с одинаковым identifier; URL и identifier не отправляются. Это не фикс M2.
Stacked PRs:
@@ -57,15 +55,9 @@ M3 telemetry
└── M6 A/B experiment
```
Current refs intentionally preserve the earlier no-commit workflow:
Следующий Git-шаг: обновить `origin/main`, сверить diff M2/M4/M5/M6 и перенести оставшиеся изменения на merged M3 в порядке выше. Старые локальные refs не подтверждают актуальный stack; готовность M2/M5/M6 в таблице относится к предыдущей локальной валидации, не к повторной проверке после merge M3. Rebase/commit этих веток ещё не выполнялись в рамках этого обновления.
- M3 and M5 branch refs: `814bce5d11`; their milestone changes are unstaged.
- M5 ghost commit used only as M6 base: `cefad244c5`.
- M6 branch ref: `cefad244c5`; M6 changes are unstaged above it.
- M5’s final combined pixel description has been pulled into M6.
- Local `origin/main` advanced from the M3 merge base by one unrelated pixel-definition commit (`67a2f2b7b2`); it does not overlap CPM files.
Before opening PRs: commit M2 and M3, update them onto current `origin/main`, recreate/rebase the M5 commit onto M3, then rebase M6 onto that M5 commit. Do not include generated `Package.resolved` churn; Cursor currently regenerates it in the open M3 workspace.
Не включать generated `Package.resolved` churn.
## Experiment contract
@@ -79,10 +71,10 @@ Full definition and config draft: [[cpm-background-graveyard-experiment-definiti
## Remaining gates
1. Build and validate M4, then open M2/M3/M4/M5/M6 PRs in the topology above.
1. Обновить оставшиеся ветки поверх merged M3; build/validate M4 и повторно проверить зависимые M5/M6. Открыть 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.
No Asana tasks, comments, or configuration were created during this review.
M3 merged, но ещё не подтверждён как shipped production release. Feature flag registry tasks созданы отдельно для iOS/macOS; ссылки — в [[cpm-m3-telemetry-privacy-triage]]. Это обновление затрагивает только Obsidian.