[2026-11-10] eagle: work/projects/cpm-permanent-breakage-metric-tech-design-2026-11-10.md work/projects/cpm-project-summary-2026-11-03.md work/projects/cpm-wake-init-detection-tech-design-2026-11-10.md

This commit is contained in:
Alexey Martemyanov committed 2026-11-10 19:20:55 +06:00
1 parent 9787c15df0
commit 1aa7ab948a
3 files changed
+55 -146

No files matched your search

@@ -9,205 +9,114 @@ status: draft
--- ---
# TD: Pixel Definition: CPM Permanent Breakage Metric (macOS/iOS) # TD: Pixel Definition: CPM Permanent Breakage Metric (macOS/iOS)
> Internal metrics, keep out of public PRs. Companion: [[cpm-wake-init-detection-tech-design-2026-11-10]] (adds `last_stage` to the pixel defined here). Where we are: [[cpm-project-summary-2026-11-03]] (section "Состояние на 10 ноября"). Review of the earlier attempt that this design replaces: same section. > Internal metrics, keep out of public PRs. Companion: [[cpm-wake-init-detection-tech-design-2026-11-10]] (adds `last_stage`). Where we are: [[cpm-project-summary-2026-11-03]].
**Author:** Alex M **Author:** Alex M
**Reviewer:** TBD (previous CPM pixel designs: Michal) **Reviewer:** TBD (previous CPM pixel designs: Michal)
**Stakeholders:** Michal, Konrad, Russell (from the previous Tech Designs; confirm) **Stakeholders:** Michal, Konrad, Russell (from the previous Tech Designs; confirm)
**Project:** [CPM: root-cause the stuck embedded extension messaging (macOS/iOS)](https://app.asana.com/1/137249556945/task/1216761517055116) **Project:** [CPM: root-cause the stuck embedded extension messaging (macOS/iOS)](https://app.asana.com/1/137249556945/task/1216761517055116)
**Task:** to create ("Permanent breakage metrics", from the plan in [[cpm-project-summary-2026-11-03]]) **Task:** to create ("Permanent breakage metrics")
**Previous Tech Designs:** [[cpm-readiness-telemetry-tech-design-2026-10-15]], [[cpm-m3-telemetry-privacy-triage]] (both describe pixels this design supersedes) **Previous Tech Designs:** [[cpm-readiness-telemetry-tech-design-2026-10-15]], [[cpm-m3-telemetry-privacy-triage]]
**Privacy Triage:** to request (new pixel names, see Additional Considerations) **Privacy Triage:** to request (new pixel names)
## Background & Requirements ## Background & Requirements
The project goal is permanent CPM failures below 1% of DAU (0.2–0.5% counts as success). Today that number does not exist as a pixel. It is derived from `web_extension_cpm_episode` with a long SQL: a four-second navigation timeout opens an episode, a later timeout or reload confirms it, a five-minute deadline or a response closes it. Three problems: The goal is permanent CPM failures below 1% of DAU. There is no pixel for it; the number is derived from `web_extension_cpm_episode` with a long SQL. The episode is wrong in two ways:
- **The timer measures slowness, not breakage.** On iOS 7.244, 6.0% of episodes closed with a late answer on the same page, 42% of those later than 30 s. A page that answers after 40 s is counted as a failure. - **A timer decides.** A four-second navigation timeout opens an episode. On iOS 7.244, 6.0% of episodes closed with a late answer on the same page, 42% of those later than 30 s. Slow answers count as failures.
- **Episodes are lost.** An unfinished episode lives in memory; closing or killing the app drops it. The pages that stay broken longest are the ones that end with the app. - **Episodes are lost.** An open episode lives in memory and is dropped when the app exits.
- **The pixel carries what the investigation needed** (diagnostic booleans, `bg_events`, load fields) and is being trimmed to five parameters (PR 2 in the stack below). What remains cannot be turned into a small, fixed dashboard set.
Requirements: Requirements: a metric without a timer, as a rate against DAU; no page content, URLs or persistent identifiers. End state: the only CPM pixels are the two defined here.
- A small fixed set of metrics, as rates against DAU, for the health indicators dashboard.
- Detection that does not depend on a timer and does not depend on reaching a deadline before the app exits.
- No page content, URLs or persistent identifiers.
- Runs next to the episode pixel for one release, so the two series can be compared before the old one is removed.
- End state: the only CPM pixels are `web_extension_cpm_breakage` and `web_extension_cpm_breakage_persistent`.
## Problem Statement ## Problem Statement
Define and measure, per platform and version: Report pages that really never got the extension's answer, and tell persistent breakage from a single page.
1. **Breakage:** an http(s) page that finished loading with the extension loaded, and never got the extension's answer while its document lived.
2. **Persistent breakage:** several documents in a row with no answer in between, meaning the extension is broken for the user, not for one page.
3. How many installs per day are affected, and how many breakages happen.
The wake investigation (which init step stops) is separate: [[cpm-wake-init-detection-tech-design-2026-11-10]].
## Recommended Approach ## Recommended Approach
A page is **healthy** as soon as an answer for its document arrives, at any time while the document lives (a late answer is not a failure). A page that never got the answer is **judged when its document ends**. There are no navigation timers. The approach is additive: the existing monitor, its timers and the episode pixel stay unchanged; the new logic is a second consumer of the same events. **A page is healthy when an answer reaches it at any time while its document lives. A page that never got an answer is judged when its document ends. There are no navigation timers.**
### What was wrong with the first attempt
The first attempt (branch `alex/cpm-breakage-pixel`, commit `e6419bb910`) already used page-end judging, but combined it with deleting the episode pixel and had these defects, all addressed below:
1. Unanswered measurements older than five minutes were dropped, so the longest-silent pages were never reported.
2. No minimum lifetime: a page left 200 ms after loading counted as broken.
3. A web content process crash counted as extension breakage.
4. `background` was derived from "background load took at least one second" and from one app-wide signal, not from the page's navigation.
5. Pages alive when the app exits were never judged.
6. The macOS pixel definition omitted `pixelSource` and `channel` and used iOS-only suffixes.
7. `navigationWaitContexts` leaked an entry when the context was replaced, and `cpmStage` parsing had no test.
### Detection rules
Per eligible document: http(s), not a browser error page, not a history restore from cache (existing eligibility rules, unchanged).
| Event | Effect | | Event | Effect |
|---|---| |---|---|
| Navigation finished, extension loaded | Document becomes **judgeable** and its clock starts. If the extension is not loaded yet, it becomes judgeable when the extension loads while the page lives. | | Navigation finished, extension loaded | The document becomes judgeable and its clock starts. If the extension loads later, the clock starts then. |
| Attributed dashboard response (later also `response_sent`, see the companion design) | Healthy. Ends judging for this document. Resets the streak. | | Attributed answer (dashboard refresh) | Healthy. Resets the streak. |
| Response that cannot be tied to one document | The document is not judgeable (neither outcome provable). | | Document ends: new navigation or tab close | If unanswered and alive at least **30 s**: breakage. Shorter silence is not judged (it may be a slow wake). |
| Document ends by a new navigation or tab close | Judge now. | | Web content process terminated | Not judged. The tab's next navigation gets `reason=tab_crash`. |
| App suspends (iOS to background, macOS sleep) or macOS terminates | Judge every pending judgeable document now, once. The processes still run at this moment; nothing is judged while suspended. | | Extension signals `cpmStage` = `config_unavailable` or `settings_missing` | Breakage at once, no lifetime limit. |
| Web content process terminated | **Not judged.** The silence is the crash's, not the extension's. The tab's next navigation is reported with `reason=tab_crash`. | | Answer that cannot be tied to one document | That document is not judged. |
| Extension signals `cpmStage` = `config_unavailable` or `settings_missing` | Breakage at once (`ended_by=explicit_signal`), no lifetime limit. | | App suspended | Nothing is judged while suspended. |
| Extension reload | Existing rule: evidence from the replaced context is discarded. |
A document is reported only if it lived at least **30 s** after it became judgeable (`minimum lifetime`). A shorter silence is ambiguous: it may be a slow wake. The 30 s value follows WebKit's unload timer and the late-answer data above; it is a constant in the first release. Shorter and longer lifetimes are told apart in analysis by the `lifetime` parameter. Judged documents are never re-judged. Measurement state never expires (the old five-minute drop is gone).
A judged document is never judged again and never retracted: an answer after the report changes nothing. Documents whose process or app ends without a suspension event (kill, crash of the app) are lost; this is the known bias, now limited to the time between the last suspension and the kill. **Streak:** consecutive judged breakages since the last healthy answer, any tab, in memory. At 3 the persistent pixel fires once for that streak.
Measurement state does not expire after five minutes. The old five-minute lifetime stays only for the restart observation, which is unrelated.
### Persistence
The monitor keeps a streak: the number of consecutive judged-broken documents since the last healthy one (any tab). When it reaches **3** the persistent pixel fires once for that streak. A healthy answer resets the streak. The streak lives in memory for the process lifetime. A relaunch resets it (see open questions).
`tabs` on the persistent pixel records whether the streak's documents were in one tab or several. This keeps the cross-tab distinction the earlier analysis found important (cross-tab permanent was the figure used against the 1% target).
### Pixels ### Pixels
| Pixel | Trigger | Frequency | | Pixel | Trigger | Frequency |
|---|---|---| |---|---|---|
| `web_extension_cpm_breakage` | A document is judged broken. Sent at that moment, once per document. | Daily + count (`dailyAndCount`) | | `web_extension_cpm_breakage` | A document is judged broken. | Daily + count |
| `web_extension_cpm_breakage_persistent` | A streak reaches 3 documents. Once per streak. | Daily + count (`dailyAndCount`) | | `web_extension_cpm_breakage_persistent` | The streak reaches 3. Once per streak. | Daily + count |
`_daily` estimates installs per day, `_count` counts events. Both metadata sets follow the existing episode pixel on each platform: `appVersion` on both; macOS also `pixelSource` and `channel`, suffix `daily_count`; iOS keeps its existing suffix set. The macOS definition must match what is sent (item 6 above). `_daily` is installs per day; `_count` is events. Metadata follows the existing episode pixel on each platform: `appVersion` on both; macOS also `pixelSource`, `channel`, suffix `daily_count`; iOS keeps its suffix set. The macOS definition must match what is sent.
`web_extension_cpm_breakage` parameters: `web_extension_cpm_breakage` parameters:
| Parameter | Values | | Parameter | Values |
|---|---| |---|---|
| `reason` | `session_restoration`, `tab_crash`, `extension_reload`, `other`. Context of the document's navigation. Same values as the episode pixel. | | `reason` | `session_restoration`, `tab_crash`, `extension_reload`, `other` (as in the episode pixel). |
| `background` | `woken` (the extension's background view was created during this navigation), `warm` (a background view existed and none was created), `unknown` (no background view known). Comes from the manager's `didCreateBackgroundWebView` delegate callback, correlated to the navigation, not from load duration. | | `background` | `woken` (a background view was created during this navigation), `warm` (views were seen, none created for it), `unknown` (no background view seen yet). From the manager's `didCreateBackgroundWebView` callback. |
| `ended_by` | `navigation`, `tab_closed`, `app_suspended`, `explicit_signal`. |
| `lifetime` | Time from judgeable to judged: `30s_to_2m`, `2m_to_10m`, `over_10m`. Omitted for `explicit_signal`. |
| `streak` | Breakages in a row including this one: `1`, `2`, `3_plus`. |
`web_extension_cpm_breakage_persistent` parameters: `web_extension_cpm_breakage_persistent` has no parameters. `last_stage` is added by the companion design.
| Parameter | Values | Dashboard: persistent installs / DAU from `breakage_persistent_daily` (the success metric); any-breakage installs / DAU from `breakage_daily`; breakages per day from `breakage_count` by `reason` and `background`.
|---|---|
| `tabs` | `one` or `many`: whether the streak's documents were in one tab or several. |
The `last_stage` parameter for wake diagnosis is added by [[cpm-wake-init-detection-tech-design-2026-11-10]], not here. No URL, document, tab or process identifier is sent. ### Removed in the same PR
### The metric set for the dashboard The episode pixel `web_extension_cpm_episode` with its timers and episode state, `web_extension_cpm_background_load_restart` (and its observation), and `web_extension_cpm_network_process_recovery_applied`: definitions, events and firing code. The recovery behavior (error-6 restart, network recovery) stays; only the pixels go.
| Metric | Source | Meaning | ### First attempt (`e6419bb910`): defects fixed here
|---|---|---|
| Installs with persistent breakage / DAU | `breakage_persistent_daily`, split by `tabs` | The success metric (target below 1%). |
| Installs with any breakage / DAU | `breakage_daily` | Upper bound; includes single-page failures. |
| Breakages per day | `breakage_count`, split by `reason`, `background`, `ended_by` | Volume and mix, for diagnosis. |
Everything is per platform and app version. Rates use the existing DAU series. Measurements older than five minutes dropped; no minimum lifetime; tab process crash counted; `background` from load duration and one app-wide signal; macOS definition mismatch; `navigationWaitContexts` leak (the wait-context set is deleted); no `cpmStage` test.
### Where it lives in the code
- `CPMMessagingHealthMonitor` (existing, per-tab document state already tracks response, judgeable and crash state) gains a per-document record independent of the timeout measurement: when it became judgeable, whether it was judged, the navigation kind. Judging and the streak are small additions to existing handlers; the correlation logic is reused, not duplicated.
- New event `CPMMessagingHealthEvent.backgroundViewCreated`, sent by the manager from `didCreateBackgroundWebView`. It replaces `backgroundLoaded(wasWoken:)` from the first attempt.
- macOS also sends the existing suspension event on termination (`applicationWillTerminate`); iOS and macOS sleep already send `interruptionBegan`.
- `AutoconsentWebExtensionMessageHandler` already separates the `cpmStage` signals; they get a test.
- `CPMMessagingBreakageReport` and the two pixel events in `WebExtensionPixelFiring`. Definitions in both platforms' `web_extensions.json5` and `params_dictionary.json5`.
### PR stack ### PR stack
| PR | Branch | Content | State, 10 Nov | | PR | Content | State |
|---|---|---|---| |---|---|---|
| 1 | `alex/cpm-episode-metrics`, [#8025](https://github.com/duckduckgo/apple-browsers/pull/8025) | Remove ineffective recovery fixes, their flags and pixels | pushed, in review | | 1, [#8025](https://github.com/duckduckgo/apple-browsers/pull/8025) | Remove ineffective recovery fixes and flags | pushed, in review |
| 2 | `alex/cpm-pixel-cleanup` (`f04c93730f`) | Remove the diagnostics, trim the episode pixel to five parameters. No new pixel, no detection change. Needs the Danger label (existing pixel parameters change). | local; 1104 package tests pass, SwiftLint clean on the changed core files; app builds and pixel validation not run; not pushed | | 2, `alex/cpm-pixel-cleanup` | Remove diagnostics, trim the episode pixel | local, tests pass, not pushed |
| 3 | new, on top of 2 | This design: two pixels, document-end judging, streak | not started | | 3, `alex/cpm-breakage-metric` | This design, two commits: new pixels; removal of the old pixels | in progress |
| 4 | new, on top of 3 | Init progress marks, `last_stage` ([[cpm-wake-init-detection-tech-design-2026-11-10]]) plus extension [#3727](https://github.com/duckduckgo/duckduckgo-privacy-extension/pull/3727) | native part drafted in `alex/cpm-init-diagnostics` (`adb28e643d`), needs the same fixes | | 4 | Marks and `last_stage` ([[cpm-wake-init-detection-tech-design-2026-11-10]]) plus extension #3727 | not started |
| 5 | later | One release after PR 3: remove the episode pixel with its timers and states, and the pixels `web_extension_cpm_background_load_restart` and `web_extension_cpm_network_process_recovery_applied` (their definitions and firing code; the recovery behavior stays). After this the only CPM pixels are the two new ones. | after comparison |
Old branches `alex/cpm-breakage-pixel` and `alex/cpm-init-diagnostics` stay as sources only.
### Pros
- Measures the user-facing fact (this page never got an answer), not an elapsed time.
- A late answer is never a failure, which removes the 6% late-answer error class and its tail.
- Detected breakages are sent at once, so closing the app loses only documents that were never judged.
- Persistent breakage has its own daily pixel: the success metric is one series, not a join.
- Additive: the episode pixel and the recovery logic keep working, so the new numbers can be validated against the old.
### Cons ### Cons
- Pages that live until a kill are not judged. Persistent breakage that a user never ends (leaves the page open for hours) appears only at suspension. - Documents alive when the app exits or suspends are never judged; breakage on pages left open is missed.
- 30 s minimum lifetime hides breakage on short pages. If most breakage is on short pages the metric underestimates; `lifetime` shows how much sits near the limit. - 30 s hides breakage on short pages.
- A page-based streak does not equal the old "no answer for five minutes". The two series will differ, and the comparison needs interpretation. - The series is new: no overlap with the episode series, so comparison is by app version against the previous derived number.
- The streak resets on relaunch; iOS terminates apps often, so persistent breakage across launches is undercounted. - Streak resets on relaunch.
### Risks ### Risks
- **Pixel volume.** A user with persistent breakage fires one count request per page. Mitigation: soft cap of 20 `breakage` count requests per process; the persistent pixel is unaffected by the cap. Volume to be estimated from `web_extension_cpm_episode` counts before release. - A page that works but whose answer is not attributed counts as breakage (`other_tab_response` was 37–53% of outcomes). Documents with an unattributable answer are not judged.
- **Healthy evidence that is missing in a working page** (dashboard refresh not delivered, wrong attribution). Counted as breakage. The earlier analysis found `other_tab_response` at 37–53% of outcomes, an attribution problem. Mitigation: documents with an unattributable response are not judged, and `unknown` attribution is visible only locally for now. Compare with the episode series. - Pixel volume for a persistently broken install: one count request per page.
- **Judging at suspension** assumes silence for 30 s before suspension means breakage. A wake that began just before suspension could be reported. Mitigation: the lifetime limit; the suspension case is separated by `ended_by`. - The Debug "Simulate CPM Messaging Breakage" switch must keep hiding answers.
- **Simulation switch.** The Debug "Simulate CPM Messaging Breakage" switch hides dashboard responses from the monitor and must keep doing so for both detectors.
## Testing ## Testing
- Unit tests for each rule in the table: late answer before document end is healthy; unanswered document shorter than 30 s is not reported and does not touch the streak; unanswered longer document is reported on navigation, tab close, suspension; process termination is not judged and tags the next navigation; judged once; extension not loaded at finish becomes judgeable on load; unattributable response; explicit signals; streak reaches 3 once, resets on a healthy answer; `tabs` value; `background` from the view-created event inside and outside the navigation window; simulation switch hides answers; extension reload evidence. - Unit tests per row of the table, streak at 3 once and reset, `reason` values, `background` values, simulation switch, `cpmStage` parsing, extension reload.
- Handler test for `cpmStage` parsing; test for `navigationWaitContexts` after a context replacement. - Pixel definitions pass both validators; parameters match what is sent.
- Pixel definitions: schema validators of both platforms in CI; parameter lists match what is sent (compare the macOS definition against the event). - Manual: [[CPM Breakage Testing Steps]] with the Debug switch.
- Manual: updated steps in [[CPM Breakage Testing Steps]] (Debug simulation switch: load a page for 35 s without an answer, navigate away, expect one `breakage`; three in a row, expect `persistent`). - Real WebKit (CPM repro harness, [PR #7378](https://github.com/duckduckgo/apple-browsers/pull/7378)): not run.
- Real WebKit: the CPM repro harness ([PR #7378](https://github.com/duckduckgo/apple-browsers/pull/7378)) with the injected error 6 and idle-wake fixtures to see both pixels with the expected values. Not run yet.
## Additional Considerations (if applicable) ## Additional Considerations (if applicable)
**Privacy** **Privacy:** two new pixel names with enumerated parameters; no URLs, identifiers or page content. Privacy Triage required, not requested yet.
Two new pixel names with enumerated, bucketed parameters. No URLs, page content, tab or document identifiers, or process IDs. The streak is a local integer that is never sent; only `streak` as a bucket. Because the pixel names are new, Privacy Triage is required before release. Not requested yet. Combined distinguishability of `reason`, `background`, `ended_by`, `lifetime`, `streak` and the platform metadata is small (five low-cardinality enums). **Security:** no new private WebKit selectors.
**Security** **Site Breakage:** none; the detector only observes events and recovery is unchanged.
No new private WebKit selectors. `didCreateBackgroundWebView` is already implemented in the manager. **Experimentation:** none.
**Site Breakage** **Operational:** pixel expiry 2027-01-15 as existing definitions. Danger label for the removed pixels. Data checks: `metrics.pixels_aggregation` and `metrics._pixels_new` (`pixel_id = 'web'`).
None intended. The detector only observes events; it sends no messages and changes no recovery behavior. Existing recovery (error-6 restart, network recovery, upgrade before load) is unchanged. **Localization / Internationalization:** none.
**Experimentation**
No experiment. The comparison is observational: the new and old series for the same versions during the overlap release.
**Operational**
- Overlap: ship PR 3 next to the episode pixel for one release; compare "installs with persistent breakage / DAU" with the existing permanent-failure series by platform. Expected: new is lower where late answers dominated, higher where the episode never reached its deadline. Remove the episode pixel (PR 5) when the difference is explained.
- Pixel expiry follows the existing definitions (2027-01-15).
- Add the three metrics to the health indicators dashboard. Data checks use ClickHouse: pixel counts from `metrics.pixels_aggregation` (`pixel_id = 'web'`), parameters from `metrics._pixels_new` (`pixel_id = 'web'`, one day).
- No remote kill switch is proposed. If volume surprises, the soft cap and the next release are the lever. **Question for reviewers:** is a feature flag required for a new pixel here?
**Localization / Internationalization**
None.
## Open questions
1. Minimum lifetime 30 s and streak 3: acceptable as first constants, or tune from the episode data first?
2. Persist the streak across launches (a local counter)? It makes persistent breakage visible across iOS terminations at the price of local state.
3. Flag or no flag for a new pixel.
4. Judge at suspension: accept, or only at document end?
5. Soft cap value.
@@ -110,7 +110,7 @@ Open:
**Что неверно в `e6419bb910`** (проверено по коду): тихие замеры старше 5 минут сбрасываются (`discardExpiredState`); нет минимального времени жизни страницы; падение процесса вкладки засчитывается поломкой; `background` берётся из «загрузка ≥ 1 с» и одного общего сигнала; страницы, живущие до выхода приложения, не оцениваются; определение macOS без `pixelSource` и `channel`, суффиксы от iOS; утечка `navigationWaitContexts`; нет теста на разбор `cpmStage`. **Что неверно в `e6419bb910`** (проверено по коду): тихие замеры старше 5 минут сбрасываются (`discardExpiredState`); нет минимального времени жизни страницы; падение процесса вкладки засчитывается поломкой; `background` берётся из «загрузка ≥ 1 с» и одного общего сигнала; страницы, живущие до выхода приложения, не оцениваются; определение macOS без `pixelSource` и `channel`, суффиксы от iOS; утечка `navigationWaitContexts`; нет теста на разбор `cpmStage`.
**Tech design (черновики, ревьюер не назначен):** **Tech design (черновики, ревьюер не назначен):**
- [[cpm-permanent-breakage-metric-tech-design-2026-11-10]]: два пикселя (`web_extension_cpm_breakage`, `web_extension_cpm_breakage_persistent`), оценка страницы по концу документа, минимум 30 с жизни, серия из 3 подряд, суждение при приостановке приложения, `background` по `didCreateBackgroundWebView`. Работает рядом с эпизодным пикселем один релиз. Конечное состояние: из CPM пикселей остаются только эти два; эпизодный, `background_load_restart` и `network_process_recovery_applied` удаляются в PR 5. - [[cpm-permanent-breakage-metric-tech-design-2026-11-10]]: два пикселя (`web_extension_cpm_breakage` с `reason` и `background`, `web_extension_cpm_breakage_persistent` без параметров), оценка страницы по концу документа, минимум 30 с жизни, серия из 3 подряд. Старые пиксели (эпизодный, `background_load_restart`, `network_process_recovery_applied`) удаляются в том же PR 3 вторым коммитом, без задержки на релиз; остаются только эти два.
- [[cpm-wake-init-detection-tech-design-2026-11-10]]: метки `cpmInitProgress` из расширения, `last_stage` в пикселе, `response_sent` как здоровый сигнал, изменение расширения (PR #3727, ветка `alex/cpm-init-progress`; метки `page_init_sent`, `config_read`, `settings_checked`, `site_checked` в рабочем дереве без коммита и без запуска тестов). - [[cpm-wake-init-detection-tech-design-2026-11-10]]: метки `cpmInitProgress` из расширения, `last_stage` в пикселе, `response_sent` как здоровый сигнал, изменение расширения (PR #3727, ветка `alex/cpm-init-progress`; метки `page_init_sent`, `config_read`, `settings_checked`, `site_checked` в рабочем дереве без коммита и без запуска тестов).
Открытые вопросы для ревью: порог 30 с и серия 3; сохранять серию между запусками; считать ли `response_sent` здоровым до проверки без слушателя; шесть меток или меньше; нужен ли флаг для нового пикселя. Открытые вопросы для ревью: порог 30 с и серия 3; сохранять серию между запусками; считать ли `response_sent` здоровым до проверки без слушателя; шесть меток или меньше; нужен ли флаг для нового пикселя.
@@ -74,7 +74,7 @@ Marks are best effort: they carry `{ tabId, url, stage }`, never wait and never
- `AutoconsentWebExtensionMessageHandler` gets a `cpmInitProgress` method. Unknown stage names are ignored without an error, so a newer extension cannot make an older app answer with failures. - `AutoconsentWebExtensionMessageHandler` gets a `cpmInitProgress` method. Unknown stage names are ignored without an error, so a newer extension cannot make an older app answer with failures.
- `CPMMessagingHealthEvent.initProgress` carries the mark; the monitor stores `lastStage` per document and clears it on a new navigation, a cancelled navigation and an extension reload. - `CPMMessagingHealthEvent.initProgress` carries the mark; the monitor stores `lastStage` per document and clears it on a new navigation, a cancelled navigation and an extension reload.
- `response_sent` counts as healthy evidence for the document, like a dashboard response. `response_failed` judges a pending document at once (`ended_by=explicit_signal`). Healthy evidence is either the dashboard refresh or `response_sent`; the open question below asks whether `response_sent` should count before it is verified. - `response_sent` counts as healthy evidence for the document, like a dashboard response. `response_failed` judges a pending document at once. Healthy evidence is either the dashboard refresh or `response_sent`; the open question below asks whether `response_sent` should count before it is verified.
- The Debug "Simulate CPM Messaging Breakage" switch hides marks as well as dashboard responses. - The Debug "Simulate CPM Messaging Breakage" switch hides marks as well as dashboard responses.
### Pixel change ### Pixel change