Files
obsidian-vault/work/projects/cpm-web-extension-code-guide-2026-10-09.md
T

521 lines
104 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: CPM Web Extension — карта кода по worktree и строкам
date: '2026-10-09'
tags:
- work
- cpm
- webkit
---
> **Актуально — 19 октября 2026, uninstall fix опубликован.** [#7379](https://github.com/duckduckgo/apple-browsers/pull/7379): production **`e970e8c90e`**, 6 commits / 22 paths; отдельный review-only harness **`e380cf98f7`**, 12 paths вне main. Удаление активного CPM context отменяет pending late-document recovery; удаление старой upgrade-версии и ошибка unload сохраняют recovery. **190 unit PASS**, red-before-fix 2 PASS / 1 FAIL, signed macOS и iOS Simulator builds PASS, strict lint 11 / 0. На новом app head проверено на одних подписанных бинарниках: **ON 8 PASS → OFF 6 ожидаемых functional FAIL → ON 8 PASS**, без skips/expected failures; все app/XCTest SHA256 совпали. CI `e970e8c90e`: **48 SUCCESS / 21 SKIPPED**, pending/failures нет. PR description и reviewer flag steps актуализированы с сохранением ручных правок. Формы оставлены в прежнем виде; late reload flag default disabled. Предыдущие heads/results ниже — история.
> **Актуально — 19 октября 2026, полный повторный review #7379.** Проверен локальный `5708197362` против #7376: 22 changed paths, **187 существующих тестов PASS + 1 новый probe FAIL**. Новый P2: uninstall активного CPM context не отменяет pending late-document recovery. Общий launch→navigation deadline и привязка recovery pixel к точной WKNavigation исправлены локально и прошли повторные регрессии; усиление защиты форм явно отложено пользователем, исходная проверка сохранена за default-disabled флагом. В GitHub пока `08866c7eda` (CI: 48 SUCCESS / 21 SKIPPED), новые production commits не опубликованы. Локальный review-only harness `4308751d59` синхронизирован; его новый ON/OFF/ON ещё не запускался. Подробности — в конце; прежние heads/results ниже относятся к истории.
> **Актуально — 16 октября 2026, PR review fixes.** В [#7379](https://github.com/duckduckgo/apple-browsers/pull/7379) проверены оба inline замечания: cancellation уже покрыта, повторные 5 секунд на каждую navigation исправлены общим deadline на startup. Production **`08866c7eda`**, 3 commits / 21 paths; review-only harness **`3f4d755511`**, 12 test-only paths, вне main. Проверки: red-before-fix **2 FAIL**, после исправления **182 unit PASS**, signed macOS build и iOS shared-module build PASS, strict lint **8 / 0**; на одних app/XCTest binaries **ON 8 PASS → OFF 6 ожидаемых functional FAIL → ON 8 PASS**, без skips. Оба review threads resolved; Danger 0 failures, только PR-size advisory; flag tracking links добавлены, defaults не менялись. Пользовательские правки и reviewer steps сохранены. Подробности этого review — в конце. Более ранние heads/results ниже сохранены как история.
> **Актуально — 16 октября 2026, reviewer ON → OFF → ON.** В [#7379](https://github.com/duckduckgo/apple-browsers/pull/7379) восстановлена проверка выключенных production flags. Review-only branch **`codex/cpm-fix-init-loss-integration-tests` / `543e37164b`** содержит два schemes: **CPM Integration Tests** → 8 PASS; **CPM Integration Tests - Fixes Disabled** → ровно 6 functional FAIL; затем normal scheme → снова 8 PASS. Проверено на одних подписанных app/XCTest binaries, без пересборки и изменения Swift code. Production PR **`da126120a9`**, 2 commits / 21 files, не менялся; harness остаётся вне main (2 test-only commits / 12 paths). Пользовательские правки PR сохранены. Подробные результаты и artifacts — в конце; прежние односхемные steps и harness head `6e4e39e67c` относятся к истории.
> **Актуально — 16 октября 2026, harness вынесен из PR.** [#7379](https://github.com/duckduckgo/apple-browsers/pull/7379): **`alex/cpm-fix-init-loss` / `da126120a9`**, база #7376, **2 коммита / 21 файл**, production fixes + 18 новых shared unit regressions. Все local-only app tests, Xcode entries и DEBUG app hooks — только в **[`codex/cpm-fix-init-loss-integration-tests`](https://github.com/duckduckgo/apple-browsers/tree/codex/cpm-fix-init-loss-integration-tests) / `6e4e39e67c`**, один дочерний commit от PR head; **эта ветка не идёт в main**. Проверено: production app build PASS, unit **179 PASS**, harness app **8/8 PASS**, strict lint **19+8 / 0**. Reviewer steps в PR обновлены и пользовательские правки сохранены. Ниже сохранена датированная история; прежние 3 commits / 32 files и app harness в PR больше не актуальны.
> **Актуальный итог — 16 октября 2026, после force push.** [PR #7379](https://github.com/duckduckgo/apple-browsers/pull/7379) теперь поверх **#7376** (`alex/cpm-messaging-readiness-telemetry`), head **`aec5be9a83`**, ровно **3 коммита / 32 файла**. Старый worktree `cpm-fix-init-loss` удалён; текущая директория `alex/cpm-fix-p1-p2` теперь на ветке `alex/cpm-fix-init-loss`. Checkpoint **`367759cbc1`** и backup старой истории сохранены. Проверка через production feature flags **без создания/переключения коммитов**: signed app **8/8 PASS ON**, **6/6 функциональных FAIL OFF**, unit **179 PASS**, strict lint **27 / 0**, pixel validators обеих платформ PASS. Новый late-reload flag **default disabled**. Описание PR/шаги и local-only `to-be-disabled before merge` annotations обновлены; GitHub CI ещё не объявляется green. Подробная финальная сверка — в конце документа. **Ниже сохранена датированная история: прежние «не закоммичено», старые head/base и over-budget limitation не являются текущим статусом.**
> **App integration update — 16 октября 2026, 01:48 Бишкек.** В `cpm-e2e-init` добавлены 20 методов настоящего app-hosted CPM suite: финальные **20/20 PASS, 0 failures/skips/expected failures**, группы 18+1+1 на одной signed build. Схема `CPM Integration Tests`; файлы/guards/запуск/ограничения — §5 ниже, полный реестр и история — [[cpm-status-2026-10-15#Финальный app-hosted итог — 16 октября 2026]]. Без commits. Карта остальных worktree/PR ниже сохраняет свою датированную сверку 15 октября.
# CPM Web Extension — карта кода по worktree и строкам
> **Актуальная карта — 15 октября 2026.** Текущий статус PR/CI, данные и все 14 новых P5/P6 результатов: [[cpm-status-2026-10-15]] (§2, §5, §10–12). Датированные исходные разделы 1–4 ниже сохранены; их «ничего не закоммичено», номера строк и общий worktree описывают 9 октября, а не текущее состояние.
## 0. Текущая карта worktree и запусков
Корень: `/Users/admin/DuckDuckGo/apple-browsers.git/`. Проверены `git worktree list`, HEAD и наличие тестовых файлов. **Единого worktree со всеми suites нет.** Общая база в истории не включает local dirty/untracked файлы; особенно это относится к remaining-failures и scratch init-progress E2E.
| Worktree | Ветка / текущий HEAD | Назначение и границы |
|---|---|---|
| `alex/cpm-web-extension-remaining-failures` | та же; `a0717cf098cd` | Основное расследование без A/B watchdog: P1 (3 wake), P2 (9 load failure), P3 (2 cache/cleanup), новые P5/P6 (12 lifecycle + 2 flag). Все 28 repro opt-in; `CPMRepro/` untracked. Production dirty telemetry — прежняя копия, не переносить вместе с тестами |
| `cpm-repro-harness` | `alex/cpm-repro-harness`; `dfb96279196d` | PR #7378: базовый harness, быстрые CI-контроли, медленные сценарии и raw WebKit characterization; без новых P5/P6 |
| `cpm-fix-init-loss` — историческая строка; worktree удалён 16 Oct | `alex/cpm-fix-init-loss`; `7ad771e227bb` | PR #7379: A, первые init при install/upgrade mid-session; красное/зелёное доказательство отдельно от remaining-failures |
| **`alex/cpm-fix-p1-p2` — актуально 19 Oct** | **`alex/cpm-fix-init-loss`; local/remote `e970e8c90e`** | PR #7379 / база #7376: 6 commits / 22 paths, 29 новых shared tests, 190 PASS. Launch deadline, WKNavigation attribution и uninstall/preflight race исправлены и опубликованы; формы отложены, late reload flag default disabled; builds/lint PASS |
| — (отдельная ветка, без своего worktree) | **`codex/cpm-fix-init-loss-integration-tests`; local/remote `e380cf98f7`** | Синхронизирована с production `e970e8c90e`, README: 29 новых shared tests; 8 app methods, ON/OFF schemes, PBX + 3 DEBUG app hooks; 12 paths вне main. На одних signed binaries ON 8 PASS → OFF 6 functional FAIL → ON 8 PASS; SHA256 совпали |
| `cpm-fix-network-crash` | `alex/cpm-fix-network-crash`; local `f1085846e7ff` | B поверх A; 5 network/termination сценариев в FullFailure + 11 watchdog unit tests. Remote PR #7381 остаётся на `39f94e12eaa4`, красный Non-Sandbox; локальный fix не запушен |
| `cpm-fix-pixels` | `alex/cpm-fix-pixels`; `d6d800846d1f` | A/B + PR0, измерение `first_load_wait`/`background_restart`; отдельный PR не зарегистрирован в текущем статусе |
| `alex/cpm-messaging-readiness-telemetry` | та же; `619869ee84c6` | PR0 #7376: readiness/failure/recovery параметры, CI без failure/pending, review required |
| `cpm-readiness-baseline` | `alex/cpm-readiness-baseline`; `99ecf8b12787` | #7384 поверх PR0: baseline readiness; Danger=FAILURE |
| `cpm-init-progress-native` | `alex/cpm-init-progress-native`; `46f25175c7c4` | #7385 поверх baseline: `init_stage`, 11 CPMInitProgress unit tests; Danger=FAILURE. Нужен JS privacy-extension#3727 |
| `cpm-e2e-init` | `scratch/cpm-e2e-init`; `46f25175c7c4` (HEAD сохранён, без новых commits) | Прежний scratch native init-progress/SPM harness плюс новые **20 app-hosted XCTest methods** в `macOS/IntegrationTests/CPM/` и схема `CPM Integration Tests`; результаты и ограничения — §13 status. Наличие HEAD, совпадающего с #7385, не означает наличие untracked suites в PR |
| `alex/cpm-web-extension-ready-handshake` | та же; `a0717cf098cd` | Старый объединённый локальный A/B harness и slow-wake-with-fixes; архив сравнения. Текущие PR A/B нарезаны в worktree выше |
| `cpm-stable-base-url` | `alex/cpm-stable-base-url`; `570c2f8ef479` | Старый M2 experiment: меняет baseURL всем extensions, не доведён и не прогнан через нужный integration harness. Прежнее «production-ready» в старых планах не является актуальным итогом |
CI статусы — read-only snapshot GitHub 15 октября, 17:26 UTC. Подробные head/base/checks и все оговорки — основной статус; локальные воспроизведения не дают зелёного CI незапушенному коду.
### 0.1. Файлы remaining-failures и полный охват
Путь тестов — `SharedPackages/WebExtensions/Tests/WebExtensionsTests/CPMRepro/`.
| Файл | Что искать |
|---|---|
| `CPMReproLifecycleHypothesisTests.swift:30` | 12 методов: nil/previous controller, startup healthy control, source-linked shared-window close + unrelated-window control, forgotten live tab, listener metadata, long documents/dashboard loss, host main-actor stall |
| `CPMReproProductionFlagTests.swift:30` | 2 метода: главный flag true→false→true даёт P6; true→true не меняет manager; embedded-only cycle сохраняет controller и работает после explicit ready wait |
| `CPMReproBackgroundLoadFailureTests.swift:31` | 9 методов P2: ≥30-секундный медленный запуск, 10-секундный контроль, file/script/bindings ошибки и четыре отрицательных process/load проверки |
| `CPMReproExtensionLoadCleanupTests.swift:36` | 2 метода P3: relaunch и reload при большом Network cache и смене baseURL |
| `CPMReproWakeExperimentTests.swift:25` | 3 метода P1: 5/12-секундный wake и сравнение readiness signals |
| `CPMReproTestCase.swift:37` | Базовый opt-in `CPM_REPRO=1`, Options, cleanup. `existingTabs` (68) предоставляет сохранённые view до load нового context |
| `CPMReproHarness.swift:258,269,296` | open/close additional window через production eventsListener; adopt existing view. `:175` watcher без strong reference через await; `:365` content marks |
| `CPMReproTabs.swift:29,148,165` | Реальные WKWebView, navigation gate/monitor callbacks, window/tab delegates и multiple-window provider |
| `CPMReproDelayedResource.swift:26` | Реальный 35-секундный image subresource; URL уникален на каждую навигацию |
| `CPMReproPrelude.swift` | DOM init/initResp marks, trace всех autoconsent payloads, startup/listener test seams |
| `CPMReproSupport.swift:43,92,175,213` | Pixel recorder; optional native-method suppression; temporary instrumented extension copy; WebKit process SPI |
**Результаты:** все 14 новых методов P5/P6 имеют pass по нескольким запускам; последние обычные tests — 478 XCTest / 28 skipped / 0 failures (10.469 с), strict SwiftLint восьми изменённых файлов — 0. P1/P2/P3 в этом продолжении заново не запускались. Таблица всех 14, начальные красные результаты, log paths и SHA-256 — основной статус §12.
### 0.2. Browser → WebKit paths
**P5:** `MainWindowController.windowWillClose` (611–640) фильтрует сохраняемые tabs только для явных `didCloseTab`, затем unregister и `didCloseWindow`. `MainWindowController+WKWebExtensionWindow.tabs(for:)` (31) всё ещё возвращает loaded pinned + regular tabs. `WindowControllersManager.unregister` (178) не очищает closing-window collections. WebKit внутри `WebExtensionContext::didCloseWindow` сам вызывает `didCloseTab` для каждого delegate tab; тот попадает в `forgetTab`. `runtimeSendMessage` возвращает tab-not-found **до** wake. Проверены текущий browser worktree и macOS tag `1.210.0+macos`; shared-window effect исполнен на системном WebKit.
**P6:** `AppDelegate.setupWebExtensions/initializeWebExtensions` (2153–2166, 2209–2221) и shared `FeatureFlag/WebExtensionFeatureFlagHandler.swift`: main disable → uninstall + clear references; enable при manager=nil → новый manager/controller без пересоздания старых WKWebView. `WebExtensionTab::webView` отвергает неправильный controller. Только test-controlled main-flag cycle доказан; исторические данные не подтверждают такой production transition. Default main flag enabled; повторный true и embedded-only toggle не заменяют controller.
WebKit source checkout: `/Users/admin/Downloads/WebKit_sparse`, HEAD `99051d5d08cd9f19ec76ce599f7539d070b1ae09`:
- `Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionContextCocoa.mm:1351,1419`;
- `Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIRuntimeCocoa.mm:129`;
- `Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionTabCocoa.mm:339`.
Это карта source contract и system-WebKit проверки, не утверждение о полном совпадении checkout с shipped binary. В SPM исполняются production shared classes и WebKit, а app-side host callbacks/delegates реализованы harness.
### 0.3. Команды и предусловия
Из `alex/cpm-web-extension-remaining-failures/SharedPackages/WebExtensions`:
```sh
CPM_REPRO=1 CPM_REPRO_P5_P6=1 swift test --disable-automatic-resolution --filter 'CPMReproLifecycleHypothesisTests|CPMReproProductionFlagTests'
CPM_REPRO=1 CPM_REPRO_P5_P6=1 swift test --disable-automatic-resolution --filter 'CPMReproProductionFlagTests|CPMReproLifecycleHypothesisTests/testClosing'
CPM_REPRO=1 swift test --disable-automatic-resolution --filter CPMReproWakeExperimentTests
CPM_REPRO=1 swift test --disable-automatic-resolution --filter CPMReproBackgroundLoadFailureTests
swift test --disable-automatic-resolution --skip-build
```
Первая команда всех 14 дана для повторения: в текущей сессии 14 методов проверялись раздельными группами. `--skip-build` применим после актуальной сборки. Для P3 нужны `CPM_REPRO_CACHE_URL` (server `/fill.html?n=N`) и `CPM_REPRO_CACHE_ENTRIES` (default 20000; заполнение порядка 10 минут), затем filter `CPMReproExtensionLoadCleanupTests`.
Выделенный PR-harness в `cpm-repro-harness` имеет быстрые tests без общего opt-in и `requireLocalRun` для тяжёлых; raw WebKit characterization требует также `CPM_REPRO_WEBKIT=1`. Новые P5/P6 живут только в remaining-failures и в PR-harness ещё не переносились.
Environment P5/P6: macOS 26.7.1 (25G241), Xcode 27.1 (27A9275), embedded bundle 2026.9.29. M4 в этих 14 tests выключен, gate включён, Network PID прогрет до context-load; production bundle zip не правился, temporary copy содержит trace/DOM instrumentation. Нативные flags в production не переключались.
---
**Архив карты на 9 октября.** Все следующие разделы сохранены для старых source line references и истории экспериментов; текущие точки входа и статус — выше.
Контекст: [[cpm-web-extension-breakage-session-summary-2026-10-07]] (раздел 10), [[cpm-web-extension-repro-and-fix-2026-10-08]]. Номера строк — на 9 октября. **Ничего не закоммичено.** Корень всех worktree — `/Users/admin/DuckDuckGo/apple-browsers.git/`; пути ниже даны от корня worktree. `SP` = `SharedPackages/WebExtensions/Sources/WebExtensions`, `TP` = `SharedPackages/WebExtensions/Tests/WebExtensionsTests`.
| Worktree | Ветка, база | Что внутри |
|---|---|---|
| `alex/cpm-web-extension-ready-handshake` | та же, `a0717cf098` | Фиксы + основной harness |
| `alex/cpm-web-extension-remaining-failures` | та же, `a0717cf098` | Прод-код без фиксов, error 6 телеметрия (старая копия), репро error 6, очистки, медленного пробуждения |
| `alex/cpm-messaging-readiness-telemetry` | та же, `main` `92e09cdbbc` | Телеметрия фазы 1 (включает error 6) — кандидат на первый PR |
| `cpm-stable-base-url` | `alex/cpm-stable-base-url`, старая база `570c2f8ef4` | Эксперимент со стабильным `baseURL` |
## 1. Фиксы — `alex/cpm-web-extension-ready-handshake`
Цепочка: навигация → coordinator спрашивает менеджер → менеджер проверяет watchdog и незавершённый первый старт background → при сбое монитор зовёт перезапуск.
1. `SP/Lifecycle/WebExtensionLifecycleCoordinator.swift:79-93` — `initialLoadWaiter`, его вызывают обе платформы на каждой main-frame навигации. Было: после открытия стартового gate → `nil`. Стало: `:89` зовёт `manager.prepareEmbeddedBackgroundForNavigation()` и ждёт его не дольше `initialLoadTimeout` (5 с, `:91-92`).
2. `SP/Manager/WebExtensionManager+EmbeddedBackground.swift` (новый):
- `:35` `prepareEmbeddedBackgroundForNavigation()` — сначала `watchdog.restartBackgroundIfStale()` (`:36`), потом возвращает waiter, если идёт первый старт background только что установленного extension.
- `:45` `trackEmbeddedBackgroundStartup(identifier:)` — запускает `context.loadBackgroundContent()` (`:49`) как `Task` и держит его в `embeddedBackgroundStartup`, пока не завершится. Зачем: WebKit при первой загрузке нового идентификатора грузит background сразу и теряет `init`, пришедший до выполнения скрипта (317981).
3. `SP/Manager/WebExtensionManager+EmbeddedExtensions.swift:178-180` — после `installationStore.add` для `.embedded` вызывается `trackEmbeddedBackgroundStartup`. Покрывает установку и обновление посреди сессии (на запуске это уже делал стартовый gate).
4. `SP/Manager/WebExtensionManager.swift`:
- `:134-139` — свойства `embeddedBackgroundWatchdog` и `embeddedBackgroundStartup`.
- `:167` — параметр init `isEmbeddedBackgroundRecoveryEnabled` (по умолчанию `{ true }`, к удалённому флагу не подключён).
- `:186` — создание watchdog.
- `:217-221` — `healthMonitor.onEpisodeStarted` → `restartBackgroundIfResponsive(reason:)`: перезапуск по первому сбою.
- `:757` — в делегате `didCreateBackgroundWebView` watchdog получает новую background view.
5. `SP/Lifecycle/EmbeddedBackgroundLivenessWatchdog.swift` (новый):
- `:63` init — все зависимости подменяемые (PID, перезапуск, проба отзывчивости).
- `:76` `backgroundWebViewDidChange` — запоминает view и PID Network-процесса, запускает периодическую проверку.
- `:85` `restartBackgroundIfStale` — PID Network-процесса сменился (или исчез) → перезапуск. Это и есть детектор «Network-процесс упал, worker мёртв».
- `:107` `restartBackgroundIfResponsive` — перезапуск по сбою, только если процесс ответил на `return 1` за 1 с (`:166-170`). Замороженный не трогаем: убийство остановленного процесса давало глухой новый worker.
- `:137` периодическая проверка раз в 1 с; `:156` `_networkProcessIdentifier` (SPI через KVC); `:173-180` перезапуск через `_killWebContentProcessAndResetState` — WebKit поднимает новый worker, и чинятся даже уже открытые документы.
6. `SP/Autoconsent/CPMMessagingHealthMonitor.swift:96-100` — колбэк `onEpisodeStarted`; `:656-658` — вызывается на первом сбое нового эпизода, только для `failureReason == .other` (не session restoration).
7. `SP/Manager/WebExtensionManaging.swift:164-168` — требование протокола; `:176-178` — реализация по умолчанию `{ nil }`, чтобы моки и платформы не ломались.
**Тесты:**
- `TP/EmbeddedBackgroundLivenessWatchdogTests.swift` — 11 unit-тестов (`:48`–`:150`): смена PID, исчезновение Network-процесса, отзывчивый и неотзывчивый процесс, выключатель, `stop()`, периодическая проверка.
- `TP/WebExtensionLifecycleCoordinatorTests.swift:193, :217, :227, :243` — навигация ждёт первый старт; не ждёт, если готов; ожидание ограничено 5 с; при выключенном gate ничего не готовится. Мок — `:667`.
- `TP/CPMMessagingHealthMonitorTests.swift:50, :61, :71` — `onEpisodeStarted` на первом сбое; не на session restoration; снова для нового эпизода.
- **Harness** `TP/CPMRepro/` (запуск: `CPM_REPRO=1 swift test --filter CPMRepro` из `SharedPackages/WebExtensions`):
- `CPMReproSingleTabTests.swift` — 10 тестов одной вкладки: холодный старт `:32`, обновление на запуске `:46`, **установка/обновление посреди сессии `:64`, `:87`** (до фикса терялись), перезапуск `:111`, `:128`, после reload `:146`, `:165`, пробуждение `:187`, падение background `:206`.
- `CPMReproFullFailureTests.swift` — краш Network-процесса `:33`, `:38`, `:55` (без M4); смерть worker'а через SPI `:76` и удаление регистраций `:85` (стоит одну навигацию); не-триггеры `:124`, `:129`, `:136`.
- `CPMReproWebKitTests.swift` — 7 тестов, нужен `CPM_REPRO_WEBKIT=1`: защиты выключены, утверждают, что баги WebKit ещё есть (`:45` потеря `init` при первой загрузке, `:80`/`:87`/`:151` мёртвый worker, `:97` только перезапуск background чинит открытые документы, `:133` сообщение из страницы background не доходит до worker'а).
- `CPMReproWakeExperimentTests.swift:27-28` — **эксперимент, не для коммита**: медленное пробуждение 5 и 12 с с фиксами. Для него в `CPMReproHarness.swift` свойство `storage` сделано не `private`.
## 2. Расследование без фиксов — `alex/cpm-web-extension-remaining-failures`
Прод-код как в релизе; harness перенесён без watchdog, с production-recorder'ом, поэтому сбои дают те же параметры пикселей, что в проде.
- `TP/CPMRepro/CPMReproHarness.swift`: `:51` recorder диагностики; `:191` `contextErrorCodes`; `:196` папка установленного extension; `:203` `prependToBackgroundScript` — дописывает код в начало background-скрипта на диске (WebKit читает его заново на каждом пробуждении); `:212` PID Network-процесса; `:262` `loadRealPage` (для наполнения кэша).
- `TP/CPMRepro/CPMReproBackgroundLoadFailureTests.swift` — **error 6**, 9 тестов, все с проверками:
- `:59` новый процесс background заморожен на 45 с при пробуждении → только `:6`, `bg_load_error_after=25s_to_35s`. Это таймер выгрузки WebKit.
- `:70` 10 с → error 6 нет.
- `:79`, `:89`, `:96` — другие пути к error 6 (нет файлов, скрипт бросает, нет `browser`) — быстрые, `under_5s`; в проде их сигнатур нет.
- `:104`, `:111`, `:118`, `:125` — условия, которые error 6 не дают.
- `TP/CPMRepro/CPMReproExtensionLoadCleanupTests.swift:49, :69` — задержка очистки при смене `baseURL` на перезапуске и reload. Нужен локальный сервер: `CPM_REPRO_CACHE_URL`, `CPM_REPRO_CACHE_ENTRIES` (наполнение ~10 мин).
- `TP/CPMRepro/CPMReproWakeExperimentTests.swift` — **эксперимент, не для коммита**:
- `:27` старт worker'а 5 с на пробуждении → `initialization_failed` с сигнатурой группы 1, затем поздний ответ.
- `:28` 12 с + навигация → `stuck` → M4.
- `:31` сигналы готовности: `isLoading=false` через 0,1 с (бесполезен), completion `loadBackgroundContent` и первое нативное сообщение — ровно на старте worker'а.
- Продовые изменения здесь (`CPMMessagingDiagnostics*`, JSON5, `WebExtensionManager+iOS.swift`, `AppDelegate.swift`) — **старая копия телеметрии error 6**, она целиком перенесена в worktree телеметрии. Отсюда не коммитить.
## 3. Телеметрия фазы 1 — `alex/cpm-messaging-readiness-telemetry`
Только параметры пикселей, поведение детектора не меняется.
1. `SP/Autoconsent/CPMMessagingDiagnostics.swift`:
- `:29` `ApplicationState`, `:38` провайдер состояния приложения в `CPMAppSessionDiagnostics`.
- `:50` `CPMBackgroundReadiness`: `noBackground` / `pending` / `loaded(seconds)` / `failed`.
- `:109-123` новые поля снимка (error 6, `applicationState`, возраст view, готовность, вкладки).
- `:182-188` имена параметров (`bg_load_error_*`, `app_state`, `bg_view_age`, `bg_ready_after`, `open_tabs`, `loading_tabs`).
- `:217-231` сериализация: error 6 — `none`, если ошибки не было, и опускается, если неизвестно; остальные опускаются при `nil`.
- Бакеты: `:248` error 6 (вокруг таймера 30 с), `:258` возраст view, `:269` готовность (вокруг 4 с монитора и 30 с WebKit), `:285` число вкладок.
2. `SP/Autoconsent/CPMBackgroundReadinessObserver.swift` (новый) — откуда берётся `bg_ready_after`:
- `:52` `observe` на создании view: ставит `pending` и на **следующем шаге run loop** (`:56`) зовёт `loadBackgroundContent`. View уже есть, поэтому вызов лишь присоединяется к идущей загрузке и ничего не будит.
- completion → `loaded(секунды от создания view)` или `failed`; ответ для заменённой view игнорируется.
- `:34` загрузчик подменяется в тестах; `:72` `readiness(of:)` — только для текущей view.
3. `SP/Autoconsent/CPMMessagingDiagnosticsRecorder.swift`:
- `:125`, `:141` наблюдатель; `:223` сброс при новом контексте.
- `:311-316` наблюдение включается тем же флагом, что и delegate proxy (`cpmBackgroundDelegateProxy`).
- `:413-415` поля error 6; `:426` состояние приложения; `:430-431` возраст view и готовность; `:435-438` `open_tabs` / `loading_tabs` из `context.openTabs` и `webView(for:).isLoading`.
- `:503-517` `recordContextErrors` — при error 6 фиксирует состояние приложения и секунды от создания view.
4. `SP/Pixels/WebExtensionPixelFiring.swift`:
- `:71`, `:73` — у recovery-событий появился `details` (по умолчанию `nil`, старые вызовы не ломаются).
- `:85` `CPMMessagingRecoveryDetails`: `failed_navigation` (опоздавший ответ) / `later_navigation` / `unattributed` + `:108` бакеты `recovery_after`.
- `:181-188` параметры пикселя.
5. `SP/Autoconsent/CPMMessagingHealthMonitor.swift`:
- `:451-452` неатрибутированный ответ закрывает stuck → `unattributed`.
- `:712-716` обычное восстановление: тот же упавший замер → `failed_navigation`, иначе `later_navigation`.
- `:722` `recoveryDetails` — секунды от первого сбоя эпизода.
6. Приложения: `iOS/DuckDuckGo/WebExtensions/WebExtensionManager+iOS.swift:107-114` (`UIApplication.applicationState`), `macOS/DuckDuckGo/Application/AppDelegate.swift:122-123` (`NSApp.isActive`).
7. JSON5:
- словари `iOS/PixelDefinitions/pixels/params_dictionary.json5` (`:602` recovery, `:619` error 6, `:643` готовность и вкладки) и `macOS/.../params_dictionary.json5` (`:677`, `:694`, `:718`);
- определения — iOS `debug_pixels.json5`, macOS `web_extensions.json5`: по 8 пикселям сбоя и 2 пикселям восстановления.
**Тесты:**
- `TP/CPMMessagingDiagnosticsTests.swift`:
- бакеты `:135`, `:144`, `:149`; полный набор параметров `:154`;
- error 6 `:279`, `:417`;
- готовность: загрузилась `:304`, упала `:327`, заменённая view `:343`, наблюдение выключено `:360`;
- состояние приложения `:377`; вкладки на реальном контроллере с незавершающейся загрузкой `:384`.
- `TP/CPMMessagingHealthMonitorTests.swift`: опоздавший ответ `:1061`, следующая навигация `:1081`, `unattributed` `:1095`, бакеты `:1112`, обновлённый тест параметров `:1119`.
- Проверка на реальном WebKit (harness временно копировался): медленное пробуждение 5 с → `bg_ready_after=pending`, `bg_view_age=4s_to_8s`, затем `recovery_document=failed_navigation`, `recovery_after=under_4s`.
## 4. Стабильный `baseURL` — `cpm-stable-base-url`
- `SP/Loading/WebExtensionLoader.swift:56`, `:93`, `:134`, `:154`, `:158-165` — в `makeContext` выставляется `context.baseURL = webkit-extension://<uniqueIdentifier>/`; если из идентификатора не собрать URL — ошибка `invalidIdentifier`.
- `TP/WebExtensionLoaderDelegateTests.swift:61-94` — 2 теста: URL выставлен до загрузки; неверный идентификатор → загрузка не начинается.
- Открыто: сейчас меняет URL **всем** extension (нужно — только встроенным без `web_accessible_resources`); не прогнан через harness (M4, краш Network-процесса, обновление); старая база.
## 5. App-hosted CPM integration tests — 16 октября 2026
Worktree `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-e2e-init`, branch `scratch/cpm-e2e-init`, HEAD `46f25175c7c42766cab23ea39f447f075c7bdd24`, **без commits**. Новая схема `macOS/DuckDuckGo-macOS.xcodeproj/xcshareddata/xcschemes/CPM Integration Tests.xcscheme` выбирает только две CPM classes, отключает parallel testing и включает `CPM_APP_INTEGRATION=1`. В ней нет Review-profile wipe/pre-action или `killall tests-server`.
| Файл в `macOS/IntegrationTests/CPM/` | Роль |
|---|---|
| `CPMAppIntegrationTests.swift` | 10 deterministic baseline/diagnostics/scope/recovery tests: slow HTTP/native, одна навигация, одна таба, несколько табов одного окна, все окна, M4, dashboard transport, proxy flag |
| `CPMAppKnownFailureIntegrationTests.swift` | 10 characterization methods: actual AppDelegate flags, shared-pin window closure, Network/background crashes, first install, slow idle wake, frozen WebKit load timeout, 20k persistent-cache reload |
| `CPMAppIntegrationHarness.swift` | Реальные AppDelegate/WindowsManager/MainWindowController/Tab, production navigation/monitor/recorder, JSON attachments, owned windows/data stores, bounded cleanup |
| `CPMAppIntegrationSupport.swift` | Реальный macOS native handler/provider/delegate с fault boundary; recorder transport before daily dedup; extraction через production storage; in-memory installation store; test-only PID/background SPI |
| `CPMAppIntegrationBridge.swift` | Instrumentation только распакованной временной extension copy: page initResp mark, реальные runtime messages и scoped drop/release controls |
| `CPMAppFixtureServer.swift` | Owned HTTP loopback, eight-second resource delay, cacheable 2KiB resources with unique URLs |
| `README.md` | Повторение, signing, flags, все сценарии, ограничения причинной интерпретации |
Все 6 Swift files включены в **Integration Tests и Integration Tests App Store**. Минимальные production seams: optional manager `controllerConfiguration` с прежним default; DEBUG AppDelegate factory с двумя guards; отдельный ConfigurationStore directory для CPM test host; разрешён только loopback HTTP в opted-in integration Tab; macOS diagnostics flag adapter доступен через `@testable` без изменения поведения. SecureVault/AppMain bypass отсутствует. Существующие dirty ZIP/SPM files сохранены.
Запускать с **project signing defaults**, как в Xcode; unsigned override оказался ошибкой bootstrap и удалён. CLI run recipe находится в README. Для реального 30s WebKit timeout нужен test-host env `CPM_APP_INTEGRATION_EXPENSIVE=1`; для persistent cache — `CPM_APP_INTEGRATION_CACHE=1`, при необходимости `CPM_APP_INTEGRATION_CACHE_ENTRIES`, execution allowance 2400 s. Cache case лучше выбирать отдельно; этот opt-in включает fresh persistent UUID configuration в harness. Обычные запуски используют nonpersistent stores.
Артефакты `/private/tmp/cpm-app-integration-20261015/`: signed `.xcresult`, logs, exported JSON, `app-test-results.json` (история результатов и hashes исходников). **Финальные результаты сверять с §13 [[cpm-status-2026-10-15]], не считать каждый characterization test доказательством воспроизведения причины.** P3 может пройти с отрицательным outcome; background crash может самоизлечиться. App P5 ограничен одной потерянной навигацией и контролями: более длинная попытка прерывалась DEBUG guard закрытого окна в XCTest. Persistent P5/stuck доказан отдельно SPM, что не превращает app attempt в green. iOS/device-only причины и естественная потеря listener metadata этим suite не доказаны.
App harness isolation follow-up: extension resources now use an owned UUID directory under **unswizzled `NSTemporaryDirectory()`**, outside `com.duckduckgo.macos.tests`. The latter is a shared FileManager swizzle target that TestRunHelper removes on host initialization/bundle completion. A preceding joint run passed 16/18 but lost its Network-test extension resource before reload (`extensionNotFound`), then the next wake test hit INT. The deleter was not identified; latest status must include that red run and the resource-isolation recheck, rather than treating prior isolated passes as a green combined suite. `CPMAppExtensionStorage.resolveInstalledExtension` still delegates to production and adds only missing-folder/root logging.
Финальная сверка §5: `signed-isolated-regression` **18/18**, `signed-isolated-pinned` **1/1 (72.644 s)**, `signed-isolated-cache` **1/1 (43.053 s)**. Все summaries: 0 failures, 0 skipped, 0 expected failures. В последнем cache run 20k ресурсов за 39.113 s, post-reload probe 0.900 s, P3 не воспроизведён, UUID store удалён. Все 20 JSON exported в одноимённые `*-evidence/` directories; `/private/tmp/cpm-app-integration-20261015/app-test-results.json` содержит текущие hashes исходников и всю per-method историю. HEAD `46f25175c7c42766cab23ea39f447f075c7bdd24` сохранён, исходный dirty ZIP не менялся. Финальный strict lint: 11 Swift files, 0 violations; PBX и scheme XML валидны. Предыдущие red attempts сохранены, не превращены в pass; единый invocation всех 20 не выполнялся.
16 Oct clarification/rerun: all 20 current app methods passed again in 18+1+1 groups, same signed build and source hashes; `/private/tmp/cpm-app-integration-20261015/rerun-failed-20261016-042858/rerun-summary.json`. This is an instrumented app-test environment, not an unchanged shipping binary. P1/P2 delay/SIGSTOP and message/native/dashboard loss are imposed faults; P5/P6/Network use real lifecycle paths but their production occurrence/causal attribution is a separate claim. Old long P5 is still outside the verified app scope; P3 remains unreproduced. Cache teardown has a recorded nonfatal SQLite warning. Submodules are not required by these CPM cases: privacy-reference-tests resources map to Unit Tests targets, SnapshotReferences to snapshot testing support, not XCUI accessibility snapshots. Neither submodule was initialized or modified by this rerun.
## Локальный кандидат P1/P2 — 16 октября 2026 (без commits)
Worktree `cpm-e2e-init`, HEAD всё ещё `46f25175c7c42766cab23ea39f447f075c7bdd24`. Эта секция дополняет прежнюю карту: #7379 не изменялся, новая работа пока только локальная.
- `WebExtensionLifecycleCoordinator.initialLoadWaiter`: после launch gate проверяет подготовку background на каждом HTTP(S) main-frame navigation. Await ограничен прежними 5 s и отменой navigation; AsyncStream позволяет освободить navigation даже при callback WebKit, игнорирующем cancellation. Жизненный цикл load/sync этим не блокируется.
- `WebExtensionManaging.prepareEmbeddedBackgroundForNavigation`: новый protocol hook с no-op default для лёгких conformers. Реализация — `WebExtensionManager.swift`, без нового project file.
- `WebExtensionManager.loadEmbeddedExtensionBackgroundContent`: публичный callback API + одна shared startup task на текущий context. Error notification завершает/очищает старую readiness task, чтобы новая попытка не приклеилась к завершённому старому startup.
- `WebExtensionManager.recoverEmbeddedBackgroundLoadFailure`: собственный текущий embedded context, настоящий domain/code 6, существующий recovery kill switch, known app_state != background, отсутствие M4 in-progress и cooldown 60 s. **Повторяется только background load; context/Tab/controller не заменяются.** Полный M4 reload остаётся при confirmed stuck. Старый full-context P2 prototype отброшен: он терял page initResp в existing tab при повторе.
- `CPMAppKnownFailureIntegrationTests.testSlowBackgroundWakeKeepsFirstNavigationHealthy`: actual idle + 8 s worker delay, первый документ получает native+page без detector failure.
- `...testFrozenBackgroundRecoversAutomaticallyForNextNavigation`: actual one-tab idle wake, owned SIGSTOP 45 s, настоящий error 6. Требует native+page на **том же failed document до новой navigation**, тот же объект context и recovery_document=failed_navigation; затем healthy existing/new-window/new-tab.
- Raw P1 и P3 characterization отключают navigation gate; P5 остаётся с gate включённым и ожидает bucket bg_view_creations=3_to_5: подготовка P1 создаёт дополнительный background перед rejected unknown-tab message. P5 tab-registration loss и healthy controls по-прежнему проверяются.
Артефакты и snapshots: `/private/tmp/cpm-p1-p2-fixes-20261016/`. `before-changes.tar.gz` сохраняет прежние dirty changes; `point-fixes.diff` предназначен для delta этой работы. Начальный background-only result — 2/2 PASS, пакет 806/14 skipped/0 failures. Общий run/negative controls/P5/cache ещё выполняются; окончательный исход — в [[cpm-status-2026-10-15]]. Зелёный случай относится к macOS с imposed timing/process faults; natural iOS cause по-прежнему не доказан.
**Финальная сверка P1/P2, 16 октября 12:30 UTC:** работа завершена без commits. `validation-summary.json`: **22/22 уникальных app methods PASS**, positive groups 2+19+1+1 (P2 повторён), 0 failures/skips/expected failures; оба disabled-fix controls functional FAIL. `unit-package-background-retry.log`: 806 / 14 skipped / 0 failures; strict lint 6 файлов — 0 violations. Source hashes после сборки не менялись, прежние dirty files/ZIP сохранены. Окончательный P2 — background-only retry и same-document native+page recovery, **не context replacement**. Предыдущая full-reset реализация отвергнута после повторной потери page initResp. Current patch — `/private/tmp/cpm-p1-p2-fixes-20261016/point-fixes.diff`; P5 ожидает дополнительный view от P1, P3 остаётся отрицательным на 20,000 resources. Cache assertions прошли, но teardown снова имеет известный SQLite vnode warning для owned UUID. Подробная проверенная матрица/ограничения и весь red-history — [[cpm-status-2026-10-15]].
## Отдельный worktree P1/P2 — 16 октября 2026
`/Users/admin/DuckDuckGo/apple-browsers.git/alex/cpm-fix-p1-p2`, branch `alex/cpm-fix-p1-p2`, от `alex/cpm-messaging-readiness-telemetry` (`01d4a0ec22`), без коммитов. Отличается от полного scratch research-набора:
- `WebExtensionLifecycleCoordinator.initialLoadWaiter`: после launch открывает ограниченное 5 s ожидание background wake; cancellation-safe AsyncStream, существующий gate flag.
- `WebExtensionManager.prepareEmbeddedBackgroundForNavigation` / `loadEmbeddedExtensionBackgroundContent`: один startup job на конкретный context object, public WK callback, release pending callers при error/reload.
- Observer `WKWebExtensionContext.errorsDidUpdateNotification` / `recoverEmbeddedBackgroundLoadFailure`: error 6 только своего embedded context, существующий recovery flag, current background guard, throttle 60 s; повтор background load без context replacement. M4 reload остаётся fallback.
- Recorder `isApplicationInBackground`: читает уже существующий appSession lifecycle provider, не добавляет telemetry dimension.
- `macOS/IntegrationTests/CPM/`: пять Swift-файлов, ровно два app-теста `CPMBackgroundRecoveryIntegrationTests`, сокращённая инфраструктура; схема `CPM Integration Tests`. Реальные app windows/tabs/native handlers/WebKit, неизменный bundled ZIP, extracted startup-delay и page initResp observation, SIGSTOP только своего background PID. Scoped drop/cache/Network/P3/P5 сценарии остались в research worktree.
- Четыре новых метода `WebExtensionLifecycleCoordinatorTests`: wake после launch, bounded non-cancellable callback, cancellation, disabled flag. Всего 6 новых test methods; 22 — весь прежний app-набор.
Текущая база уже перешла на episode contract. Test-only diagnostics proxy фиксирует реальный first-failure вызов, не синтезируя monitor events. P2 требует terminal `failed_measurement_response` / `reload_state=none` плюс page initResp исходного документа до другой навигации и ту же context identity. Signed app 2/2 PASS; unit 31+134=165 PASS; strict lint 14 files / 0 violations. Оба disabled-flag контроля на той же сборке функционально FAIL. Test autorelease pools исправили deallocation guard без отключения проверок. Source hashes и исходный scratch неизменны; schemas/ZIP/dependencies в diff не менялись. Артефакты и полная история: `/private/tmp/cpm-p1-p2-clean-20261016/validation-summary.json`, [[cpm-status-2026-10-15]]. Границы P1 cap / macOS injection / неустановленного natural iOS trigger сохраняются.
## Объединение #7379 с P1/P2 и независимый lifecycle guard — 16 октября 2026
Текущий `alex/cpm-fix-p1-p2` семантически заменяет #7379 в пределах прежнего navigation timeout 5 s:
- `WebExtensionManager+EmbeddedExtensions.installEmbeddedExtension` вызывает `trackEmbeddedBackgroundStartup(identifier:)` именно для вновь установленного embedded context.
- Manager сохраняет preferred context, а `prepareEmbeddedBackgroundForNavigation` захватывает конкретный context object. Fallback учитывает append-порядок installation store и не выбирает произвольный context при неизвестном порядке нескольких версий. `loadEmbeddedExtensionBackgroundContent(in:)` coalesces job на тот же object.
- `recoverEmbeddedBackgroundLoadFailure` игнорирует superseded context. Состояние background приходит через самостоятельный `isApplicationInBackground` provider; iOS factory читает `UIApplication.shared.applicationState` независимо от diagnostics flag. Старый diagnostics getter удалён. Перед queued retry повторно проверяются flag/background.
- Два install/upgrade-теста #7379 адаптированы в `CPMBackgroundRecoveryIntegrationTests` рядом с P1/P2. Реальный upgrade даёт context sets `[0,1,2,1]`; assertions требуют native dashboard + page initResp первого документа, а не просто загрузку context. Default delay 4500 ms.
- Пять новых manager unit tests проверяют coexistence/newest selection, fallback после удаления replacement, diagnostics=nil + background→foreground, runtime kill switch и отказ retry старого context. Вместе с четырьмя lifecycle tests в ветке 9 новых unit methods.
Финальная валидация: общий signed app **4/4 PASS**; install/upgrade **10/10 + 10/10 PASS** на той же сборке; targeted unit **170 PASS**, strict lint 16 files / 0 violations; WebExtensions generic iOS Simulator module **BUILD SUCCEEDED**. Все 4 disabled-flag controls функционально FAIL без teardown errors. Коммитов и изменений scratch/ZIP/schema/dependencies нет.
Локальные `[CPM Background]` logs показывают медленный WK callback (>=1 s) и release gate по deadline. Они подтвердили предел: fixture 5000 ms даёт фактическую готовность после 5 s cap и может потерять первый upgrade document (6/10 ранее). Этот over-budget режим не исправлен и сохраняется через `CPM_APP_INTEGRATION_INSTALL_DELAY_MS=5000`; здоровые contract regression cases проверяют 4500 ms. Это не доказательство всех natural production causes и не реальный iOS suspension/resume run. Артефакты и история: `/private/tmp/cpm-p1-p2-replacement-20261016/validation-summary.json`, [[cpm-status-2026-10-15]].
## Дополнение: late background recovery — 16 октября 2026, работа продолжается
По запросу сначала сохранён прежний проверенный стейт: commit `dff29dfb1e` (`Fix CPM background wake and installation readiness`) в `alex/cpm-fix-p1-p2`. Предыдущие утверждения «коммитов нет» относятся к проверке до этого checkpoint. Новые изменения пока не закоммичены.
Добавлен кандидат восстановления потерянного первого документа при startup за пределами 5 s: отдельный remote flag `webExtensions.cpmLateBackgroundDocumentRecovery`, default disabled. Navigation cap остаётся 5 s. Кандидат привязывает документ к конкретному unfinished startup, требует настоящий missing-dashboard timeout и успешный public WK background callback; после этого допускает одну GET-перезагрузку в пределах 30 s от старта. Смена/закрытие/ошибка документа, context replacement, background app state, выключение flag, изменённые form fields/contenteditable/loading document отменяют reload. Логика независима от diagnostics flag.
Пять новых dailyAndCount pixels различают install readiness, upgrade readiness, idle background wake, error-6 background retry и late document reload. `background_ready` — факт успешного WK callback после использованного navigation wait (для error-6 retry — любого успешного retry), не доказательство initResp; `reload_started` — принятый WK reload; `response_received` — однозначно атрибутированный dashboard response после reload, не ACK content script. App tests отдельно требуют настоящий page initResp. macOS names заканчиваются `_macos`, iOS — стандартные platform/form-factor suffixes. Только outcome, appVersion и стандартные platform parameters, без URL/tab ID/form values/raw error.
На данном этапе: targeted unit suite **178 PASS**, strict lint **17 Swift / 0 violations**, pixel definitions + wide-event consistency/immutability validators обеих платформ PASS. Подписанная app-сборка и реальные install/upgrade 8000 ms regression runs ещё не завершены; over-budget failure пока НЕ объявлен исправленным. Новые артефакты: `/private/tmp/cpm-late-recovery-20261016/`.
## Итог late background recovery и fix instrumentation — 16 октября 2026
Актуальный стейт: checkpoint **`dff29dfb1e`** сохранён до новых изменений; новый код/tests/pixels в `alex/cpm-fix-p1-p2` остаются **незакоммиченными**. Новый flag **`webExtensions.cpmLateBackgroundDocumentRecovery` default disabled**, динамически проверяется до попытки и после async safety check; remote config не менялась. Navigation cap по-прежнему **5 s**.
Новый `CPMBackgroundReadinessRecovery.swift` в shared Autoconsent получает startup UUID manager, commit/failure/response/navigation events monitor и зарегистрированный GET WKWebView от platform tabs. Документ должен commit во время известного unfinished startup; только genuine missing-response timeout вместе с successful public background callback допускают одну GET-перезагрузку в пределах 30 s startup. Изменённый/loading/closed/failed document, main-document form edits/contenteditable, background app state, flag OFF и context replacement запрещают reload. Идентичность проверяется после async JS. Результат — replacement document на том же URL, сохранённый extension context; P2 background-only recovery исходного документа остаётся отдельным механизмом. Safety check использует настоящий async WK API label `contentWorld:`; ранние красные app runs поймали ошибочный callback overload с Void, и вся история сохранена.
Пять отдельных dailyAndCount pixels с prefix `web_extension_cpm_`: `install_readiness`, `upgrade_readiness`, `background_wake`, `background_load_retry`, `late_document_reload`. macOS suffix `_macos`, iOS standard suffixes. Первые четыре: `background_ready` после использованного slow navigation wait (для P2 любого successful retry), без page-ACK claim; последний: `reload_started` и однозначный dashboard `response_received` после replacement navigation. Ответ исходного документа во время safety check не получает credit. Только outcome и стандартные параметры, без URL/tab IDs/field values/raw errors. Injection + PixelKitMock adapter test, definitions/validators обеих платформ PASS. Existing episode contract сохранён; document reload даёт `failed_tab_response`, extension `reload_state=none`.
**Проверено на одной финальной подписанной сборке:** общий набор **8/8 PASS** (7 app/WebKit scenarios + adapter test); новые 8-second install/upgrade **2/2 PASS**, затем **10/10 repetitions PASS**; новый flag OFF **2/2 функционально FAIL**, 0 skips/expected failures/teardown errors. Во всех 14 positive over-budget executions первая загрузка **5.085–5.328 s**, затем ровно один reload, настоящий page initResp + native dashboard, URL/context сохранены. Edit-veto app test PASS; прежние P1/P2/install/upgrade PASS. Targeted units **179 PASS** (9 новых recovery; всего 18 новых unit methods в ветке), lint **17 files / 0 violations**, новый recovery без compiler warnings. Shared iOS Simulator и FeatureFlags-iOS builds PASS; полный iOS app/runtime не запускался.
Code entry points: manager background startup callback/typed fix marker; monitor `backgroundReadinessRecovery` и `registerReadinessRecoveryDocument`; macOS PrivacyDashboardTabExtension weak view + real initiating request method; iOS TabViewController main-frame GET tracking; platform factories dedicated provider; PixelFiring adapters + platform JSON5 definitions. Все 8 app methods остаются в существующем CPM file/scheme; SharedPackages files подхватывает SwiftPM без pbxproj изменений.
Все facts/history/guards/pixel semantics: [[cpm-status-2026-10-15]], `/private/tmp/cpm-late-recovery-20261016/validation-summary.json`. Source/app/test binary hashes стабильны; bundled ZIP равен checkpoint; package pin side effect убран; scratch/#7379 не менялись. Это opt-in bounded GET recovery, не универсальное исправление permanent background failures/unsafe documents. Прежние P3/P5 и natural-production/iOS suspension ограничения не сняты; 22 historical research methods отдельно.
## Перенос в PR #7379 поверх #7376 — 16 октября 2026
### Git и состав PR
По запросу до переноса сохранён checkpoint `367759cbc17d717b05e53ab30792c077ff103a8b` в `alex/cpm-fix-p1-p2` (предыдущий checkpoint `dff29dfb1e` также сохранён). Старый чистый worktree `/Users/admin/DuckDuckGo/apple-browsers.git/cpm-fix-init-loss` удалён штатно; локальные ignored settings сохранены в `/private/tmp/cpm-pr7379-port-20261016/retired-worktree-local-settings.tar.gz`. Research worktree `cpm-e2e-init` и его незакоммиченные данные не менялись.
Рабочая директория остаётся `/Users/admin/DuckDuckGo/apple-browsers.git/alex/cpm-fix-p1-p2`, но её текущая ветка теперь **`alex/cpm-fix-init-loss`**. Финальный HEAD **`aec5be9a834e97984d4cbbee01a207548222427c`**, ровно **3 коммита / 32 изменённых файла** поверх **#7376**, base branch `alex/cpm-messaging-readiness-telemetry`, base SHA `713d67419a776b235b66210b1c594ce39da6fe03`:
1. `05b499e355` — P1/P2, ожидание нового install/upgrade context и необходимые app/unit tests.
2. `c661f805b1` — late GET document recovery, независимый flag, пять fix pixels, регрессии.
3. `aec5be9a83` — маркировка local-only fault-injection suite/scheme/README как `to-be-disabled before merge`.
[PR #7379](https://github.com/duckduckgo/apple-browsers/pull/7379) обновлён разрешённым `force-with-lease` с точным ожидаемым старым head `7ad771e227bbd68348ed7b719684783575dd7c9f`; новый head/base проверены через GitHub. Старый head и промежуточная история сохранены в backup branch `alex/cpm-fix-init-loss-before-force-20261016` (`70869f5c69`). Зависимость от #7378/research SPM harness убрана из diff; episode contract #7376 сохранён. Промежуточный перенос на прежнюю базу с 167 unit tests — историческая попытка, окончательный результат относится к новой базе и 179 tests.
PR title/description/Testing Steps переписаны под окончательный код. **Коммитить или переключать test-only/fix commits для проверки не требуется.** Удалены старые инструкции и placeholder `GREEN_RUN_URL`; используется одна подписанная сборка с переключением production flags в test fixture.
### Feature flags
| Механизм | Remote flag | Default |
|---|---|---|
| Install/upgrade readiness + P1 navigation wake wait | `webExtensions.stateRestorationGate` | Enabled, существующий |
| P2 background-only error-6 retry | `webExtensions.cpmMessagingHangRecovery` | Enabled, существующий |
| Late first-document GET reload | `webExtensions.cpmLateBackgroundDocumentRecovery` | **Disabled**, новый независимый |
Флаги явно задаются fixture; ENV `CPM_APP_INTEGRATION_DISABLE_P1_P2_FIXES=1` и `CPM_APP_INTEGRATION_DISABLE_LATE_RECOVERY=1` выбирают соответствующие negative controls без изменения исходников или binary. В двух over-budget controls отключается late recovery при сохранённом 5 s navigation gate, чтобы изолировать новый механизм. Remote configuration не изменялась.
### Финальная проверка запушенного head `aec5be9a83`
Новый прогон на базе #7376, macOS **26.7.1**, Xcode **27.1**, нормальная подпись **Apple Development: Alexey Martemyanov (9SAT8X9S84)** / team `HKE973VLUW`. Проверены identity подписи app и strict signatures app debug dylib / XCTest bundle; это не заявление о distribution signing/notarization или полной deep-проверке вложенных продуктов.
| Проверка | Результат |
|---|---|
| Все восемь методов dedicated app suite, соответствующие flags ON | **8/8 PASS**, 0 failures/skips/expected failures |
| Шесть recovery controls, соответствующие flags OFF, те же бинарники | **0/6 PASS, 6 функциональных FAIL**, 0 skips/expected failures, без teardown/deallocation errors |
| Targeted shared suites | **179 PASS**, 0 failures/skips (13 diagnostics + 9 recovery + 78 monitor + 31 lifecycle + 48 manager) |
| Strict lint относительно новой PR base | **27 Swift files**, 0 violations |
| Pixel schemas/formatting + wide-event consistency/immutability | **macOS и iOS PASS** |
| Source/binary integrity | Все **32 source hashes и 2 app/test binary hashes** неизменны; git status чистый |
Положительный набор: P1 slow idle wake, P2 реальная WK error 6 с background-only retry и ответом исходному документу без навигации, install, upgrade при coexistence contexts, late install, late upgrade, edited-document veto и PixelKitMock adapter. При 8000 ms startup в новых late install/upgrade runs первый документ завершился за **5.159 / 5.093 s**, затем ровно один automatic reload (итого два finish), настоящий page `initResp` и native dashboard, URL/context сохранены. Navigation cap остаётся **5 s**. Отдельные десять stress repetitions и более ранние iOS module/flags builds относятся к предыдущему checkpoint/проверке, не объявляются новым stress/iOS runtime run на этом head.
Красные controls на той же сборке: install/upgrade теряют первый native/page ответ; P1 получает response timeout; P2 достигает реальной error 6, но без flag не получает same-document recovery; оба over-budget случая не получают automatic document recovery. Это намеренные функциональные FAIL, не инфраструктурные crashes и не `XCTExpectFailure`. У controls и positive runs отдельные `.xcresult` / summaries / JSON attachments.
Local app suite, setup и dedicated scheme/README помечены **`to-be-disabled before merge`**. Они уже требуют `CPM_APP_INTEGRATION=1` и пропускаются обычным CI без opt-in. Сценарии используют GUI-host и 45 s SIGSTOP только собственного background PID; deterministic unit regressions оставлены включёнными. General macOS UI Tests scheme локально не запускалась из-за её profile-clearing preaction.
Артефакты нового прогона: **`/private/tmp/cpm-pr7379-final-20261016/validation-summary.json`**, рядом `enabled.xcresult`, `disabled.xcresult`, JSON evidence, logs, signature/hash snapshots, `pr-after.json`, финальный PR body и CI snapshots. Bundled extension ZIP неизменён относительно base/checkpoint: SHA256 **`ff98a2b0c07819b2ee5ee6de67d6dd8d28696363c08c9fbc2c32255e9d9efa94`**; package pin side effect убран. Прежние red runs и расследовательские данные сохранены.
Новый remote CI после push запущен; локальный PASS не выдаётся за завершённый зелёный GitHub CI. Контракт fix pixels остаётся прежним: `background_ready` — callback readiness, `response_received` — dashboard correlation после reload; page ACK проверяет app assertion отдельно. Default-disabled late flag не включался в remote config. Natural production attribution, unreproduced P3, прежние P5 app ограничения и реальный iOS suspension/resume этой работой не закрыты.
### GitHub checks после push
Финальная сверка CI для `aec5be9a83`: основные jobs ещё IN_PROGRESS/QUEUED; **Danger — FAILURE**, точная причина — новые pixels требуют label **`privacy review required`**, который создаёт privacy-review task в Asana. Label в этой работе не менялся. Дополнительно Danger предупреждает об отсутствующем Asana Feature Flags project task link для `cpmLateBackgroundDocumentRecovery` в macOS/iOS enum comments и размере PR; это warnings, не причина функциональных app failures. Значения flags и локальные ON/OFF результаты выше подтверждены независимо. Evidence: `/private/tmp/cpm-pr7379-final-20261016/danger-comment.md`, `ci-final.json`.
## Вынос app harness из PR #7379 — 16 октября 2026
По запросу весь local-only app harness вынесен в отдельную review-only ветку **`codex/cpm-fix-init-loss-integration-tests`**, основанную непосредственно на очищенном **`alex/cpm-fix-init-loss`**. Тестовая ветка не предназначена для merge в main, отдельный PR на неё не создавался.
- Production PR #7379: head **`da126120a94b625cf1d9705302c29099b4dbc62d`**, база #7376 / `713d67419a`, **2 коммита, 21 файл**. `7dbe2a25bb` — P1/P2/install/upgrade; `da126120a9` — late recovery + flags/pixels. Все 21 production/unit/schema файла побайтово совпадают с прежним `aec5be9a83`; поведение и defaults не менялись.
- Harness branch: head **`6e4e39e67c64907127bd17197a932b0117a4cb09`**, parent ровно production head `da126120a9`, **один дополнительный commit, 11 test-only paths**. Это пять Swift app-test files + README, CPM scheme и PBX registrations, DEBUG manager factory/config storage/loopback navigation hooks в AppDelegate, ConfigurationStore, Tab.
- Все app hooks и Xcode project changes исключены не только из финального diff PR, но и из его двух новых коммитов. 18 новых deterministic shared unit methods остаются в production PR; восемь app methods — только в harness branch.
- В harness комментариях прежнее `to-be-disabled before merge` заменено на ясное review-only назначение: эта ветка не идёт в main. Explicit scheme opt-in сохранён. Старые checkpoints и исходная история сохранены; дополнительный backup `codex/cpm-fix-init-loss-before-harness-split-20261016` -> `aec5be9a83`.
- Reviewer instructions в PR: unit suites на PR branch; `git fetch origin`, `git switch --detach origin/codex/cpm-fix-init-loss-integration-tests`; открыть workspace, выбрать **CPM Integration Tests / My Mac**, **⌘U**, ожидать 8 PASS / 0 skips; затем вернуться `git switch --detach origin/alex/cpm-fix-init-loss`. Production description и пользовательские правки сохранены. Исторические intentional-red controls не возвращены в reviewer steps.
Артефакты разделения: `/private/tmp/cpm-harness-split-20261016/`: manifest с production/harness paths и исходными SHA256, раздельные patches, build/unit/lint/signing logs и snapshots PR description. Точные окончательные результаты новой проверки и remote SHA — ниже.
### Проверка после разделения
- Production `da126120a9`: обычная **macOS Browser Debug app build PASS** без harness; нормальная Apple Development подпись, team `HKE973VLUW`; strict app dylib verification PASS.
- Production shared regressions: **179/179 PASS**, 0 failures/skips; 18 новых unit methods сохранены в PR.
- Review-only harness `6e4e39e67c`: полный **`xcodebuild test`** (эквивалент Product → Test), **8/8 PASS**, 0 failures/skips/expected failures, отдельная подписанная app/XCTest сборка. Все 32 source hashes неизменны во время run; strict dylib/XCTest signature checks PASS.
- Strict lint: **19 production Swift files / 0 violations**, **8 harness Swift files / 0 violations**. PBX syntax и scheme XML валидны; каждый из пяти harness Swift files зарегистрирован ровно по одному разу в обоих native Integration Tests targets. Выполнен normal signed target, не отдельный App Store runtime run.
- Подтверждены remote SHA обоих branches и GitHub PR diff: production `da126120a94b625cf1d9705302c29099b4dbc62d`, harness `6e4e39e67c64907127bd17197a932b0117a4cb09`; parent harness совпадает с production head. Production PR остаётся на базе #7376. В рабочей директории снова `alex/cpm-fix-init-loss`, git status чистый.
- `validation-summary.json`, `pr-after.json`, `remote-heads.txt`, `harness-enabled.xcresult` и JSON evidence находятся в `/private/tmp/cpm-harness-split-20261016/`. Generated Package.resolved side effect сохранён и убран из рабочей копии; production dependencies/ZIP не менялись.
- Новые шесть disabled controls после branch-only split не повторялись: предыдущие **6/6 functional FAIL** на эквивалентном production/harness code остаются историческим evidence в `/private/tmp/cpm-pr7379-final-20261016/`. Нового stress/iOS runtime run не заявляется. P3, прежние P5 app и natural-production/iOS ограничения сохраняются. Remote CI нового head — отдельная незавершённая проверка, snapshot `ci-after-split.json`.
Ранние формулировки о local-only suite внутри production PR и необходимости disable перед merge описывают прежнюю структуру. В актуальном PR этих файлов и hooks нет; review-only branch целиком остаётся вне main.
## Возврат проверки production feature flags OFF → failures — 16 октября 2026
При упрощении reviewer instructions ошибочно удалён необходимый negative-control шаг. В PR #7379 восстановлен полный **ON → OFF → ON** workflow с конкретными Xcode schemes; остальные пользовательские правки сохранены.
В review-only branch `codex/cpm-fix-init-loss-integration-tests` добавлен commit **`543e37164b369c8da8b6734e500af4a203944943`**, только два файла: новый shared scheme **CPM Integration Tests - Fixes Disabled** и README с точными cases/flags/ожидаемыми failures. Эта ветка теперь содержит **2 test-only commits / 12 paths** сверх production `da126120a9`; в main она не идёт. Production PR остаётся **2 commits / 21 files**, его head/code/unit regressions не менялись.
- **CPM Integration Tests** → 8 tests должны PASS.
- **CPM Integration Tests - Fixes Disabled** → автоматически выбирает ровно 6 recovery methods и задаёт обе fixture ENV variables `CPM_APP_INTEGRATION_DISABLE_P1_P2_FIXES=1` / `CPM_APP_INTEGRATION_DISABLE_LATE_RECOVERY=1`. Ожидаются **6 functional FAIL / 0 PASS / 0 skips**.
- Снова **CPM Integration Tests** → все 8 должны PASS.
Ручное редактирование ENV или выбор отдельных tests reviewer больше не нужны. OFF scheme через fixture отключает реальные production flags: `webExtensions.stateRestorationGate` для install/upgrade/slow wake, `webExtensions.cpmMessagingHangRecovery` для error-6 retry, `webExtensions.cpmLateBackgroundDocumentRecovery` для late GET recovery. Late recovery выключен и в ранних controls, чтобы не маскировать их; в двух over-budget controls navigation gate сохраняется ON.
Ожидаемые причины FAIL: потерянный native/page first response после install/upgrade; CPM timeout после slow wake; timeout same-document recovery после реальной WK error 6; timeout automatic first-document recovery в late install/upgrade. Setup failures, host crashes или skips не подтверждают работу флага. Полные test names и mapping находятся в README тестовой ветки. В PR сохранены checkout/test/return commands и добавлен возврат к ON scheme.
Артефакты этой проверки: `/private/tmp/cpm-flag-controls-20261016/`; compiler/test Swift sources и production ref не менялись. Предыдущие 179 unit PASS / production build PASS остаются предыдущей проверкой, не объявляются новым unit/build run.
### Финальная проверка ON → OFF → ON
Baseline normal scheme на `6e4e39e67c` ранее дал **8/8 PASS**. После добавления только OFF scheme/README на `543e37164b` выполнены оба shared schemes через `xcodebuild test-without-building -workspace DuckDuckGo.xcworkspace -scheme …` на тех же подписанных app/XCTest binaries:
| Run | Результат |
|---|---|
| CPM Integration Tests - Fixes Disabled | **6 failed / 0 passed / 0 skipped / 0 expected failures**, exit 65 |
| CPM Integration Tests после OFF | **8 passed / 0 failed / 0 skipped / 0 expected failures**, exit 0 |
У OFF все шесть failure records сопоставлены точным test names и ожидаемым функциональным причинам; никаких deallocation/setup failures или подмены через `XCTExpectFailure`. Возврат ON восстанавливает PASS для прежних P1/P2/install/upgrade, обоих late cases, edit veto и pixel adapter. SHA256 app debug dylib и XCTest binary неизменны до/после обоих runs, Swift sources не менялись относительно baseline harness. Remote config и production defaults не редактировались.
Artifacts: **`/private/tmp/cpm-flag-controls-20261016/validation-summary.json`**, `off.xcresult`, `on.xcresult`, summaries, exported JSON evidence, `binary-hashes-before.json`, `pr-after.json`, source/branch verification. Это новая runtime-проверка переключения flags, без нового production build/unit run. Reviewer теперь выбирает два готовых schemes: не редактирует ENV вручную, не выбирает шесть methods и не переключает commits. Production PR и прежние P3/P5/natural-production/iOS ограничения сохраняются.
## 16 октября 2026 — исправления замечаний review #7379
Production head: **`08866c7eda9b1395f7c0baf9887c827bd3f59ec5`**, branch **`alex/cpm-fix-init-loss`**, PR [#7379](https://github.com/duckduckgo/apple-browsers/pull/7379), база **#7376 / `alex/cpm-messaging-readiness-telemetry` / `713d67419a`**. Три commits / 21 changed paths. Новый commit **Share CPM startup deadline across navigations** изменяет 8 существующих Swift files; app fixture/hooks в production diff не возвращены. Production и review-only branch обновлены обычным push без переписывания истории.
### Что проверено и исправлено
- [Замечание о неотменяемом `Task.value`](https://github.com/duckduckgo/apple-browsers/pull/7379#discussion_r4296231857): исходное замечание уже было закрыто реализацией cancellable AsyncStream bridge и regression `testBackgroundWakeWaitIsBoundedWhenWebKitIgnoresCancellation`. Проверено повторно на текущем коде: timeout освобождает navigation, даже когда реальный startup task не реагирует на cancellation; физический WebKit startup не отменяется.
- [Повторные пять секунд на каждом navigation](https://github.com/duckduckgo/apple-browsers/pull/7379#discussion_r4296231864): замечание оказалось актуальным по поведению, несмотря на устаревшее имя файла в тексте. Раньше каждый waiter имел отдельный deadline. Теперь manager возвращает стабильный `Task` текущего startup, а coordinator хранит один bounded wait для его identity. Оба уже подготовленных waiter и последующие navigation после timeout используют завершившийся wait и не платят ещё по 5 секунд.
- Новый startup/replacement получает собственный deadline. `cancelAll()` отменяет только navigation wait и очищает его cache. Startup completion очищается при matching uninstall/unload/reload/error-6 retry или замене context; поздний callback старого task не может очистить текущий startup. Timeout сам по себе не отменяет background: поздний успешный callback по-прежнему может запустить отдельный late-document recovery.
- В Mac `PrivacyDashboardTabExtension` и iOS `TabViewController` вызов provider ограничен main-frame HTTP(S), поскольку получение task теперь сразу запускает подготовку background. Iframes и другие URL schemes не начинают этот startup через navigation gate.
- Добавлены три CI unit regressions: `testTimedOutBackgroundStartupDoesNotDelayLaterNavigations` (включая waiter, подготовленный до истечения deadline), `testNewBackgroundStartupCanWaitAfterEarlierStartupTimesOut`, `testCancelAllReleasesPendingBackgroundNavigation`. Test double сохраняет identity pending startup; зависший callback моделируется неотменяемым task, затем continuation явно освобождается при cleanup.
### Feature flag tracking и review status
Default/config flags не менялись: `stateRestorationGate` и `cpmMessagingHangRecovery` сохраняют прежние default; отдельный `cpmLateBackgroundDocumentRecovery` остаётся **false**. Remote configuration не редактировалась. Для нового flag добавлены реальные per-platform задачи в **Apple Feature Flags Registry** и ссылки непосредственно над enum case:
- [macOS tracking task](https://app.asana.com/1/137249556945/project/1211834678943996/task/1219588497257482).
- [iOS tracking task](https://app.asana.com/1/137249556945/project/1211834678943996/task/1219588320073672).
Оба review threads проверены и resolved; открытых inline threads после readback — 0. Danger на `08866c7eda`: **0 failures / 1 warning**, только advisory о размере PR (1090 added lines); предупреждение об отсутствующих flag tracking links исчезло. Отдельная Privacy Review task уже существовала, дубликат не создавался.
### Проверки и отдельная test branch
- До исправления две новые проверки дали **2/2 functional FAIL**, после успешной компиляции: повторный navigation wait и освобождение через `cancelAll`.
- После исправления пять выбранных shared suites: **182 PASS / 0 failures** (13 diagnostics + 9 readiness recovery + 78 monitor + 34 lifecycle + 48 manager). Относительно базы PR теперь 21 новый shared unit method.
- Strict SwiftLint: **8 changed Swift files / 0 violations**.
- Production **macOS Browser Debug**, обычная **Apple Development signing**, build PASS. Подпись app debug dylib проверена. **Shared WebExtensions module для iOS Simulator build PASS**; это не full iOS app build и не runtime validation iOS suspension/resume.
- Review-only **`codex/cpm-fix-init-loss-integration-tests` / `3f4d7555113d547797b45ff6b2c7b883efedd520`** синхронизирована merge нового production head; отдельный doc-only commit актуализировал README (7 новых coordinator regressions, 21 shared regression суммарно). Над production — два прежних harness commits + sync merge + README correction; diff всё ещё ровно **12 test-only paths**, 8 app methods и два shared schemes. Эта ветка не идёт в main.
- Description #7379 изменена только в двух местах для shared deadline и риска зависшего callback. Пользовательские правки и понятные Xcode steps **ON → OFF → ON**, названия schemes и ожидаемые 8 PASS / 6 FAIL / 8 PASS сохранены. Коммитить или редактировать Swift для reviewer run не нужно.
Финальный runtime cycle на одном наборе подписанных app/XCTest binaries:
| Scheme / запуск | Passed | Failed | Skipped | Результат |
|---|---:|---:|---:|---|
| CPM Integration Tests — ON | 8 | 0 | 0 | PASS |
| CPM Integration Tests - Fixes Disabled — OFF | 0 | 6 | 0 | Все шесть functional failures совпали с ожидаемыми симптомами |
| CPM Integration Tests — ON повторно, без сборки | 8 | 0 | 0 | PASS |
OFF действительно отключает production flag для соответствующего сценария: install/upgrade/slow-wake потеряли первый ответ, P2 не дождался same-document recovery после фактического error 6, late install/upgrade не дождались automatic first-document recovery. `expectedFailures` в XCTest — 0: OFF остаётся явным красным контрольным запуском, а не ignored CI failure. Fixture setup/host crash/skips не засчитаны как воспроизведение. SHA256 app dylib и XCTest executable совпали до OFF, после OFF и после повторного ON. Бинарники собраны на sync head `d0922bb277`; финальный test-branch commit `3f4d755511` меняет только README.
CI snapshot `08866c7eda` на 16:19 UTC: 31 SUCCESS, 12 SKIPPED, 3 QUEUED, 5 IN_PROGRESS; failures отсутствуют. SwiftLint, macOS/iOS pixel validation и Danger — SUCCESS. Полный CI ещё не объявлен зелёным: оставшиеся unit/UI/release jobs были pending/running.
Артефакты этого прогона: **`/private/tmp/cpm-pr7379-review-fixes-20261016/`**. `red-tests.log` — red-before-fix; `unit-final.log`, `lint-final.log`, `production-build-final.log`, `ios-module.log` — final validation. Отдельные `app-on*`, `app-off*`, `app-on-restored*` содержат xcresult summaries и exported evidence. SHA256 app/XCTest сохранены до OFF и после каждого последующего run; review/PR/Danger/CI snapshots и `validation-summary.json` находятся там же.
Это не новая декларация воспроизведения всех production причин. Ограничения прежнего исследования P3/P5 и iOS runtime сохраняются. Точные два новых navigation/cancellation failures сначала воспроизведены deterministic shared tests; восемь app methods проверяют реальные WebKit/app пути существующих install/upgrade, P1/P2 и late recovery сценариев. В ON evidence P2 снова записан фактический **`WKWebExtensionContextErrorDomain:6`**; late install/upgrade завершили первый navigation примерно за **5.18 / 5.10 секунды**, затем получили отдельный recovery reload, без увеличения navigation timeout.
## 19 октября 2026 — полный повторный review после точечных правок
**Объём.** Повторно прочитан весь production diff: локальный `alex/cpm-fix-init-loss / 57081973629e1ad43b9312fee479d27e34232abf` против `alex/cpm-messaging-readiness-telemetry / 713d67419a` (#7376), 5 commits / 22 changed paths; manager/lifecycle, native callers обеих платформ, recovery/health monitor, flags, pixel adapters/definitions и shared tests. Опубликованный #7379 пока остаётся на `08866c7eda`: 3 commits / 21 files; его CI snapshot — 48 SUCCESS, 21 SKIPPED, без failures. Это не CI непушенных исправлений.
**Сохранённые исправления предыдущего review.** Launch gate переносит уже израсходованный deadline на тот же pending startup; следующий navigation не платит ещё пять секунд. Safety preflight отделён от синхронного reload; attribution вооружается после проверок и сопоставляется с конкретной WKNavigation из принятого automatic reload, включая синхронный didStart. После await повторно проверяются flag, app state, document identity и 30-секундный срок. Пять новых regression methods увеличили выбранные suites со 182 до 187 PASS (13 diagnostics + 13 readiness recovery + 78 monitor + 35 lifecycle + 48 manager). Оба production builds macOS/iOS Simulator и strict lint десяти изменённых Swift files прошли на этом коде; logs сохранены в `/private/tmp/cpm-pr7379-review-selected-fixes-20261016/`.
**Формы — явно отложено пользователем.** После сообщения «пока не парься на счёт форм, оставить как есть оно за фича флагом» document-start tracker, новые conservative veto и четыре form-safety tests убраны отдельным коммитом `5708197362`. Исходный JavaScript predicate побайтно совпадает с `08866c7eda`; новая instrumentation не установлена ни на macOS, ни на iOS. Известное ограничение для controlled inputs с изменяемым defaultValue и Shadow DOM остаётся нерешённым; результаты предыдущего реального WKWebView воспроизведения не отменены. Независимый `webExtensions.cpmLateBackgroundDocumentRecovery` по-прежнему default disabled на обеих платформах. Форма не заявляется исправленной.
**Новый P2 — удаление extension не инвалидирует поздний reload.** `WebExtensionManager.uninstallExtension` после реальной выгрузки вызывает `clearEmbeddedBackgroundStartup(identifier:)`, но не инвалидирует document recovery. Если успешный startup уже запустил async safety preflight и в это время отключить/удалить активный embedded context, late-recovery flag может остаться включённым. Document identity сохранена; после завершения preflight вызывается reload closure для уже отключённого CPM. Изолированный `testFreshReviewUninstallInvalidatesPendingLateRecovery` использовал production manager и default loader: context действительно исчез из controller, installation record удалён, затем reload callback вызван **1 раз вместо 0**. Следует инвалидировать recovery удаляемого активного context; удаление старой версии при upgrade не должно отменять recovery её замены. Production-код в ходе повторного review не исправлялся.
**Проверка finding.** Production sources скопированы побайтно (96 файлов) в изолированный package; добавлен только временный probe. Финальный запуск: **188 методов выполнены, 187 PASS / 1 assertion FAIL**, без падения host. Первый запуск временного probe упал в WebKit dealloc искусственно созданной WKNavigation (CFRetain NULL); этот тестовый setup исправлен, и затем получен именно функциональный failure callback count. Артефакты: `/private/tmp/cpm-pr7379-full-review-20261019/selected-tests-final.log`, `review-results.json`, `source-copy.json`. В production tests этот probe не добавлен.
**Harness и публикация.** Локальный `codex/cpm-fix-init-loss-integration-tests / 4308751d593bcc63abfb4baaced83e35bd19255f` обычным merge синхронизирован с `5708197362`; README исправлен на 26 новых shared regressions и прежнюю защиту форм. Над production по-прежнему ровно 12 harness-only paths, 8 app methods и два ON/OFF schemes; они не идут в main. Удалён только собственный generated Package.resolved diff от предыдущего test run. Рабочая production branch чистая. Production и harness commits пока не push; PR body не перезаписан. Старый ON 8 PASS → OFF 6 functional FAIL → ON 8 PASS относится к опубликованному `08866c7eda` / test branch `3f4d755511`; на новом локальном head этот app cycle ещё не повторён.
**Замечание о стиле URL check.** Для HTTP(S) подходит `URL.NavigationalScheme.hypertextSchemes` из Common; initializer сохраняет регистр, поэтому lowercased необходимо сохранить. Это ответ на вопрос, source replacement не выполнялся.
## 19 октября 2026 — исправление uninstall/preflight race и push
Production **`e970e8c90eca76e55217f75c13122735a72664c5`**, branch `alex/cpm-fix-init-loss`, [#7379](https://github.com/duckduckgo/apple-browsers/pull/7379), база #7376 / `713d67419a`. Опубликованы все три локальных commits после `08866c7eda`; итоговый diff — 6 commits / 22 paths. Push обычный fast-forward, без переписывания истории. Review-only **`codex/cpm-fix-init-loss-integration-tests / e380cf98f7e9709085ce76289211f3e1e43c8395`** синхронизирована обычным merge, над production по-прежнему ровно 12 harness-only paths; они не идут в main.
**Исправление.** До unload запоминается, удаляется ли текущий выбранный embedded context. После успешного unload его background document recovery инвалидируется, включая уже ожидающую async safety preflight. Неудачный unload выходит до invalidation; удаление старого context при upgrade не отменяет recovery replacement. Добавлены постоянные shared regressions `testUninstallingActiveEmbeddedContextCancelsPendingDocumentRecovery`, `testUninstallingOldEmbeddedContextPreservesReplacementDocumentRecovery` и `testFailedUninstallPreservesActiveDocumentRecovery`. Первые два используют production loader с настоящими WKWebExtensionContext; третий моделирует ошибку unload и проверяет сохранение live context. HTTP(S)-checks в native navigation callers macOS/iOS переведены на `URL.NavigationalScheme.hypertextSchemes` с сохранением lowercased.
**Проверки.** До fix новые три tests: **2 PASS / 1 functional FAIL** — активное удаление оставляло выполняющийся reload callback. После fix пять выбранных shared suites: **190 PASS / 0 failures** (13 background diagnostics + 13 readiness recovery + 78 monitor + 35 lifecycle + 51 manager), всего 29 новых shared regression methods относительно базы PR. Unit run выполнен в изолированной копии 96 побайтно совпадающих production source files. Полные macOS и iOS Simulator builds — PASS; macOS app подписан Apple Development, team HKE973VLUW, strict verification всего app bundle PASS. Strict lint всех 11 непушенных Swift files — 0 violations. Схемы/имена/параметры/frequency pixels не менялись.
**Формы и flags.** По явному указанию пользователя новая защита форм не возвращена. Original JavaScript predicate побайтно совпадает с `08866c7eda`; known limitation controlled/defaultValue и Shadow DOM остаётся. Независимый `webExtensions.cpmLateBackgroundDocumentRecovery` всё ещё default disabled на обеих платформах. Deadline launch→navigation и attribution по конкретной WKNavigation сохранены из предыдущих локальных исправлений и теперь опубликованы.
**Документация и app validation.** PR body точечно уточняет общий launch/navigation deadline, invalidation при удалении активного context и точную WKNavigation attribution. Пользовательские правки, testing branch link и ON 8 PASS → OFF 6 functional FAIL → ON 8 PASS instructions сохранены. README harness обновлён: 8 новых manager regressions / 29 новых shared methods. На новом head первый ON: 8 PASS; OFF: ровно 6 functional FAIL, 0 PASS/skips/expected failures. Проверены реальные причины отказа: install/upgrade/slow-wake first response, P2 same-document recovery timeout и late install/upgrade automatic-reload timeout. App/XCTest SHA256 до и после OFF совпали. Повторный ON завершился 8 PASS; SHA256 до OFF, после OFF и после повторного ON совпали. CI опубликованного e970e8c90e: 48 SUCCESS / 21 SKIPPED, failures/pending нет. Старые app результаты не перенесены на новый head.
Артефакты: `/private/tmp/cpm-pr7379-uninstall-fix-20261019/` (`unit-green.log`, `unit-evidence.json`, `lint.log`, `mac-build.log`, `ios-build.log`, signature logs, `heads.json`, PR snapshots); red control — `/private/tmp/cpm-pr7379-full-review-20261019/uninstall-red.log`.
### Итог app validation и CI на опубликованном head
Production `e970e8c90e`, review-only harness `e380cf98f7`. Все три запуска выполнены на одних Apple Development-signed app/XCTest binaries; OFF и повторный ON — `test-without-building`.
| Запуск | Passed | Failed | Skipped | Expected failures |
|---|---:|---:|---:|---:|
| CPM Integration Tests — первый ON | 8 | 0 | 0 | 0 |
| CPM Integration Tests - Fixes Disabled | 0 | 6 | 0 | 0 |
| CPM Integration Tests — повторный ON | 8 | 0 | 0 | 0 |
OFF failure reasons проверены по xcresult: install/upgrade/slow-wake потеряли первый ответ; P2 дождался реального WebKit error 6, но не получил same-document recovery; late install/upgrade не дождались automatic first-document reload. Это шесть намеренно красных функциональных контролей, без host crashes/setup failures/skips. Все 8 ON assertions восстановились после включения flags. SHA256 app debug dylib и XCTest executable совпали до OFF, после OFF и после повторного ON.
Из exported ON evidence: P2 записал **actualBackgroundLoadError=[6]** и затем прошёл проверки ответа оригинальному document без context replacement или ручной навигации; последующие existing-tab/new-tab/new-window assertions также PASS. При 8-секундном startup первая navigation завершилась за **5.167 s (install)** / **5.089 s (upgrade)**, затем был ровно один automatic GET reload (finishCount=2), реальный page initResp и оба этапа pixel `reload_started` / `response_received`. Эти измерения включают локальную загрузку документа после прежнего 5-секундного gate; его лимит не увеличен.
CI опубликованного **`e970e8c90e` завершён: 48 SUCCESS / 21 SKIPPED**, failures/pending нет. GitHub readback подтвердил оба remote heads, прежнюю базу #7376 / `713d67419a` и точное сохранённое PR body. Production worktree возвращён на `alex/cpm-fix-init-loss`, tracked/untracked diff пуст; обе ветки up to date. Новых production изменений после validation нет. Формы оставлены как согласовано; independent late-recovery flag default disabled.
Артефакты `/private/tmp/cpm-pr7379-uninstall-fix-20261019/`: `validation-summary.json`, три `app-*-summary.json` и `.xcresult`, exported attachment folders, `app-on-evidence-summary.json`, три `hashes-*.json`, `pr-final.json`, `remote-heads.txt`. Прежние промежуточные snapshots выше сохранены как история.