8.7 KiB
Tech Design 2 / Privacy Triage: Apple — CPM M3 Telemetry
Status: DRAFT — privacy review required
Name: Alex M · Email: amartemyanov@duckduckgo.com · Objective: O-E
PR: [Add PR link]
Project: https://app.asana.com/1/137249556945/project/1163321984198618/task/1216761517055116?focus=true
Baseline: cpm-extension-health-pixels-privacy-triage
Scope: Current implementation in codex/cpm-failure-telemetry, reviewed 2026-09-17. iOS 18.4+ / macOS 15.4+. Describes implemented collection, not the proposed reduced design.
1. Purpose and approach
The first design detects CPM initialization failures, persistent messaging failures and recovery. M3 adds facts that help distinguish background-process failure, context-load errors and tab wiring problems.
An in-memory recorder observes extension lifecycle, WebKit process callbacks and critical memory pressure. When an existing initialization-failure or stuck pixel fires, it attaches a best-effort snapshot. There are no new JavaScript probes, page-content reads or periodic telemetry uploads. The delegate proxy forwards WebKit callbacks and is remotely switchable; this switch disables the proxy, not all M3 collection.
A separate new pixel reports extension reload operations that fail before the replacement context becomes active. Graveyard mitigation and A/B cohorts are outside M3.
2. Pixels and frequency
Names below are base names; PixelKit adds frequency/platform suffixes. iOS uses its standard platform/form-factor suffixes; macOS keeps its standard naming policy. These are not new explicit platform / form_factor payload fields. Standard metadata includes appVersion; macOS schemas also declare pixelSource and channel.
| Pixel | Change / trigger | Frequency |
|---|---|---|
debug_web_extension_cpm_initialization_failed_after_<reason> |
Add the snapshot when an eligible finished navigation fails the existing CPM response check. | Daily |
debug_web_extension_cpm_messaging_stuck_<reason> |
Add the snapshot when a later eligible navigation confirms the failure episode. | Daily + count; once per stuck episode |
debug_web_extension_reload_failed |
New: an extension context unload/load operation fails during reload; not limited to CPM. | Daily + count |
For both CPM families, <reason> is session_restoration, tab_crash, extension_reload or other. The stuck reason comes from the initial failure. This includes the stuck_extension_reload variant omitted from the first note's table.
Existing recovery pixels and debug_web_extension_cpm_messaging_extension_reload_failed gain no diagnostic parameters. The latter means CPM failed after a successful extension reload; the new pixel instead means the reload operation itself failed.
3. What is actually sent
CPM failure / stuck snapshot
Booleans are strings true\|false. Unavailable optional facts are omitted, not sent as false.
| Parameter | Collected fact / transmitted values |
|---|---|
extension_context_loaded |
Whether the tracked context is loaded. Boolean. |
background_view_alive |
Whether the recorder's current background WKWebView still exists. Boolean. |
background_web_process_alive |
Whether its current process identifier is nonzero. Boolean; PID is not sent. |
background_web_process_responsive |
WebKit's process-responsiveness verdict via SPI. Boolean. |
network_process_restarted |
Current network PID differs from the value captured at context load. Boolean; not a restart count or proof of causality. |
tab_known_to_webkit |
Failing tab is present in the context's open-tabs set. Boolean. |
tab_controller_matches_context |
Failing tab and context use the same extension controller. Boolean. |
tab_has_extension_user_scripts |
Tab's registered user scripts include the expected extension world. Boolean; does not prove execution. |
native_handler_registered |
Autoconsent native-message handler is registered. Boolean; does not prove a reply was delivered. |
memory_pressure_critical |
Age of last observed critical-pressure notification: none|under_1_min|under_5_min|under_30_min|over_30_min. No memory size is sent. |
background_view_create_count |
Views created since context load: 0|1|2|3_to_5|over_5. |
background_view_leaked_count |
Tracked live views beyond the expected current view; same count buckets. Indicates retained views, not a proven leak. |
extension_context_errors |
none, or accumulated distinct error descriptors since context load. Context errors map to names; other/underlying errors include domain and numeric code, not localized text. No explicit payload cap. |
background_events |
Recent lifecycle sequence, described below. Omitted when empty. |
The recorder retains at most 40 lifecycle events in memory. A snapshot takes the latest 12; serialization removes oldest entries until the value fits 255 characters. Format: event_name@-seconds, relative to snapshot time, rounded to whole seconds.
Event names: load, view, dealloc, unresponsive, responsive, proxy_on, proxy_off, died_<reason>, error_<descriptor>. Death reasons: memory_limit\|cpu_limit\|requested_by_client\|crash\|shared_crash_limit\|unknown. Error timeline entries represent background-load errors. Names are lowercased, character-filtered and limited to 64 characters; filtering is not an error-domain allowlist.
Example: view@-60,died_crash@-42,error_background_failed_to_load@-41. These are internally generated event labels, not identifiers supplied by a website.
New reload-failure pixel
| Parameter | Allowed values |
|---|---|
extension_type |
embedded|darkMode|adBlocking|searchToken|unknown |
reload_trigger |
data_clearing|scriptlet_update|explicit |
reload_phase |
unload|load|lightweight_load|full_load|fallback_load |
reload_error |
already_loaded|not_loaded|base_url_in_use|no_background_content|background_failed_to_load|unknown|other |
Unlike extension_context_errors, reload_error is an allowlisted category: it includes no raw domain, numeric code, underlying error or message.
4. Privacy review questions
Browsing data or identifiers? No intentional collection of URLs, domains visited, search queries, titles, page content, message payloads, user IDs, tab/document/context IDs, extension UUIDs or process IDs in these new payloads. Internal object/ID comparisons emit booleans. Error domains are error namespaces, not visited website domains, but are currently not allowlisted.
Could events be linked or unusually specific? No explicit correlation identifier is added. However, exact-second event sequences and arbitrary error-domain/code combinations can be high-cardinality. This draft does not assert that linkability is impossible.
Sensitive user actions? reload_trigger=data_clearing reveals that a failing reload followed data clearing. It does not identify cleared sites or data. This requires explicit triage consideration.
Temporary or permanent? Intended for diagnosis and the M4 validation gate. Current schemas do not implement an expiry/removal deadline; agree one before rollout. No change to the baseline health pixels' intended lifetime.
Self-service eligible? Not asserted. Exact seconds conflict with the repository's coarse-bucketing guidance; error descriptors, the data-clearing trigger and retention/removal policy need a privacy decision.
5. Boundaries and release gates
Local logs are separate from pixel payloads. Current recorder code also logs process IDs and raw localized/underlying errors with privacy: .public. These logs may expose paths or URLs and must be removed/redacted before production; pixel approval does not approve them.
What these pixels cannot establish: no denominator of all process deaths; deaths followed by normal recovery may produce no diagnostic pixel. A failing tab does not prove an all-tabs outage. The rolling timeline may lose the original trigger, and deferred snapshots can observe newer state.
Observer effect: the findings note records that installing the delegate proxy changed the forced-process-death reproduction. Validate the current proxy on/off on both platforms before treating M3 as an unaffected production baseline.
Approval requested: necessity and format of the snapshot fields, coarse replacement or explicit exception for exact-second timelines, allowlisting/bounding of error descriptors, the data-clearing trigger, and a diagnostic sunset date.
Implementation references: CPMMessagingDiagnostics.swift, CPMMessagingDiagnosticsRecorder.swift, CPMBackgroundWebViewDelegateProxy.swift, WebExtensionPixelFiring.swift; iOS/macOS pixel schemas. Investigation: cpm-web-extension-breakage-findings. Milestones: cpm-web-extension-breakage-validation.