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

34 KiB
Executable File
Raw Blame History

title, date, tags
title date tags
CPM Web Extension — карта кода по worktree и строкам 2026-10-09
work
cpm
webkit

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 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:

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 не выполнялся.