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

220 lines
33 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
---
# 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` | `alex/cpm-fix-init-loss`; `7ad771e227bb` | PR #7379: A, первые init при install/upgrade mid-session; красное/зелёное доказательство отдельно от remaining-failures |
| `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.