Files
obsidian-vault/work/projects/cpm-m3-telemetry-privacy-triage.md
T

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.