Files
obsidian-vault/work/projects/cpm-status-2026-10-15.md
T

182 KiB
Executable File
Raw Blame History

title, date, tags
title date tags
CPM Web Extension — единый статус (PR, данные, гипотезы, телеметрия) 2026-10-15
work
cpm
status

Отдельная ветка P1/P2 — финал 16 Oct, 13:33 UTC. Новый worktree alex/cpm-fix-p1-p2 от текущего alex/cpm-messaging-readiness-telemetry (01d4a0ec22): только P1/P2-кандидаты и необходимые тесты. Новых методов 2 app + 4 unit; 22 — полный старый research app-набор. На новой signed app-сборке 2/2 PASS, unit 165 PASS, strict lint 0; те же два app-теста при выключенных flags дают ожидаемые функциональные FAIL. Текущая база использует episode contract: P2 failed_measurement_response, reload_state=none, плюс реальная доставка initResp в тот же документ без навигации. История исправленного test teardown сохранена. Коммитов нет; scratch не изменён; подробности и артефакты — в конце документа.

CPM Web Extension: единый статус, обновление 16 октября 2026

Актуальный локальный итог P1/P2 — 16 октября, 12:30 UTC / 18:30 Бишкек. В cpm-e2e-init добавлены точечные production-кандидаты и две app-регрессии: 22/22 текущих app methods PASS, 0 failed/skipped/expected failures в positive runs; P2 same-document повторён дважды. С выключенными фиксами обе новые регрессии красные по функциональным причинам. Пакет: 806 tests, 14 skipped, 0 failures; strict SwiftLint — 0 нарушений. P2 в окончательном варианте повторяет только background load и сохраняет context; full-context prototype отвергнут как нестабильный. Подробный итог и ограничения — раздел «Окончательный итог точечных P1/P2-кандидатов». Это macOS с управляемыми fault conditions, не доказательство natural iOS production cause. Коммитов/PR изменений нет; прежние разделы и красные прогоны сохранены.

Внутренний документ. Цифры из ClickHouse, в публичные PR, комментарии и issues не переносить (репозитории duckduckgo/apple-browsers и duckduckgo/duckduckgo-privacy-extension публичные). Заменяет заметку cpm-telemetry-data-requirements-2026-10-15 (теперь ссылка сюда). Исторические срезы с собственными датами и знаменателями: cpm-web-extension-public-attribution-2026-10-05, cpm-web-extension-data-analysis-summary-2026-10-08, cpm-web-extension-repro-and-fix-2026-10-08. Актуальная карта worktree и точек входа добавлена в cpm-web-extension-code-guide-2026-10-09; её прежние разделы сохранены как карта на 9 октября.

Дополнение 16 октября: app-hosted integration tests. Добавлены 20 методов в cpm-e2e-init, схема CPM Integration Tests, штатная Apple Development signing, без commits. Финальный тестовый результат — 20/20 PASS в группах 18+1+1, 0 failed/skipped/expected failures. Это не «все production-кейсы кроме P3 воспроизведены»: часть методов использует явные fault injections, а все app methods — тестовое окружение и инструментированный bundle. Связь с production-причинами доказана не для всех групп. Все результаты, включая красные совместные прогоны, отрицательный P3 и ограничение app P5, находятся в §13 и #Финальный app-hosted итог — 16 октября 2026. Финальную сводку §13 считать текущей; ранние «пока нет результатов» — сохранённая хронология. PR/CI и ClickHouse в рамках app-тестов заново не запрашивались.

Предыдущая сверка — 15 октября, 17:26 UTC / 23:26 Бишкек. Разделы 1–9 — сводный статус; §10–11 — сохранённая хронология экспериментов, где «пока», «не найдено» и красные ожидания относятся к моменту записи. Текущий итог всех 14 P5/P6-проверок, границы доказательства и команды — §12. PR/CI сверены read-only через GitHub; результаты локальных тестов сверены с существующими логами и исходниками, в этом документационном проходе тесты и ClickHouse-запросы заново не запускались.

1. Коротко

  • App integration, 16 октября: 20/20 PASS, 0 failed/skipped/expected failures, на одной signed build в группах 18 + 1 P5 + 1 cache. Отдельные тесты slow load, one-navigation recovery, persistent one-tab, several-tabs/one-window и all-windows реализованы и прошли. Реальные app/WebKit пути, полный реестр, отрицательный P3, ограничение app P5 и сохранённые красные прогоны — #Финальный app-hosted итог — 16 октября 2026. Коммитов нет.
  • Фикс B (падение Network-процесса) проверен локально и красным прогоном на CI. Основание в данных есть, но неполное: обогащение в stuck есть, а доказать, что строки это именно мёртвый worker, нельзя (не хватает network_replaced_since_view).
  • Фикс A (первая загрузка посреди сессии) реален, но мелкий: около 1% stuck на iOS, на macOS почти не виден, чаще самоизлечивается.
  • Три самые крупные production-группы фиксами A и B не закрыты: пробуждение после idle, error 6 (iOS), «нет background view» на запуске (iOS). В новых signed app integration tests macOS теперь проверены рычаги P1 (slow idle wake) и P2 (остановка worker → настоящий WebKit error 6 около 30 s). P3 на 20 000 cacheable ресурсов не воспроизведён. Это локальные macOS-механизмы, а не доказательство причин iOS production-групп (§13).
  • P5/tab_in_context=false, macOS: найден browser→WebKit путь — закрываемое окно по-прежнему возвращает общий pinned tab, а WebKit внутри didCloseWindow удаляет его регистрацию. На системном WebKit получены точная цепочка, два failure и actual stuck; прямой didCloseTab тест не вызывает. Это продолжает ручное наблюдение 10 сентября из cpm-web-extension-breakage-findings, но теперь подтверждены текущий source contract и автоматический сценарий (§11–12).
  • P6, macOS: подтверждён условный путь через реальный WebExtensionFeatureFlagHandler: true → false → true главного webExtensions создаёт новый manager/controller, существующий WKWebView остаётся со старым controller. Реальность такого переключения в выбранных production-сессиях не доказана; обычное обновление config или только embedded flag не равны этому условию.
  • Проверки P5/P6: все 14 текущих методов имеют pass по последним соответствующим логам; единый финальный прогон всех 14 не выполнялся. Пакет: 478 XCTest, 28 opt-in skipped, 0 failures; strict SwiftLint восьми изменённых Swift-файлов — 0 нарушений. Зелёный characterization-тест подтверждает ожидаемый сбой/контроль, а не исправление CPM.
  • Осталось неизвестным: естественная утрата runtime listener metadata, P5 при tab_in_context=true, реальный iOS suspend/resume и естественный P6 без доказанного main-flag transition. Ограничение прежних SPM-тестов сохранено: их app callbacks/delegates реализованы harness по исходникам. Новый app-hosted suite (§13) выполняет настоящие AppDelegate/WindowsManager/MainWindowController/Tab и macOS native providers; app P5 ограничен одной потерянной навигацией, persistent P5/stuck доказан отдельно SPM.
  • Телеметрия PR0 пока в виде, вызвавшем вопросы о склейке пикселей. Решение по правкам не принято (раздел 8). #7384/#7385 имеют красный Danger, #7381 — красный Non-Sandbox; текущие heads и ограничения — раздел 2.

2. Цепочки PR (все в duckduckgo/apple-browsers, задачи Asana в телах PR; не читать)

Телеметрия:

Статусы ниже проверены через GitHub 15 октября в 17:26 UTC. Все семь перечисленных PR открыты; это не означает merge или shipped release. «CI зелёный» означает отсутствие failing/pending среди опубликованных checks; skipped jobs отдельно не считаются выполненными. Счётчики — записи GitHub statusCheckRollup, а не число уникальных тестов: имена checks из разных workflows могут повторяться.

PR Ветка База Что Статус
#7376 (PR0) alex/cpm-messaging-readiness-telemetry main параметры готовности и тайминга на failure/recovery пикселях (без новых пикселей) OPEN, head 619869ee84c6; CI: 39 success / 18 skipped, без pending/failure; REVIEW_REQUIRED. JA1–JA3 и неотправленный ответ — прежняя запись сессии; новые review comments в этой сверке не перечитывались
#7384 (0b) alex/cpm-readiness-baseline #7376 ежедневные базлайн-пиксели cpm_background_ready_{context_load,wake}, нагрузка устройства, возраст возобновления OPEN, head 99ecf8b12787; 36 success / 23 skipped / 1 failure: Danger; pending нет. Причина Danger здесь не устанавливалась
#7385 (0c) alex/cpm-init-progress-native #7384 init_stage на failure-пикселях (нужен JS из privacy-extension#3727) OPEN, head 46f25175c7c4; 36 success / 23 skipped / 1 failure: Danger; pending нет. Причина Danger здесь не устанавливалась
privacy-extension #3727 alex/cpm-init-progress main уведомления cpmInitProgress из встроенного расширения OPEN, head b00cc227112d; 32 success, без skipped/failure/pending; REVIEW_REQUIRED

Harness и фиксы (красный коммит, затем зелёный; не ребейзить и не форс-пушить, иначе пропадёт доказательство красного прогона):

PR Ветка База Что Статус
#7378 alex/cpm-repro-harness main harness (только тесты); 4 быстрых теста в CI около 3 с, остальное локально через CPM_REPRO=1 OPEN, head dfb96279196d; 37 success / 18 skipped, без failure/pending; REVIEW_REQUIRED. Предыдущее Danger-предупреждение о 1570 строках сохранено как историческое; в ветке есть коммит другой сессии «Require initResp in the page…»
#7379 alex/cpm-fix-init-loss harness фикс A: ожидание первой загрузки background после установки/апгрейда посреди сессии OPEN, head 7ad771e227bb; 35 success / 20 skipped, без failure/pending; прежние красный и зелёный прогоны сохраняются
#7381 alex/cpm-fix-network-crash фикс A фикс B: перезапуск background при замене Network-процесса и мёртвом worker'е OPEN, remote head 39f94e12eaa4; 28 success / 24 skipped / 6 failure, включая Test (Non-Sandbox) и итоговые отчёты. Прежний красный repro testNetworkProcessCrashKeepsEveryTabWorking с 3 ретраями подтверждён. Исправление f1085846e7ff — единственный локальный commit поверх remote head; в PR его нет, зелёного CI для него нет
(нет PR) alex/cpm-fix-pixels фикс B + PR0 пиксели background_restart и first_load_wait для измерения эффекта держим до слияния PR0, A, B

Локальные проверки фикса A: на коде без фикса оба repro-теста красные (5 из 5 итераций), с фиксом 0 из 10 потерь, 761 тест пакета зелёный. Фикс B: на main красные тесты падения Network-процесса и смерти worker'а, 5 из 5 итераций.

Новые P5/P6 тесты остаются локально в alex/cpm-web-extension-remaining-failures; в PR #7378/#7379/#7381 они не переносились. Весь CPMRepro/ этого worktree untracked, поэтому одного HEAD недостаточно для восстановления тестов. Нужны сами файлы (§12 и актуальная карта кода).

Недоделано в harness и CI: CI гоняет тесты с -test-iterations 3 -retry-tests-on-failure, красный тест должен падать стабильно; тесты WebExtensions идут внутри схем macOS Browser и iOS Browser на osx-tahoe-27. Медленные эксперименты (заморозка 45 с, заполнение кэша около 10 минут, медленный старт worker'а) локальные и не для коммита.

3. Данные (ClickHouse, 12–14 октября, версии iOS 7.240+ и macOS 1.210+)

Единица: daily-пиксель = устройство-день на причину, не пользователь и не эпизод. initialization_failed только .daily; stuck, recovered_*, reload_failed: daily+count. Параметры снимка есть только на init_failed и stuck.

iOS macOS
initialization_failed.daily 3 273 025 119 950
stuck.daily 511 660 40 842
init восстановились без reload 1 135 477 (34.7%) 30 738 (25.6%)
stuck → восстановились после M4 reload 293 557 (57.4%) 21 865 (53.5%)
reload_failed.daily 140 921 (27.5% от stuck) 17 796 (43.6%)

Когорты (по приоритету: error 6 → нет view → вкладка не подключена → Network заменён → пробуждение (view <30 с после dealloc) → недавняя загрузка (load/base_url_changed ≤2 мин) → other). Доля init → stuck:

Когорта iOS macOS Смысл роста или падения
error 6 6.1% → 21.5% 1.0% → 1.9% остаётся сломанной
нет background view 48.1% → 10.9% 3.3% → 1.8% самоизлечивается
вкладка не подключена 0.03% → 0.05% 6.2% → 8.9% остаётся сломанной
Network заменён 16.8% → 39.7% 13.6% → 26.6% остаётся сломанной
пробуждение 7.9% → 9.5% 32.5% → 32.2% без изменений
недавняя загрузка контекста 15.7% → 6.0% 20.0% → 6.4% самоизлечивается
other 5.3% → 12.5% 23.4% → 22.2%

Объёмы stuck.count в разных версиях на порядки выше daily: дашборды сравнивать только по версии и частоте.

4. Основание фиксов

  • B: доля «Network заменён» растёт в 2–2.4× при переходе init → stuck. reload_failed высок: reload расширения мёртвый worker не лечит. Оговорка: network_restarted сравнивает PID с загрузкой контекста, а не с последним view. Если view пересоздан после смены процесса, worker свежий и механизм неприменим. По заметке 8 октября механизм объясняет в лучшем случае 20–45% «здоровых» stuck. Причинность подтвердят network_replaced_since_view (нет ни в одной ветке) и init_stage=none (#7385).
  • A: сигнатура «загрузка контекста ≤2 мин, не при запуске, первый view»: iOS 129k init (3.9%) → 4.6k stuck (0.9%); macOS 505 → 118. Разовая потеря одной навигации (0.23× при переходе в stuck). Сигнатура неточна (захватывает M4-reload). Название связанной Asana-задачи («init loss on background unload») относится скорее к группе пробуждения, чем к фиксу A; несоответствие надо снять с автором задачи.

5. Картины событий (stuck.daily, 14 октября, хвост bg_events без возрастов) и воспроизведение

Картина Хвост iOS / macOS Воспроизведено? Рычаг в приложении
P1. Цикл пробуждения view>proxy_on>dealloc>view>proxy_on 16.0k / 4.3k (wake) + 16.3k / 2.4k (старше 30 с) только с искусственно занятым worker'ом (5 с: init_failed и поздний ответ; 12 с: stuck → M4); органическое пробуждение в тестах проходит. WebKit-баг 324743 (исправлен 22 сентября, нет в 26.7/27.0) гипотезы: отсчёт 4 с от готовности background («фаза 2 детектора»), ожидание готовности при пробуждении, предпрогрев
P2. «Пробуждение → error 6», циклы proxy_on>dealloc>view>proxy_on>error_E:6 (23.6k), error>dealloc>view>proxy_on>error (5.7k), после died_crash (1.5k) iOS; macOS 118 macOS: заморозка нового процесса на ≥30 с даёт error 6, корзина 25–35 с. iPhone: 4 сценария безуспешно. Главный iOS-хвост без unresponsive (в воспроизведении он есть) корень в WebKit (wakeUpBackgroundContentIfNecessary взводит 30-секундный таймер до загрузки worker'а). Гипотеза: перезагрузка расширения при возврате на передний план
P3. Контекст загружен, view нет load / load>base_url_changed 17.1k + 0.9k / 162 + 81 задержка очистки при смене baseURL воспроизведена на macOS при большом кэше (около 10 минут заполнения); iOS-гонка запуска (bg_view_creations=0, восстановление сессии, первые 30 с) не воспроизведена: холодный старт в harness'е отвечает за миллисекунды кандидат: стабильный baseURL (ветка cpm-stable-base-url: не доведён, меняет URL всем расширениям, не прогнан через harness, старая база). Фикс WebKit 318756@main, номер сборки ОС не проверен
P4. Первая загрузка load>view>proxy_on 6.8k + 2.3k / 570 да (фикс A) фикс A
P5. Фон выгружен в момент сбоя proxy_on>dealloc>view>proxy_on>dealloc 1.9k / 494 да: закрытие окна с shared pinned tab через production listener → WebKit сам удаляет сохранённый tab; точная сигнатура с tab=false (§11). Пустые listener metadata пока только test-only (§10) найден browser→WebKit путь для Mac/tab=false; фильтрации явных didCloseTab недостаточно — didCloseWindow закрывает tabs повторно. Для tab=true и iOS естественный вход не найден
P6. macOS: вкладка без расширения load>view>proxy_on>dealloc / load>view>proxy_on; варианты с extension_updated отдельно iOS ≈0 / 627 + 235 да: цикл главного webExtensions flag через production handler создаёт replacement manager, прежний WKWebView сохраняет старый controller и даёт точную сигнатуру (§11); nil controller до/после idle — test-only (§10) условный production-путь найден; фактическое переключение главного flag в выборке не подтверждено. Embedded-only cycle сохраняет controller; reload контекста старую привязку не чинит
Network заменён см. раздел 4 да (краш, worker мёртв при том же view) фикс B
«Здоровый» остаток, other iOS 12.5%, macOS 22% нет нет гипотезы

Сверка определения P5: строка выше сохраняет исходные 494 macOS. Повторная выборка 14 октября по хвосту P5 без error 6, network_restarted=true и scripts=false дала 1953 iOS / 499 macOS (§10). Разница 494/499 не устранена сверкой всех дополнительных фильтров: эти знаменатели не подменяют друг друга. В этом повторном Mac-срезе 68 снимков имеют tab_in_context=false, 374 — все три tab-флага true, 57 — неполную/неразрешённую диагностику вкладки. На iOS 646 снимков имеют все три tab-флага true, у 1299 вкладка не разрешилась, ещё 8 имеют неизвестный tab_in_context. Найденный shared-window механизм применим к сигнатуре Mac/tab=false, но происхождение всех 68 событий не доказано. Неразрешённая вкладка не тождественна tab_in_context=false.

P6 не равен P4 по одному хвосту: load>view>proxy_on при controller/scripts=false — P6, при корректной привязке и потерянном раннем init может быть A/P4. 235 событий P6 — хвост без dealloc, а не группа extension_updated. В 468/627 и 234/235 P6-снимках запуск моложе двух минут. Поэтому main-flag cycle проверяет достижимость, но его частоту и соответствие раннему запуску нужно подтвердить отдельно.

Размер группы не равен возможности её починить: P1 и P3 в основном опаздывающие ответы, а часть «сбоев» это срабатывание детектора («нет ответа за 4 с», опоздавший ответ тоже считается). iOS-когорта «other» на самом деле тот же рисунок P1, но со view старше 30 секунд.

6. Телеметрия

Что делает PR0 (#7376): на 8 failure-пикселей и 2 recovery-пикселя добавлены параметры bg_ready_after, bg_view_age, bg_load_error_after, bg_load_error_app_state, cpm_app_state, cpm_open_tabs, cpm_loading_tabs (failure) и cpm_recovery_document, cpm_recovery_after (recovery). Новых пикселей нет. Recovery-пиксели получают только свои два параметра, extension_reload_failed без параметров.

Замечания ревьюера (JA1–JA3): корреляция между пикселями из-за множества перечислений; сколько пикселей на ошибку; константность cpm_open_tabs/cpm_app_state; что такое background view.

Что верно по пикселям (исправление моей прежней ошибки): initialization_failed только daily (не больше одного на устройство в день на причину); stuck daily+count (daily и count одного события идут с одинаковыми параметрами); snapshot пересобирается при каждой отправке, поэтому быстро меняющиеся поля (bg_view_age, bg_ready_after) между init_failed и stuck различаются, медленные (app_state, open_tabs) повторяются.

Уникальность строк stuck.daily внутри дня (доля уникальных по всем параметрам):

Вариант iOS macOS
сейчас, bg_events из 12 событий 8.3% 30%
информативные токены, последние 4 2.1% 5.2%
скаляры вместо bg_events 0.96% 4.4%
без bg_events 0.25% 2.0%

Верхняя граница прироста от новых полей (синтетическое независимое равновероятное поле): со старым bg_events при десяти значениях iOS 21%, macOS 65%; со скалярами 3.6% / 15.8%. Вывод: «хуже не будет» можно утверждать только со скалярами. Главный идентификатор уже выкачен (bg_events).

bg_events: единственный источник давности загрузки (load, base_url_changed) и причины смерти (died_*). view, dealloc, proxy_on, error_* дублируют bg_view_creations и context_errors; hold/release/proxy_off равны нулю. Скаляры воспроизводят когорты загрузки и пробуждения на 100% строк. Теряется: поиск неожиданных последовательностей в остатке (смягчить удалённым флагом).

Минимум данных по гипотезам:

Поле / пиксель Вопрос Где
bg_ready_after, bg_view_age идёт ли загрузка, пробуждение или давно PR0
bg_load_error_after, bg_load_error_app_state (только при error 6) таймер 30 с, состояние приложения PR0
recovery_document, recovery_after опоздавший ответ или потерянный PR0
app_state, open_tabs (грубо, 1 / 2–5 / >5), только на initialization_failed когорта «нет view» на запуске PR0
новое: context_load_cause, context_load_age (вместо load@ и extension_updated) точная сигнатура A PR0
новое: bg_died_reason, base_url_changed смерть процесса, очистка при смене baseURL PR0
новое: мелкие корзины app_launch_age для первых 30 с (<4 / 4–10 / 10–30) гонка запуска PR0
новое на stuck: stuck_after сколько от первого сбоя до stuck PR0
новое, срочно: network_replaced_since_view доля реальных мёртвых worker'ов в когорте B (рекордер вычисляет, но только логирует; предложено 8 октября, нигде не реализовано) PR0
init_stage дошёл ли init до background #7385 (срочно)
cpm_background_ready_{context_load,wake} базлайн здоровых загрузок и пробуждений #7384
background_restart (причина, исход; убрать из него bg_ready_after, он дублирует соседний failure-пиксель), first_load_wait эффект B и A удержанная ветка
убрать loading_tabs; из таймлайна view, dealloc, proxy_on, error_*, hold/release/proxy_off

Связность стека: #7384 добавляет ещё 5 измерений на те же 8 failure-пикселей (context_load_to_background_view, cpm_low_power_mode, cpm_thermal_state, cpm_lockdown_mode, cpm_resume_age); оценку склейки надо повторять и по нему. background_restart срабатывает вместе с первым initialization_failed: общий ключ bg_ready_after.

7. Критерии успеха и опровержения

  • B подтверждается, если после релиза падают доля «Network заменён» среди stuck и reload_failed, background_restart показывает восстановление, а у таких stuck init_stage=none.
  • A подтверждается, если сокращается когорта «мид-сессионная первая загрузка» (context_load_cause). Если нет, основание слабое.
  • Остаток P1/P3: гипотеза «медленный старт, а не потеря» подтверждается, если bg_ready_after у упавших заметно хуже базлайна здоровых загрузок и recovery_document=failed_navigation.

8. Открытые решения и следующие шаги

  1. Правки PR0 (ждут вашего «да»): скаляры вместо bg_events, context_load_cause, stuck_after, network_replaced_since_view, мелкие корзины app_launch_age, убрать loading_tabs, константы только на initialization_failed, open_tabs грубо, bg_load_error_* только при error 6; после этого ребейз #7384 и #7385. Альтернатива «оставить bg_events» требует ответа ревьюеру без утверждения «хуже не будет» и обязательства перемерить после релиза.
  2. Ответ ревьюеру (публично, без цифр): JA1 (init_failed daily-only; stuck daily+count с одинаковыми параметрами; recovered несут только recovery_*), JA2 (медленные поля повторяются, быстрые различаются; планируется сократить набор), JA3 (background view = скрытый WKWebView, в котором WebKit запускает фоновый скрипт расширения; выгружается через около 30 с простоя и пересоздаётся при пробуждении). Обязательство: перемерить различимость строк после первого релиза.
  3. Запушить фикс B (f1085846e7): красный прогон подтверждён, ждёт зелёного.
  4. Судьба фикса A: слабое основание в данных, не совпадает с названием задачи; решить, сливать ли без зелёного измерения эффекта через first_load_wait.
  5. P3: довести стабильный baseURL (ограничить встроенными расширениями без web_accessible_resources, прогнать harness, перенести на свежий main), проверить номер сборки ОС с фиксом 318756@main.
  6. Локальные repro для P1–P3 и новые P5/P6 находятся в alex/cpm-web-extension-remaining-failures; отдельный PR без фиксов ещё не создавался. В этом worktree весь базовый класс gated CPM_REPRO=1, новые 14 методов дополнительно CPM_REPRO_P5_P6=1; requireLocalRun относится к выделенному PR-harness. Не путать opt-in и ветки при переносе.
  7. iOS: воспроизведение error 6 на реальном устройстве. Для Mac P5/tab=false найден и проверен путь shared pinned window → didCloseWindow → внутренний didCloseTab WebKit; для P6 проверен условный путь цикла главного flag (§11). Не установлены P5/tab=true, iOS lifecycle и реальность flag-transition в P6-сессиях.
  8. Блокеры по свежей сверке: #7376/#7378 и JS #3727 ждут ревью; #7384/#7385 стоят на #7376 и имеют failing Danger; #7381 остаётся на красном remote head без локального fix B.
  9. Найденный P5 tab lifecycle: проверить исправление полного window-close контракта, а не только списка явных didCloseTab. Направления — исключить сохраняемые shared tabs из delegate закрываемого окна либо восстановить регистрацию после window callback. Это варианты для отдельной реализации и проверки, не выполненный фикс. Контроли: закрытие второго/последнего окна, merge, перемещение и новые/существующие документы.
  10. P5 с исправной регистрацией и P6 attribution: естественная потеря listener metadata и фактический main-flag transition остаются без доказательства. Задержка main actor, длинный документ и один пропуск listener не заменяют такое доказательство; результаты отрицательных проверок сохранены в §10–12.

9. Методика и инструменты

  • Доступ: ~/Developer/grafana-data-tools (Grafana → ClickHouse metrics.pixels, read-only; авторизация через grafana-auth). Ad-hoc SQL запускался отдельным раннером через ~/DuckDuckGo/grafana-dashboards/tools/grafana-query/read-dashboard.py. Сканы по pixel LIKE без фильтра по дате, агенту и pixel_id='debug' превышают лимит строк. Временные запросы лежали в папке сессии и могут пропасть; ключевая классификация ниже.
  • Версии с диагностикой: iOS 7.240+, macOS 1.210+. Окно: 12–14 октября (3 полных дня). Ограничения: сигнатуры эвристические; network_restarted только прокси; устройства нельзя проследить от init до stuck, сравниваются только доли; доля не равна пользовательскому эффекту.
-- классификация когорт для init_failed.daily / stuck.daily
multiIf(
  errs LIKE '%ContextErrorDomain:6%', '1_error6',
  creations = '0', '2_no_background_view',
  scripts = 'false', '3_tab_not_connected',
  net = 'true', '4_network_replaced',
  has(ev,'view@30s') AND arrayExists(x -> startsWith(x,'dealloc@'), ev), '5_wake_view_lt30s',
  arrayExists(x -> x IN ('load@30s','load@1m','load@2m','base_url_changed@30s','base_url_changed@1m','base_url_changed@2m'), ev), '6_context_load_recent',
  '7_other')
-- параметры: network_restarted, context_errors, bg_view_creations, tab_has_ext_scripts, bg_events (decodeURLComponent)

10. Журнал воспроизведения P5/P6 — 15 октября 2026

Хронологический журнал первоначальных controlled экспериментов; дополнительные результаты с привязкой к production-путям и текущие ограничения — в §11.

Работа начата по запросу: добавить тесты новых гипотез и попытаться воспроизвести наблюдаемые цепочки. Продакшен-фиксы и публичные PR этим запросом не меняются.

Выбор worktree: /Users/admin/DuckDuckGo/apple-browsers.git/alex/cpm-web-extension-remaining-failures, ветка alex/cpm-web-extension-remaining-failures, HEAD a0717cf098cd1cbd32d5bc9d19aa7db0fab2399f. Здесь общий harness, production recorder, локальные эксперименты пробуждения, error 6 и очистки baseURL (P1–P3), без watchdog/фиксов A/B. Полного единого набора всех сценариев сейчас нет: базовые A/B находятся в cpm-repro-harness/фикс-ветках, init-progress E2E — в cpm-e2e-init. Существующие dirty-изменения сохранены; добавления этой работы ограничиваются тестовым harness.

Среда: macOS 26.7.1 (25G241), Xcode 27.1 (27A9275), системный WebKit. Прогон на macOS проверяет механизм; перенос на iOS требует отдельной проверки.

Уточнение данных перед тестами: read-only перепроверка ClickHouse за 14 октября, stuck.daily, iOS 7.240+/macOS 1.210+:

  • P5: в выборке хвоста proxy_on>dealloc>view>proxy_on>dealloc без error 6/Network replacement/scripts=false — 1953 iOS и 499 macOS, bg_view_alive=false во всех снимках; последняя view везде старше 30 с. Это определение чуть шире строки P5 выше, поэтому 499 не подменяет прежние 494 без сверки дополнительных фильтров. На macOS 68 снимков с tab_in_context=false; на iOS у 1299 не разрешилась вкладка для табличных диагностических полей.
  • P6: load>view>proxy_on>dealloc — 627 macOS; load>view>proxy_on — 235. В обеих подгруппах context_loaded=true, tab_in_context=true, tab_controller_match=false, tab_has_ext_scripts=false. В первых двух минутах запуска — 468/627 и 234/235. 235 — это хвост без dealloc, а не маркер extension_updated; варианты с extension_updated существуют отдельно.

Проверяемые гипотезы:

  1. P5: потеря регистрации вкладки или runtime listener приводит к отсутствию пробуждения после штатного idle; сравнить content-script send/error, background receive, текущую view и параметры failure-пикселя.
  2. P5: долгий документ/пауза приложения позволяют background выгрузиться до диагностического снимка; отличить запоздалый снимок от неработающего worker.
  3. P6: вкладка создана без CPM controller, затем попала в openTabs при загрузке контекста.
  4. P6: существующая вкладка сохранила controller предыдущего менеджера; новый контекст видит вкладку через openTabs, но не внедряет в неё скрипты.

Текущий статус: читаю точки расширения harness; тесты и результаты ещё не получены. Контролируемая инъекция состояния будет явно отделена от естественного воспроизведения.

P5/P6 — тесты добавлены, начат первый прогон

В CPMReproLifecycleHypothesisTests.swift добавлены 7 локальных XCTest-проверок, отдельный opt-in CPM_REPRO_P5_P6=1 поверх CPM_REPRO=1:

  • P6: отсутствующий controller до idle; тот же случай после штатной выгрузки и после reload контекста; перенос существующего WKWebView в replacement manager с сохранением прежнего controller.
  • P5: забытая в WebKit, но оставшаяся открытой вкладка после двух idle-циклов; отсутствие runtime listener после следующего пробуждения; два здоровых документа с реальной задержкой image-subresource на 35 с; те же документы с искусственно потерянными dashboard-уведомлениями.

Harness позволяет отдельно выбирать controller вкладки, переносить существующую view и подавлять конкретный native method только в тестах. CPMReproDelayedResource.swift удерживает реальный subresource через WKURLSchemeHandler; навигационные finish-события не подделываются. Проверки требуют actual stuck от production monitor, полный набор диагностических признаков, content-script initResp для здоровых контролей и восстановление после снятия инъекции.

Первым запущен testTabWithoutControllerMatchesP6BeforeIdleUnload; пока идёт сборка, результата нет. Логи сессии: /private/tmp/cpm-p5-p6-20261015/. Команда сборки: CPM_REPRO=1 CPM_REPRO_P5_P6=1 swift test --disable-automatic-resolution --filter CPMReproLifecycleHypothesisTests/testTabWithoutControllerMatchesP6BeforeIdleUnload.

Первый результат P6 до idle

Сборка прошла. Первый тест получил реальный initialization_failed и затем stuck с load>view>proxy_on, context_loaded=true, tab_in_context=true, tab_controller_match=false, tab_has_ext_scripts=false, bg_view_alive=true, bg_view_creations=1. Content marks пустые: CPM не внедрён. Новая вкладка с current controller получила initResp и вызвала recovery.

Прогон пока красный (1 assertion): дополнительное ожидание network_restarted=false не выполнено — поле true. Падения/замены Network-процесса тест не инъецирует. Проверяю холодный старт Network до контекста: recorder сравнивает PID с context-load, что может смешивать первичный запуск и замену. Результат нельзя объявлять полным совпадением всех признаков до этой проверки. Лог: build-and-p6-before-idle.log. SwiftLint пяти изменённых файлов прошёл с --no-cache, 0 нарушений; обычный режим упёрся в недоступный для записи кэш.

Уточнение harness и первый зелёный P6

Проверенный прогрев default WKWebsiteDataStore перед context-load даёт ненулевой Network PID; в последующем failure network_restarted=false. Это сняло лишнее отличие первого холодного прогона. testTabWithoutControllerMatchesP6BeforeIdleUnload теперь зелёный: совпадают хвост и все проверяемые признаки, новая правильно подключённая вкладка работает. Механизм получен контролируемым отсутствием controller; естественный путь создания такой вкладки в приложении ещё не найден.

Проверка replacement manager сначала не дошла до failure-пикселя: teardown первого harness снимает navigationDelegate, и при переносе view его надо восстановить. Исправлен только тестовый adoptTab; это ошибка постановки эксперимента, не результат о CPM. Начат полный прогон семи тестов с исправлением: p5-p6-full.log.

Дополнительный контроль P6

Добавлен восьмой тест testCurrentControllerBeforeContextLoadRemainsHealthy: создать вкладку до завершения context-load, но сразу с правильным controller, и потребовать initResp, скрипты и отсутствие initialization_failed. Это отделяет обычный ранний startup от отсутствующей привязки controller. Он будет выполнен после текущего запущенного бинарника; результат пока не получен.

Подтверждены два контролируемых механизма

P6, replacement manager — зелёный, 9.186 с. Сначала существующая вкладка получила initResp со старым менеджером. После teardown старого менеджера её тот же WKWebView и прежний controller зарегистрированы в replacement manager. Получены два failure и actual stuck с load>view>proxy_on, context/tab=true, controller/scripts=false, view=true, creations=1, errors=none, network_restarted=false. Новая вкладка replacement manager получила initResp. Это воспроизводит сохранённую привязку; естественный триггер замены менеджера не доказан.

P5, забытая живая вкладка — зелёный, 72.744 с. Два нормальных initResp и две реальные idle-выгрузки, затем test-only didCloseTab без удаления WKWebView. Получен actual stuck с load>view>proxy_on>dealloc>view>proxy_on>dealloc, creations=2, view=false, context=true, tab=false, controller/scripts=true, errors=none, network_restarted=false. Content script отправляет init и получает init-error. После didOpenTab эта же вкладка снова получила initResp. Механизм подходит под P5-подгруппу с tab_in_context=false; его доля в проде и естественное действие приложения, создающее нарушение lifecycle, этим тестом не установлены.

Оба результата находятся в p5-p6-full.log; другие сценарии продолжают выполняться.

Полный первый прогон: положительные и отрицательные результаты

Прогон p5-p6-full.log завершён: 7 тестов, 4 зелёных и 3 красных (11 assertions), 273.8 с. Не все красные результаты означают дефект harness:

  • P6 после idle — зелёный, 38.658 с. Получена цепочка load>view>proxy_on>dealloc с исходной сигнатурой controller/scripts=false, tab/context=true. Reload контекста не исправил существующую вкладку; новая правильно подключённая вкладка получила initResp.
  • Потеря runtime listener только на одном wake не воспроизвела P5. Инъекция действительно потеряла первый init (native dashboard не пришёл), но после восстановления файла и следующего idle новый worker принял следующий init и прислал initResp, stuck=0. Ожидание устойчивого P5 в тесте было неверным; превращаю его в отрицательную characterization-проверку этого результата.
  • Долгий документ: первый image-subresource действительно удерживался 35 с. Background выгрузился примерно на 32 с, но на finish навигации в 35 с WebKit снова создал background; на failure в 39 с view уже жива. При искусственной потере dashboard получен stuck с живой view, а не P5. Здоровый документ получил initResp и не вызвал failure.
  • Ошибка постановки второго долгого документа: одинаковый URL image взялся из кэша, поэтому второй load не был долгим. Исправляю уникальный URL на каждую навигацию и повторю оба контроля.
  • Наблюдение о trace: watcher держал strong reference на view через 50-мс await. При переходе сразу после context._backgroundWebView=nil dealloc старой view иногда попадал после создания новой. Убираю удержание через await и буду ждать одновременно отсутствия WebKit background и dealloc в production recorder, чтобы не менять исследуемую последовательность самим наблюдением.

Все неподтверждённые результаты выше остаются отрицательными/ограниченными; совпадение P5 пока подтверждено только для test-only потери регистрации живой вкладки.

Уточнённые проверки P5 — начат повтор

Теперь 9 тестов. В новом testNoRuntimeListenerMetadataFromFirstStartupMatchesP5 runtime listener не регистрируется с самого первого старта; затем background явно загружается второй раз и оба раза штатно выгружается. Это отдельно проверяет пустые listener metadata, которые нельзя было получить простым пропуском регистрации на одном wake после здорового старта. Условия искусственные; естественная утрата metadata не доказана. После проверки инъекция снимается, выполняются explicit background load и проверка initResp.

Первоначальный listener-тест переименован в testMissingRuntimeListenerOnOneWakeRecoversAfterNextIdle и проверяет реально наблюдённое восстановление, отсутствие stuck и третью view.

Оба 35-секундных документа теперь используют разные URL image. Ожидания изменены по наблюдению первого прогона: здоровый длинный документ — no failure; потеря dashboard — stuck с живой view, не P5. Расширен test-only background trace на все типы autoconsent-сообщений, чтобы установить, какое сообщение повторно будит worker на finish.

Watcher больше не удерживает WKWebView через await, а idle-helper требует и освобождения background в WebKit, и dealloc по recorder. Повтор пяти новых/уточнённых проверок: p5-refined-and-controls.log.

Здоровый startup-контроль подтверждён

testCurrentControllerBeforeContextLoadRemainsHealthy — зелёный, 1.190 с: вкладка с правильным controller проходит стартовый gate, получает initResp и скрипты, initialization_failed=0. Одной ранней загрузки контекста для P6 в этом сценарии недостаточно.

Повтор one-wake listener подтвердил восстановление и отсутствие stuck. Остался только ошибочный assertion формата счётчика: pixel bg_view_creations для трёх view равен 3_to_5, а не строке 3. Проверка заменена на raw backgroundWebViewCreateCount == 3; будет повторена. Расширенный trace действительно показывает дополнительные report/eval после init — проверяю их связь с повторным пробуждением длинного документа.

P5 с исправными tab/controller/scripts — воспроизведён

testNoRuntimeListenerMetadataFromFirstStartupMatchesP5 зелёный, 70.149 с. Пропуск runtime listener с первого старта, две реальные idle-выгрузки (между ними explicit background load), затем две навигации дали actual stuck: load>view>proxy_on>dealloc>view>proxy_on>dealloc, view=false, creations=2, context/tab/controller/scripts=true, errors=none, network_restarted=false. Content script отправляет init; новые view не создаются. После разрешения регистрации listener в установленной тестовой копии скрипта, explicit background load и навигации получен initResp.

Это соответствует P5 с внешне исправной вкладкой. Доказан механизм пустых listener metadata при test-only инъекции; естественная потеря metadata в приложении/WebKit не найдена. В отличие от него, одноразовый пропуск listener после здорового старта восстанавливается на следующем wake.

Два долгих документа с потерянными dashboard после устранения image-cache — зелёная отрицательная проверка, 80.181 с: оба load действительно по 35 с, в обоих документах есть initResp, production monitor получает stuck, но в его snapshot view=true и цепочка заканчивается view>proxy_on. Это воспроизводит сбой диагностического канала, но не P5. Здоровый контроль ещё выполняется. Лог: p5-refined-and-controls.log.

Долгие документы: повтор завершён

Здоровый контроль с двумя разными 35-секундными image — зелёный, 70.683 с: оба документа получили initResp, initialization_failed=0 и stuck=0. На finish background снова просыпается: trace показывает recv report и recv eval примерно на 35.39 с после background none на 32.35 с. Таким образом, в проверенном сценарии длинная загрузка сама по себе не даёт P5.

Повтор пяти тестов завершён: четыре зелёных, один красный исключительно из-за ранее описанного сравнения bucket со строкой «3» (исправлено). Начат финальный прогон оставшихся сценариев и исправленного assertion: p5-p6-final-regressions-and-stall.log.

Добавлен десятый эксперимент testMainActorStallDelaysActualFailureSnapshots: после реального initResp искусственно теряется dashboard, затем test host main actor дважды блокируется на 35 с при ожидающем production timeout. Тест требует actual failures/stuck и восстановления; совпадение P5 отдельно записывается как matchesP5, а не предполагается. Это исследование задержанного выполнения host callbacks, не реальная iOS suspend/resume.

Задержка host main actor: P5 не получен

testMainActorStallDelaysActualFailureSnapshots — зелёный, 71.027 с; результат классификации matchesP5=false. При двух блокировках main actor на 35 с реальные failure-пиксели пришли через 35.42 и 70.68 с, второй дал stuck. В обоих снимках view=true, creations=1, хвост остаётся load>view>proxy_on, context/tab/controller/scripts=true, errors=none, network_restarted=false. InitResp пришёл в документы до пауз; dashboard терялся намеренно. После снятия инъекции новая вкладка работает.

Это подтверждает задержку deadline, но в проверенном порядке callbacks не воспроизводит P5. Настоящие iOS suspend/resume и остановка всего процесса не проверялись; блокировка main actor не подменяет их.

Финальный прогон также повторно подтвердил replacement-manager P6 (9.167 с) и P5 с забытым live tab (72.737 с) после устранения удержания view watcher'ом.

Итог локальных repro-проверок

Финальный прогон шести сценариев — зелёный: 6 тестов, 0 failures, 262.676 с. Исправленный one-wake listener-тест зелёный (62.633 с). P6 до/после idle повторно зелёные (8.569 / 38.543 с).

С учётом четырёх зелёных независимых проверок из предыдущего прогона все 10 актуальных методов добавленного класса имеют зелёный результат. Это проверено несколькими запусками; единого прогона всех десяти после последних добавлений не было. Сводка по каждому методу и исходному логу: /private/tmp/cpm-p5-p6-20261015/results.json, генератор summarize.py.

Что воспроизведено:

  • P6: отсутствующий controller до и после idle; сохранённый controller существующего WKWebView после replacement manager. Reload контекста не исправляет такую вкладку; новая вкладка работает.
  • P5: потеря WebKit-регистрации сохранившейся живой вкладки; отсутствие runtime listener metadata с первого старта. Совпадают хвост, actual stuck и диагностические признаки; после адресного снятия инъекции работа восстанавливается.

Что P5 не дало на macOS 26.7.1: однократная потеря listener на wake после здорового старта, два действительно долгих документа, две задержки main actor на 35 с. Потеря dashboard при дошедшем initResp даёт диагностический stuck с живой view.

Ограничения: все положительные воспроизведения требуют test-only условия; естественные production-триггеры отсутствующего controller/утраты listener metadata не найдены. Настоящая iOS suspension не проверена. Фиксы поведения приложения этим исследованием не добавлялись.

Запущен обычный пакетный прогон swift test --disable-automatic-resolution --skip-build без repro-флагов; результат будет записан ниже. Финальный strict SwiftLint семи изменённых файлов: 0 нарушений (--no-cache). SHA-1 существующего dirty Package.resolved не изменился: 7ccce20acd452656c9bf2d2fa186d044e4a8185f.

Финальная проверка пакета и файлы

Обычный пакетный прогон завершён успешно: swift test --disable-automatic-resolution --skip-build, 474 XCTest, из них 24 opt-in repro пропущены, 0 failures, 11.344 с. Лог: /private/tmp/cpm-p5-p6-20261015/package-tests.log. Отдельный Swift Testing runner: 0 тестов. Длительные эксперименты запускались отдельно с обоими repro-флагами; все 10 текущих методов имеют зелёный результат в сводке.

Изменены только тестовые файлы этой работы внутри выбранного worktree:

  • добавлены CPMRepro/CPMReproLifecycleHypothesisTests.swift (10 тестов) и CPMRepro/CPMReproDelayedResource.swift;
  • расширены CPMReproHarness.swift, CPMReproTabs.swift, CPMReproSupport.swift, CPMReproPrelude.swift, CPMReproTestCase.swift;
  • existing dirty production/telemetry changes не включались в эту работу, Package.resolved сохранил первоначальную контрольную сумму. Commit/PR/push не делались.

Команда полного повторения новых экспериментов из SharedPackages/WebExtensions:

CPM_REPRO=1 CPM_REPRO_P5_P6=1 swift test --disable-automatic-resolution --filter CPMReproLifecycleHypothesisTests

Эта полная команда приведена для повторения: в текущей сессии десять методов проверены раздельными группами, а не одним финальным запуском всех десяти.

Текущий тест Последний результат, с Лог
CurrentControllerBeforeContextLoadRemainsHealthy pass, 1.190 p5-refined-and-controls.log
TabWithoutControllerMatchesP6BeforeIdleUnload pass, 8.569 p5-p6-final-regressions-and-stall.log
TabWithoutControllerMatchesP6AfterIdleUnload pass, 38.543 p5-p6-final-regressions-and-stall.log
ExistingTabKeepsPreviousManagerController pass, 9.167 p5-p6-final-regressions-and-stall.log
ForgottenLiveTabDoesNotWakeBackgroundAndMatchesP5 pass, 72.737 p5-p6-final-regressions-and-stall.log
MissingRuntimeListenerOnOneWakeRecoversAfterNextIdle pass, 62.633 p5-p6-final-regressions-and-stall.log
NoRuntimeListenerMetadataFromFirstStartupMatchesP5 pass, 70.149 p5-refined-and-controls.log
SlowHealthyDocumentsWakeBackgroundAgainAtFinishWithoutFailure pass, 70.683 p5-refined-and-controls.log
SlowDocumentsWithLostDashboardDoNotMatchP5 pass, 80.181 p5-refined-and-controls.log
MainActorStallDelaysActualFailureSnapshots pass, 71.027; matchesP5=false p5-p6-final-regressions-and-stall.log

Зелёный результат characterization-теста означает, что проверенное условие и ожидаемый исход подтверждены. Он не означает исправление CPM или доказательство естественного production-триггера.

11. Привязка P5/P6 к достижимым production-путям

Продолжение после замечания: положительный test-only fault доказывает механизм, но не объясняет его возникновение у пользователя. Дальше нужны конкретные caller → browser state → WebKit side effect.

Найден кандидат P5: закрытие окна с shared pinned tab

Проверено в исходниках текущего repro-worktree и macOS release tag 1.210.0+macos:

  • MainWindowController.windowWillClose отфильтровывает сохранённые общие pinned tabs перед явными didCloseTab, но после windowControllersManager.unregister(self) всё равно вызывает eventsListener.didCloseWindow(self).
  • MainWindowController+WKWebExtensionWindow.tabs(for:) возвращает loadedPinnedTabs + loadedTabs; MainViewController.windowWillClose и WindowControllersManager.unregister эти коллекции не очищают.
  • В локальном WebKit checkout 99051d5d08cd9f19ec76ce599f7539d070b1ae09, WebExtensionContextCocoa.mm:1351–1367, didCloseWindow вызывает didCloseTab для каждого элемента window.tabs(). Фильтрация явных callbacks браузером не предотвращает этот внутренний вызов.
  • didCloseTab удаляет tab из m_tabMap; runtimeSendMessage проверяет tab до пробуждения background и при отсутствии возвращает tab not found. Это возможная связь с P5/tab_in_context=false.

Начат эксперимент на системном WebKit: один и тот же живой tab в двух windows; закрыть одну через production WebExtensionEventsListener.didCloseWindow, сохранить tab в другой, без прямого didCloseTab, изменения listener или выгрузки context. До выполнения это кандидат, не подтверждённое воспроизведение.

Проверка истории также отсекла старые объяснения: исправление Close Other Tabs и фильтр shared pinned tabs уже находятся в 1.210.0+macos; синхронное создание manager до state restoration существует с февраля 2026. Их прежние версии нельзя выдавать за текущий production-триггер.

P5: production callback → WebKit side effect подтверждены

Два новых теста зелёные, 0 failures:

  • testClosingWindowForgetsSharedPinnedTabThroughWebKitAndMatchesP5 — 73.033 с. Один живой tab предоставлен двумя window delegates, как shared pinned tab в macOS. Две успешные инициализации и две штатные idle-выгрузки. Затем только unregister закрываемого окна и production eventsListener.didCloseWindow — без явного didCloseTab, изменения listeners, ручной выгрузки context или замены manager. Сразу после callback tab_in_context меняется true → false, WKWebView остаётся тем же и tab сохраняется в другом окне.
  • Две последующие навигации дали actual initialization failures и stuck: load>view>proxy_on>dealloc>view>proxy_on>dealloc, creations=2, view=false, context=true, tab=false, controller/scripts=true, errors=none, network_restarted=false. Content script получает init-error. Новая вкладка работает; didOpenTab восстанавливает исходную.
  • testClosingUnrelatedWindowPreservesRegisteredTab — 1.205 с: закрытие окна, не возвращающего эту вкладку, сохраняет tab_in_context=true, initResp и 0 failures.

Лог: /private/tmp/cpm-p5-p6-20261015/production-window-close.log. Это source-linked воспроизведение на системном WebKit под macOS 26.7.1 через production listener и неизменённую логику CPM, а не произвольное закрытие живого tab. Harness использует window/tab delegates, повторяющие контракт macOS; сам MainWindowController/AppKit целиком в SPM-тест не загружен. Следовательно, browser call chain подтверждён чтением исходников, внутренний эффект WebKit и P5 — исполнением теста.

Область объяснения: P5/tab_in_context=false (в выбранном Mac-срезе 68 событий). Это не доказательство, что все эти события были caused by shared pinned windows, и не объяснение P5/tab_in_context=true либо iOS.

Тесты WindowsManagerPopupTests в macOS проверяют только список явных didCloseTab и поэтому не проверяют повторное закрытие tabs внутри системного didCloseWindow.

P6: найден условный production-путь, начата проверка

В AppDelegate.setupWebExtensions главная webExtensions flag подключена к production WebExtensionFeatureFlagHandler. Disable вызывает uninstallAllExtensions, затем callback очищает coordinator/manager. Enable вызывает initializeWebExtensions: если manager отсутствует, создаётся новый controller, а существующие WKWebView не пересоздаются. WebKit WebExtensionTab::webView отвергает view, чей controller не равен controller контекста.

Добавлены отдельные source-linked тесты CPMReproProductionFlagTests:

  • явное true → false → true главного flag через реальный handler, с host callbacks по AppDelegate и существующей вкладкой, предоставленной WebKit при загрузке replacement context;
  • negative control: цикл только embedded flag, при неизменном главном, должен сохранить controller и initResp;
  • внутри первого теста есть контроль true → true: повторный одинаковый флаг не должен заменять manager.

Это проверка достижимости механизма через production flag handler, не утверждение, что flag переключался у пользователей. И macOS, и iOS имеют defaultValue enabled для главного flag; подтверждения реального false → true в рассматриваемой выборке нет. Обычная задержка скачивания config сама по себе не доказывает этот путь.

Первый прогон flag-путей: P6 получен, контроль обнаружил известную startup race

production-flag-cycle.log: главный flag-тест зелёный, 9.244 с. Реальный handler по false удалил contexts и host references; по true создан новый manager при сохранённом старом WKWebView/controller. Получены два actual failures и stuck с точным load>view>proxy_on, context/tab/view=true, controller/scripts=false, creations=1, errors=none, network_restarted=false. true → true manager не заменил; новая вкладка replacement manager работает. Это подтверждает условный production-путь P6 при реальном переключении главного flag.

Embedded-контроль красный, 6.119 с, 3 assertions: controller/scripts/tab=true, но первая навигация в 0.704 с ушла до sw activate в 0.747 с и потеряла init. Таким образом, «embedded cycle всегда здоровый сразу после sync» — неверное ожидание: здесь воспроизводится уже известный механизм A / P4, а не P6. Не считаю красный результат ошибкой постановки.

Для изоляции именно controller-binding уточняю контроль: после sync явно дождаться loadEmbeddedExtensionBackgroundContent, затем требовать initResp без failures при прежнем controller. Этот readiness wait — контрольное условие теста; production fix A данной работой не добавляется.

P6: уточнённый прогон завершён

production-flag-cycle-final.log: 2 tests, 0 failures, 10.237 с:

  • testMainFeatureFlagCycleRecreatesManagerAndMatchesP6 — 9.252 с; повторное подтверждение P6 через true → false → true главного flag.
  • testEmbeddedFeatureFlagCycleKeepsControllerAfterBackgroundBecomesReady — 0.985 с; прежний controller и scripts=true, после explicit readiness wait initResp и 0 failures. Первоначальный красный контроль с потерей первой навигации сохранён в предыдущем логе и описан выше как известный A.

Таким образом, добавлены четыре новых source-linked проверки: две window-close и две flag-cycle; все четыре текущих метода зелёные в двух запусках. Они выполняют production listener/flag handler/manager/CPM и системный WebKit; app-side host callbacks и window delegates реализованы harness по исходникам macOS, приложение целиком не запущено.

Оставшиеся неизвестные: происхождение P5 с tab_in_context=true; реальные iOS lifecycle-условия; было ли переключение главного flag в P6-сессиях. Главный flag default enabled, поэтому config fetch с неизвестной задержкой не считается доказанным триггером.

Проверка после source-linked дополнений

  • Обычный пакет: swift test --disable-automatic-resolution --skip-build — 478 XCTest, 28 opt-in skipped, 0 failures, 10.469 с. Лог package-tests-production-paths.log.
  • Strict SwiftLint всех восьми файлов этой работы — 0 violations (--no-cache).
  • summarize.py и results.json обновлены: 14 текущих методов (10 первоначальных + 4 source-linked) имеют pass по последним соответствующим логам. Это несколько групп запусков, не единый прогон всех 14.
  • Изменения этой работы остались в SharedPackages/WebExtensions/Tests/WebExtensionsTests/CPMRepro/: дополнен harness windows/initial-tabs, два P5 window tests в CPMReproLifecycleHypothesisTests.swift, новый CPMReproProductionFlagTests.swift. Новых production-правок нет; первоначальный tracked dirty diff — те же 10 файлов / 164 additions / 8 deletions. SHA-1 Package.resolved прежний 7ccce20acd452656c9bf2d2fa186d044e4a8185f.

Точки для проверки call chain:

  • browser: macOS/DuckDuckGo/MainWindow/MainWindowController.swift:611–640, macOS/DuckDuckGo/WebExtensions/MainWindowController+WKWebExtensionWindow.swift:31, macOS/DuckDuckGo/Windows/View/WindowControllersManager.swift:178;
  • WebKit: Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionContextCocoa.mm:1351–1367 (window closes tabs), :1419 (forgetTab), Cocoa/API/WebExtensionContextAPIRuntimeCocoa.mm:129–141 (tab check before wake);
  • P6 browser: macOS/DuckDuckGo/Application/AppDelegate.swift:2153–2166,2209–2221; shared production handler: Sources/WebExtensions/FeatureFlag/WebExtensionFeatureFlagHandler.swift; WebKit validates controller in WebExtensionTabCocoa.mm:339–360.

Повторить именно четыре новых source-linked теста из alex/cpm-web-extension-remaining-failures/SharedPackages/WebExtensions:

CPM_REPRO=1 CPM_REPRO_P5_P6=1 swift test --disable-automatic-resolution --filter 'CPMReproProductionFlagTests|CPMReproLifecycleHypothesisTests/testClosing'

12. Актуальный итог P5/P6: доказательства, полный реестр и повторение

Границы доказательства и сигнатуры

Механизм Что проверено Чего это не доказывает
P5: закрытие окна с общим pinned tab Browser call chain прочитан в текущем worktree и tag 1.210.0+macos; production listener и системный WebKit исполнены. После window callback tab сохраняется в другом window delegate, но tab_in_context становится false; два failure → stuck с нужным хвостом Полное приложение/AppKit в SPM не запускались; неизвестно, какая доля production P5 связана с этим действием. Не объясняет P5/tab=true или iOS
P5: нет runtime listener metadata При подавлении регистрации с первого старта, двух настоящих idle-выгрузках и explicit load между ними получен P5 с исправными tab/controller/scripts Это test-only исходное условие. Естественное исчезновение metadata и подходящий production caller не найдены
P6: главный flag меняет manager Реальные handler/uninstall/load/context, существующая view при загрузке replacement context; прежний controller сохраняется и воспроизводит P6 Смена состояния flag задаётся тестом; события false→true в production-выборке не установлены. Default главного flag enabled на обеих платформах
P6: nil controller До и после idle контекст знает tab, но scripts отсутствуют; context reload не исправляет конфигурацию созданного WKWebView Это controlled construction. Синхронный manager до state restoration уже есть с февраля 2026; обычный ранний startup сам по себе причиной не оказался
Длинные документы / потерянный dashboard / задержка main actor Требуется отдельно смотреть page initResp, native arrival и actual failure snapshot. Длинные здоровые документы работают; lost dashboard/stall дают stuck при живой view Ни один из этих сценариев не воспроизвёл P5 на проверенной macOS. Host main-actor stall не равен настоящему iOS suspend/resume

Две положительные сигнатуры:

  • P5: load>view>proxy_on>dealloc>view>proxy_on>dealloc; creations=2, view=false, context=true, errors=none, network_restarted=false. Shared-window/прямой didCloseTab: tab=false, controller/scripts=true. Отсутствие listener с первого старта: tab/controller/scripts=true.
  • P6: load>view>proxy_on (view=true) либо load>view>proxy_on>dealloc (view=false); creations=1, context/tab=true, controller/scripts=false, errors=none, network_restarted=false. Main-flag source-linked тест проверяет вариант с живой view; вариант после idle проверен с nil controller.

tab_controller_match=false означает nil или другой controller: recorder требует ненулевую identity-совместимость. tab_in_context проверяет identity tab среди context.openTabs; tab_has_ext_scripts ищет user scripts в content world текущего UUID контекста. Эти поля не являются взаимозаменяемыми и не доказывают page receipt.

Все 14 текущих P5/P6-проверок

Все результаты ниже извлечены из локальных логов; название без суффикса файла — метод XCTest. Первые 12 находятся в CPMReproLifecycleHypothesisTests.swift, последние 2 — в CPMReproProductionFlagTests.swift.

Метод Последний результат, с Наблюдённый смысл pass Исходный лог
testCurrentControllerBeforeContextLoadRemainsHealthy pass, 1.190 Контроль P6: correct controller до context-load → initResp, 0 failures p5-refined-and-controls.log
testTabWithoutControllerMatchesP6BeforeIdleUnload pass, 8.569 Инъекция nil controller → P6, view=true; новая вкладка работает p5-p6-final-regressions-and-stall.log
testTabWithoutControllerMatchesP6AfterIdleUnload pass, 38.543 nil controller → P6 после idle; context reload не чинит эту view p5-p6-final-regressions-and-stall.log
testExistingTabKeepsPreviousManagerController pass, 9.167 Прямой controlled replacement → P6; прежние view/controller сохранены p5-p6-final-regressions-and-stall.log
testClosingWindowForgetsSharedPinnedTabThroughWebKitAndMatchesP5 pass, 73.033 Browser callback → WebKit: shared-window close → P5/tab=false; восстановление и healthy новая вкладка production-window-close.log
testClosingUnrelatedWindowPreservesRegisteredTab pass, 1.205 Контроль window close: без общей вкладки initResp и 0 failures production-window-close.log
testMainActorStallDelaysActualFailureSnapshots pass, 71.027 Две паузы host main actor по 35 с → actual stuck, но matchesP5=false; view жива p5-p6-final-regressions-and-stall.log
testForgottenLiveTabDoesNotWakeBackgroundAndMatchesP5 pass, 72.737 Прямой test-only didCloseTab → P5/tab=false; didOpenTab восстанавливает p5-p6-final-regressions-and-stall.log
testMissingRuntimeListenerOnOneWakeRecoversAfterNextIdle pass, 62.633 Один wake без listener после здорового старта → следующий wake здоровый, stuck=0 p5-p6-final-regressions-and-stall.log
testNoRuntimeListenerMetadataFromFirstStartupMatchesP5 pass, 70.149 Test-only отсутствие listener с первого старта; два idle и explicit load между ними → P5/all tab flags=true p5-refined-and-controls.log
testSlowHealthyDocumentsWakeBackgroundAgainAtFinishWithoutFailure pass, 70.683 Два 35-секундных image → initResp, report/eval снова будят background на finish, 0 failures p5-refined-and-controls.log
testSlowDocumentsWithLostDashboardDoNotMatchP5 pass, 80.181 Два 35-секундных image + потеря dashboard → initResp и stuck с живой view; не P5 p5-refined-and-controls.log
testMainFeatureFlagCycleRecreatesManagerAndMatchesP6 pass, 9.252 Production flag handler true→false→true → P6; true→true controller не меняет production-flag-cycle-final.log
testEmbeddedFeatureFlagCycleKeepsControllerAfterBackgroundBecomesReady pass, 0.985 Embedded-only cycle сохраняет controller; initResp после explicit background-ready wait production-flag-cycle-final.log

Зелёный тест с ожидаемым сбоем означает воспроизведение механизма; зелёный negative control означает отсутствие предполагаемой сигнатуры в проверенных условиях. Исходные неудачные ожидания, ошибки harness и их исправления не удалены из §10–11. Единого итогового запуска всех 14 не было: green подтверждён раздельными группами. 478 XCTest обычного пакета включают 28 пропущенных opt-in; они не заменяют выполненные длительные repro.

Среда, исходники и устройство harness

  • Worktree: /Users/admin/DuckDuckGo/apple-browsers.git/alex/cpm-web-extension-remaining-failures, HEAD a0717cf098cd1cbd32d5bc9d19aa7db0fab2399f; новые тесты untracked, поэтому checkout одного commit их не восстановит.
  • macOS 26.7.1 (25G241), Xcode 27.1 (27A9275), системный WebKit; bundled embedded extension 2026.9.29. Проверка source: /Users/admin/Downloads/WebKit_sparse, HEAD 99051d5d08cd9f19ec76ce599f7539d070b1ae09. Этот checkout не доказывает, что весь его код соответствует shipped binary; нужный эффект отдельно проверен на system WebKit.
  • Harness запускает production extension manager/coordinator, recorder, health monitor и native autoconsent handler, реальные WKWebView и WKWebExtension contexts. Host window/tab delegates и callbacks flag enable/disable собраны по исходникам macOS, не импортированы из целого приложения.
  • База Options: startup navigation gate и M4 включены. В P5/P6-тестах M4 выключен, чтобы actual stuck не был сразу затёрт automatic reload; navigation gate остаётся включён. Перед context load прогревается Network-процесс default data store. Без прогрева PID=0→ненулевой может дать network_restarted=true без crash; это было причиной первого несовпадения.
  • Временная распакованная копия bundled extension содержит test prelude/DOM marks и trace. Production zip не менялся. В source-linked window/flag сценариях listener не подавляется, dashboard не теряется намеренно, background startup не задерживается. Это не byte-identical bundle без instrumentation.
  • Отдельные controlled сценарии используют: nil/старый controller, suppressed runtime listener, suppressed native dashboard method, задержанный image через WKURLSchemeHandler или блокировку main actor. Их нельзя выдавать за найденный естественный production-trigger.
  • Content marks требуют initresp для здоровых контролей; native dashboard arrival отдельно измеряет production detector. Trace не должен удерживать view: watcher исправлен, чтобы strong reference не пересекал 50-мс await. Idle-helper требует одновременно context._backgroundWebView == nil и backgroundWebViewAlive == false в recorder.
  • Долгие image имеют уникальный URL на каждую навигацию, иначе второй документ берётся из cache. adoptTab восстанавливает navigationDelegate после teardown предыдущего harness. Счётчик 3 view в pixel — bucket 3_to_5; raw assertion использует backgroundWebViewCreateCount == 3.
  • Три новых файла: CPMReproLifecycleHypothesisTests.swift, CPMReproDelayedResource.swift, CPMReproProductionFlagTests.swift. Пять дополненных: CPMReproHarness.swift, CPMReproTabs.swift, CPMReproSupport.swift, CPMReproPrelude.swift, CPMReproTestCase.swift. Существующая dirty telemetry/Package.resolved не включалась в эту работу; тестовыми изменениями production поведение не исправлялось.

Артефакты и история красных прогонов

Все перечисленные файлы находились в /private/tmp/cpm-p5-p6-20261015/ на момент документирования. Папка временная: долговечная сводка значимых исходов, причин красных assertions и ограничений сохранена здесь и в §10–11; сами raw logs могут исчезнуть.

Файл Результат и как его читать
build-and-p6-before-idle.log P6 получен, но network_restarted=true из-за холодного PID attribution; не полное совпадение
p6-fast.log 2 теста / 1 failure: перенос старой view упёрся в снятый navigationDelegate; исправлен test adoptTab. P6 до idle уже green
p5-p6-full.log 7 тестов / 11 failing assertions, 273.808 с: one-wake listener сам восстановился; finish длинного документа разбудил background; повторный image был cached. Эти результаты разделены на отрицательные выводы и ошибку постановки, а не скрыты
p5-refined-and-controls.log 5 тестов / 1 failure, 289.130 с: 4 green, оставшийся assertion ожидал строку «3» вместо bucket «3_to_5». Исправлен raw count assertion и повторён
p5-p6-final-regressions-and-stall.log 6 тестов / 0 failures, 262.676 с; задержка main actor даёт matchesP5=false
production-window-close.log 2 теста / 0 failures, 74.238 с; source-linked P5 и unrelated-window control
production-flag-cycle.log 2 теста / 3 assertions, 15.363 с: main flag P6 green; embedded-only first init потерян до worker activate — известный A/P4, не P6
production-flag-cycle-final.log 2 теста / 0 failures, 10.237 с; embedded control проверен после explicit ready wait
package-tests.log Промежуточный пакет: 474 XCTest, 24 skipped, 0 failures, 11.344 с
package-tests-production-paths.log Последний пакет: 478 XCTest, 28 skipped, 0 failures, 10.469 с
summarize.py, results.json Сводка последних результатов 14 текущих методов, имена исходных логов и SHA-256 source файлов; это не лог одного общего прогона

Контрольные суммы актуальных тестов:

  • CPMReproLifecycleHypothesisTests.swift: 60c2f3081c08a40cfc0244e023652013f0abf939151a63c14addc400a40ed413.
  • CPMReproProductionFlagTests.swift: 240c3828e3fac13357c6e80a954f3d07fc5dd26376d36308258ecc984bfd860e.
  • Существующий dirty Package.resolved, SHA-1: 7ccce20acd452656c9bf2d2fa186d044e4a8185f.

Команды повторения и границы следующей работы

Из /Users/admin/DuckDuckGo/apple-browsers.git/alex/cpm-web-extension-remaining-failures/SharedPackages/WebExtensions:

# Все 14 P5/P6 методов; эта общая команда дана для повторения, единым прогоном ещё не выполнялась.
CPM_REPRO=1 CPM_REPRO_P5_P6=1 swift test --disable-automatic-resolution --filter 'CPMReproLifecycleHypothesisTests|CPMReproProductionFlagTests'

# Только четыре новых source-linked метода.
CPM_REPRO=1 CPM_REPRO_P5_P6=1 swift test --disable-automatic-resolution --filter 'CPMReproProductionFlagTests|CPMReproLifecycleHypothesisTests/testClosing'

# Обычные тесты после актуальной сборки: repro без флагов пропускаются.
swift test --disable-automatic-resolution --skip-build

Strict SwiftLint запускался для восьми перечисленных Swift-файлов из корня worktree: mint run swiftlint lint --strict --force-exclude --no-cache <paths>, 0 violations. --no-cache нужен, чтобы не писать во внешний SwiftLint cache; это не пропуск правил.

Карта P1–P3/A/B/init-progress, file names, параметры тяжёлых экспериментов и актуальные worktree — cpm-web-extension-code-guide-2026-10-09. Нельзя предполагать, что один worktree содержит все эксперименты или что HEAD включает локальные untracked файлы.

Эта актуализация — документация. Новые app/WebKit fixes, telemetry/schema changes, commit/push/PR, сообщения ревьюерам, переключения remote flags и новые iOS/device проверки не выполнялись. Старые результаты до исправления ожиданий сохраняются, а causal attribution production-групп не повышена до доказанной.

13. Integration tests в настоящем приложении — реализация и проверки

Запрос: без commit добавить app integration tests, по возможности воспроизвести известные сбои, обязательно разделить slow load, одну потерянную навигацию с recovery, persistent single-tab, несколько tabs одного окна и все окна.

Выбран cpm-e2e-init (HEAD 46f25175c7c42766cab23ea39f447f075c7bdd24): здесь уже PR0/baseline/init_stage и изменённый до этой работы JS bundle для init-progress. Существующие dirty bundle и SPM harness сохраняются. Новый слой — macOS app-hosted Integration Tests, реальный DuckDuckGo.app/AppDelegate/Tab/MainWindowController, production navigation/native/diagnostics. Это отличается от прежнего standalone SPM harness с собственными window delegates.

Исходная запись перед реализацией (история, не текущий итог). Планируется отдельная схема без destructive UI-scheme clean-app pre-action. Внешние сервисы/config и pixel transport изолируются, production monitor/navigation callbacks вручную не подделываются. Естественные P5 shared-window / условный P6 flag / process failure и controlled diagnostic scopes будут явно различаться. Начата предварительная сборка в /private/tmp/cpm-app-integration-20261015/; результатов новых integration tests пока нет.

App integration: реализация, 16 октября

В cpm-e2e-init/macOS/IntegrationTests/CPM/ добавлены app-hosted harness и отдельные методы для slow HTTP, slow native settings (received → recovery), одной потерянной навигации, persistent one-tab, нескольких tabs одного окна, всех окон и dashboard-transport negative control (initResp получен, detector timeout). Естественные/условные пути вынесены в CPMAppKnownFailureIntegrationTests: реальные AppDelegate flag callbacks, shared pinned window close, process termination, первая embedded install, slow wake, opt-in 30s WebKit timeout.

Тесты используют реальные Tab, MainWindowController, WindowsManager, macOS provider/native handler/delegate, navigation callbacks и системный WebKit. Monitor events вручную не вызываются. CPM Integration Tests.xcscheme не содержит pre/post actions для очистки Review или killall tests-server; тесты запускаются только при explicit opt-in и не параллельно. Собственные HTTP listener на loopback, временная распакованная extension copy, in-memory installation store, nonpersistent WebKit data store и recorder transport. Существующий dirty ZIP не менялся этой работой.

Минимальные seams: DEBUG-only AppDelegate factory, доступная лишь в .integrationTests при CPM_APP_INTEGRATION=1; необязательная controllerConfiguration у shared manager для изоляции data store (default production прежний). Контролируемые scoped faults меняют доставку настоящих autoconsent messages в extracted test copy; они не являются доказательством естественной причины production-событий.

Xcode bootstrap: исходный frozen lockfile отсутствовал на workspace-level; resolver подготовил зависимости. Попытка ad-hoc build упёрлась в provisioning app/VPN/helpers; начата unsigned build. В sandbox Xcode ошибочно объявлял существующий workspace не-workspace из-за доступа к build services, повтор выполняется с разрешённым доступом. Strict SwiftLint новых 6 Swift + 2 seams: 0 violations (--no-cache). Исполнение новых integration tests ещё не подтверждено; результаты будут дописаны после сборки.

App host bootstrap: проверенные блокеры и границы, 16 октября

Unsigned app/test bundle успешно собран (build-app-integration-2.log), но первый запуск не дошёл до XCTest: stack sample остановился в ConfigurationStore.loadData → NSFileCoordinator → open shared app-group config. Тестовый host остановлен отдельно, без killall и без очистки профиля. Добавлен отдельный directory initializer ConfigurationStore, прежний initializer сохранён; только при DEBUG + .integrationTests + CPM opt-in AppDelegate использует собственный temporary configuration cache и test defaults. Production default не меняется. Промежуточные compile/link ошибки из-за type inference/incremental symbol устранены; build-app-integration-5.log — TEST BUILD SUCCEEDED.

Повтор baseline-2.xcresult дошёл до реального XCTest и штатного cpm_background_ready_context_load (under_1s, ctx_load_to_view under_250ms). Затем создание настоящего Tab упёрлось в независимый сервис: SpecialPagesUserScript → OnboardingActionsManager → SecureVaultFactory.makeVault; main thread ждал lock, background CredentialsDatabaseCleaner ждал SecItemCopyMatching/SecurityServer. Это блокировка unsigned host при доступе к Keychain, не CPM failure и не зелёный baseline. Отдельный test process остановлен; пользовательское запущенное приложение не останавливалось.

Для suite добавлен DEBUG-only SecureVaultFactory.isDisabledForIntegrationTesting; AppMain выставляет его до создания app environment лишь для .integrationTests + CPM opt-in. Autofill/vault этим suite не тестируется и Keychain для него не требуется; monitor, Tab/navigation, native CPM и WebKit остаются production. Выполняется пересборка. Первый и второй запуск пока считаются bootstrap failures, не результатами новых CPM assertions. В коде уже 18 отдельных сценариев, включая M4 recovery и embedded-only negative control; P3 persistent cache / естественная утрата listener / iOS остаются отдельными непроверенными здесь областями.

После DEBUG-only vault isolation baseline-3.xcresult дошёл до реального Tab.willStart для HTTP fixture, где сработал существующий fatalError: обычным integration tests разрешён только WKURLSchemeHandler, настоящая HTTP-навигация запрещена. Добавлено узкое DEBUG-исключение: .integrationTests + CPM_APP_INTEGRATION=1 + host 127.0.0.1. Удалённые hosts и остальные suites сохраняют прежнюю защиту. Это требование инфраструктуры для настоящего HTTP и штатных navigation callbacks; это не CPM fix. Baseline №3 — infrastructure failure, не доказательство сбоя расширения. Пересборка и повтор продолжаются.

Baseline №4: настоящие HTTP navigation callbacks/native dashboard успешно проходят для двух реальных tabs (≈0.40/0.31 с), diagnostics: context/tab/controller/scripts/handler=true, errors=none, network_restarted=false, ready under_1s. Два assertions page receipt были красными: test storage перехватывал copyExtension, но штатная ZIP install использует extractExtension на background task, поэтому DOM bridge вообще не был внедрён. Исправлен override extraction с делегированием production extractor, затем instrumentation только временной копии. Content mark теперь оборачивает существующий runtime listener, не добавляя ещё одного listener для ответов. Этот красный результат сохранён (baseline-4.xcresult) как ошибка harness, не CPM init loss. Выполняется повтор с проверкой control bridge.

Исправление signing-обхода — 16 октября

После вопроса пользователя проверены исходные настройки: Debug app использует Automatic signing, DEVELOPMENT_TEAM=HKE973VLUW; валидные identities/profile доступны. Unsigned был моим обходом, а не требованием среды. Первую provisioning-ошибку вызвали мои CLI overrides CODE_SIGN_STYLE=Manual, пустой DEVELOPMENT_TEAM/profile и ad-hoc identity. Повтор без этих overrides — build-signed-default.log, TEST BUILD SUCCEEDED, Apple Development: Alexey Martemyanov. Дальнейшие результаты считаются по подписанному приложению; unsigned command убрана из README. Ранние bootstrap-гипотезы о невозможности штатной подписи неверны.

Добавленные test-only изоляции cache/vault не являются CPM fixes; необходимость их сохранения пересматривается после signed проверки. Временная extraction instrumentation теперь подтверждена строкой CPM APP INSTRUMENTED; baseline с native response, но без page mark всё ещё красный, control bridge не подтверждён. Добавлена фиксация DOM marks/native methods. Прямой async импорт WebKit Any-result заменяется callback continuation с String-only границей после runtime abort «freed pointer was not the last allocation» в диагностическом прогоне. Результаты не объявляются зелёными до проверки page receipt и реальных failures/recovery.

Signed baseline и one-navigation — первые подтверждённые результаты

signed-baseline-and-navigation.xcresult / .log: 2 tests, 0 failures, baseline 1.864 с и single-navigation/recovery 5.360 с. На здоровых страницах есть реальные DOM marks initresp (≈13–17 мс) и forwarded native dashboard; при ровно одной потерянной навигации mark отсутствует, есть штатный timeout, следующая навигация отвечает и даёт recovery_document=later_navigation. Это signed DuckDuckGo.app, TeamIdentifier=HKE973VLUW, hardened runtime.

Прежний unsigned vault-обход удалён из SecureVaultFactory.swift и AppMain.swift: эти два файла снова без diff. Последующая signed build без обхода успешна (build-signed-core.log). Recorder в harness теперь использует реальный macOS runtime feature-flag adapter (видимость internal для app-hosted теста), добавлен отдельный test переключения proxy flag с проверкой здоровых навигаций и omission readiness на новом view при off. Всего 19 методов. Запущены все 10 core diagnostic scope/recovery методов. Slow wake/freeze задают busy wait до первой загрузки контекста и ждут исходную готовность, чтобы проверка следующего wake не зависела от чтения изменённого JS из уже закэшированного ресурса.

Core suite завершён: 10/10, signed, штатный Vault

signed-core.xcresult / signed-core.log: 10 tests, 0 failures, все выполнены, без skip. Последовательные методы: M4 11.571 с; dashboard transport 5.195; runtime proxy flag 1.719; healthy baseline 1.149; one navigation/recovery 5.292; all windows + new tab + recovery 23.995; one tab + healthy controls 11.127; several tabs + controls 19.648; slow HTTP 8.872; slow native settings/recovery 8.961. Десять JSON attachments экспортированы в signed-core-evidence/.

Проверенные поля: controlled loss → init_stage=none; native settings delay → received, recovery=failed_navigation, 4s_to_10s; потеря dashboard подтверждения при наличии page initResp → response_sent; scoped loss → actual init_failed/stuck и recovery=later_navigation; M4 recovery идёт after_extension_reload. При proxy off новый view не имеет readiness-параметра, CPM отвечает; при on готовность снова измеряется. Vault-обход удалён, подпись штатная.

Known-failures suite начат с включённым expensive flag. Первый background-SIGKILL автоматически восстановился в том же контексте: actual page initResp + native ответ, timeline содержит died_crash/dealloc/new view. Следующий explicit reload потерял первую навигацию (init_stage=none, ready уже under_1s) — известная startup race A, а не доказательство неустранимого worker death. Recovery-контроль уточнён: после explicit reload дождаться production loadEmbeddedExtensionBackgroundContent, как уже сделано в embedded-only negative control. Исходный красный прогон сохраняется. Embedded-only cycle, first embedded install и first main-enable уже прошли; выполняется реальный WebKit load timeout.

Known paths: P1/P2/P6 и Network подтверждены; P5 требует повторной диагностики

Первый signed-known-failures.xcresult:

  • Embedded-only negative control pass 1.243 с; первая embedded install pass 11.934 с: первая навигация потеряна, view/handler/tab/scripts корректны, readiness=pending, затем CPM работает — controlled A/P4.
  • First main-enable pass 9.130 с и main true→false→true pass 14.616 с: реальные AppDelegate callbacks оставляют nil/старый controller у существующих Tab; новые tabs здоровы — app-level P6.
  • Frozen background pass 68.319 с: настоящая idle-выгрузка, SIGSTOP нового background process, WebKit error 6; bg_load_error_after=25s_to_35s, app_state=inactive, context_errors=WKWebExtensionContextErrorDomain:6, хвост содержит unresponsive/dealloc/error, creations=2. SIGCONT отправлен в defer/teardown.
  • Slow idle wake pass 50.469 с: busy startup 8 с задан до context load; после healthy baseline/idle получены pending + timeout и поздний ответ, затем здоровая навигация.
  • Network SIGKILL дал 4/4 отказавших tabs двух окон, actual init_stage=none, observation=global failure reproduced. Первый recovery-контроль после explicit reload попал в startup race A; добавлен readiness wait, идёт повтор.
  • Background SIGKILL сам восстановился (не утверждается persistent failure); первый post-reload контроль тоже попал в A; уточнён тем же readiness wait.
  • Реальный shared-pinned-window close дошёл до WebKit Tab not found после двух здоровых idle cycles. Однако XCTest host завершился signal INT без финальных assertions/attachment; это пока не green app-level P5. Запущен отдельный повтор со stack sample, причина SIGINT ещё не установлена.

Добавлен 20-й метод P3 testLargePersistentCacheRecordsContextReloadInjectionGap: opt-in CPM_APP_INTEGRATION_CACHE=1, свежие собственные UUID website/controller stores, 20k loopback cache entries, настоящий baseURL-changing reload и проверка actual no-view failure. Сборка signed успешна, strict SwiftLint всех 11 текущих изменённых Swift — 0 violations. P3 ещё не исполнялся; normal suite остаётся nonpersistent. Persistent данные удаляются WebKit API только для UUID данного теста.

Recovery controls исправлены; источник INT в P5 установлен по коду

signed-known-retests.xcresult: background crash/reload control pass 3.328 с, Network global crash/reload control pass 21.972 с после explicit production readiness wait. Никакой browser fix для этих проверок не добавлялся. Повтор P5 опять завершился INT спустя около 4 с после window close, до timeout assertion. Отложенный stack sample не успел: к моменту съёмки test host уже завершился.

Найден штатный DEBUG caller: MainWindowController.windowWillClose вызывает self.ensureObjectDeallocated(after: 4.0, do: .interrupt); NSObjectExtension → breakByRaisingSigInt → raise(SIGINT). Тест держал let controller в async-method scope и закрытое NSWindow в harness.windows; это тестовое удержание, не CPM-сигнатура. Исправлено: pinning в ограниченном autoreleasepool, close удаляет окно из owned collection немедленно, surviving window index скорректирован. Проверка приложения не отключается. Signed build успешна, запущен повтор только P5.

Уточнение P5 после освобождения явной ссылки: первый actual failure уже был получен, но второй вызов helper снова мог выбрать закрытое окно. WindowControllersManager.windowController(for: Tab) сначала возвращает controller из tab.webView.window, даже если тот уже unregister. Поэтому automation могла повторно открыть/удержать closed window вместо пользовательского действия в surviving window и попасть в DEBUG deallocation interrupt. Исправлено только в harness: выбор из registered mainWindowControllers; shared pinned tab заранее выбирается в surviving window через настоящий TabCollectionViewModel.select. Production lifecycle/registration не подделывается, guard приложения остаётся включён. Ещё один повтор P5 необходим, прежний SIGINT не объявляется доказательством app crash/CPM failure.

P5 interrupt: LLDB дал точный объект

Первая попытка attach до idle изменила условия: idle-helper не дождался выгрузки за 45 с; этот debugger run — отдельный infrastructure failure, не результат P5. Attach перенесён только после двух idle cycles и window close.

pinned-lldb-after-idle.log: breakpoint в Common.breakByRaisingSigInt, stack NSObject.DeallocationCheckAction.interrupt → ensureObjectDeallocated, parameter description = MainWindow has not been deallocated, line NSObjectExtension.swift:80. Это уточняет прежнюю гипотезу: controller уже прошёл deinit, сработала следующая 8-секундная проверка его окна. Исправления только harness: создание NSWindow/MainWindowController целиком в отдельном autoreleasepool (как в существующих DeallocationTests), lookup/selection не держит controllers через await, closing window немедленно удаляется из owned list. Guards приложения и production window-close path не отключаются. Нужен повтор без debugger; прежние app-level P5 результаты пока red/incomplete, хотя actual first failure/tab-not-found уже получены.

16 Oct: signed cache/P5 follow-up (intermediate, not a green suite)

  • signed-pinned-autorelease.xcresult: real shared pin + two normal WebKit idle unloads + real window closure again produced runtime.sendMessage(): Tab not found, missing page initResp, production timeout init_stage=none, tab_in_context=false, matching controller and bg_view_creations=2. The host still interrupted before the second probe/attachment. Prior LLDB identifies the DEBUG deallocation guard for MainWindow, not an assertion from the CPM detector. Wrapping creation/selection in autorelease pools alone did not resolve that retention; the test is still red.
  • signed-large-cache.xcresult: fresh persistent UUID store, 20,000 cacheable loopback resources, actual disk-cache records, fill 42.705 s. Real extension reload changed baseURL; immediate next navigation succeeded in 1.058 s, page initResp present, background created once and ready under 1 s. p3NoBackgroundFailure=false: P3 not reproduced in this run. The readiness control also succeeded. The test failed only in teardown (WKWebSiteDataStore: Data store is in use (by network process)), while its own controller/store references were still retained. Cleanup is being corrected before a new run.
  • These runs use the project's normal Apple Development signing. The earlier unsigned CLI workaround was incorrect and removed; no SecureVault bypass remains. Current verified distinct passing methods remain 18/20, with the two red methods above; there is no combined all-green result.

P5: memory-reference evidence and harness correction

pinned-lldb-retainers.log again stops in breakByRaisingSigInt for MainWindow. leaks --noContent --nostacks --traceTree=<closed-window> <owned-test-host> saved pinned-window-retainers.txt: a direct chain is NSApplication._currentEvent → NSEvent._window → closed MainWindow. SDK declares NSApplication.currentEvent strong, and Apple documents it as the last dequeued event. TestRunHelper.loadedViews is weak (confirmed in source), not a strong owner. The graph also contains autorelease-pool and system/weak references; not every reported root proves retention.

The harness now posts and dequeues one neutral application-defined event after window closure, using public AppKit APIs, to advance the event queue while async XCTest is awaiting. It does not disable the DEBUG guard, detach tabs, or send manual extension lifecycle events. Verification is pending. Persistent-store teardown now clears owned website/controller data, releases controllers and the last harness store reference, then retries WebKit's asynchronous removal for at most 10 seconds. The orphan from the first failed cleanup was removed only at its verified fresh UUID 83CBE3E6-4B0E-41F0-B53F-22CD306B8DEE (creation time and extension State.plist matched that test), after the owner process exited.

Signing verification detail

The latest signed build (build-signed-event-loop.log) invokes codesign with Apple Development: Alexey Martemyanov (9SAT8X9S84), team HKE973VLUW, hardened runtime. codesign -dvv with normal keychain access confirms the Apple Development → WWDR → Apple Root CA chain. App debug dylib and Integration Tests.xctest independently pass codesign --verify --strict. codesign --verify --deep --strict on the entire Debug app fails in nested DuckDuckGo VPN.app/Contents/Frameworks/Subscription.framework: the seal lists four missing Modules/Subscription.swiftmodule artifacts (abi.json/swiftdoc/swiftmodule/Project/swiftsourceinfo). This is a separate nested Debug packaging limitation, not an unsigned main app or test bundle. No manual re-signing or production packaging changes were made for it.

Verification of cleanup/event-queue changes: still red

  • signed-pinned-final-control.xcresult: posting/dequeuing a neutral event alone did not resolve the DEBUG window interruption. The first real P5 failure is again recorded in stdout, but no completed green test. The memory graph's currentEvent chain is a real reference; it was not established as the sole cause. Harness synchronous Tab.setContent, diagnostics collection and polling conditions are now also confined to autorelease pools, pending verification.
  • signed-large-cache-cleanup.xcresult: the 20k cache scenario again completed its body with healthy post-reload/readiness controls, but static store removal still failed after the bounded retry. Merely clearing data and dropping harness references was insufficient: the pre-existing Integration Tests process-pool swizzle outlives each test. Teardown now requests _terminateNetworkProcess on the owned store after evidence capture and data clearing, before UUID removal. This is cleanup, not a causal fault used to claim P3. The previous fresh UUID 0FB56422-72D5-4CCB-AC66-86F3C50DE165 is recorded explicitly for cleanup auditing; no user-store identifier is selected.

WebKit cleanup contract checked against primary sources: profiles API documents that removal can fail while an active view uses the profile; WKWebsiteDataStore.mm implements _terminateNetworkProcess by forwarding to its WebsiteDataStore. Current upstream source is reference material, not an assertion that this macOS release has the identical revision. The actual runtime selector is checked before test-only cleanup.

P5 app test scope narrowed explicitly, not a hidden green result

signed-pinned-scoped-pools.xcresult again interrupted after the first lost navigation. The harness printed currentEventStillOwnsWindow=false, so advancing the event queue demonstrably does not solve the remaining lifetime problem. That neutral-event workaround is removed. The earlier graph also showed the closed MainWindow directly retained in an autorelease pool while the async XCTest was running; the sole original autorelease call site was not established. Synchronous harness navigation/diagnostics/polling remain inside scoped pools as normal lifetime hygiene.

The app P5 method is now deliberately limited to one real failed navigation (tab_in_context=false, matching controller, bg_view_creations=2) after two healthy/idle cycles and actual shared-pin window closure, followed by healthy regular/new-tab controls. It no longer attempts the second four-second timeout that reaches the app's eight-second DEBUG window guard before XCTest can finish. The guard is not disabled, no controller/tab detach is fabricated, and no claim is made that app-hosted persistent P5/stuck passed. The separate source-linked SPM P5 tests retain the existing two-failure/stuck proof. A new app run is still required for the narrowed method.

Persistent cache cleanup verified

signed-large-cache-session-end.xcresult: PASS, 47.519 s, no skip. The 20,000-resource persistent-cache scenario completed; teardown printed CPM APP REMOVED PERSISTENT STORE F705D072-DDD7-4ED7-BA23-C57A6193EA7E after clearing owned data and terminating this test host's Network session. The earlier orphan UUID from the second red cleanup was separately removed only after its owner exited, based on the harness's explicit UUID log. P3 remains a negative reproduction result; this passing characterization is not evidence of the no-background mechanism. There are now 19 distinct passing methods from corresponding signed runs, while P5's narrowed one-navigation method still requires its final run. A final build/regression is starting; it will keep the cache opt-in separate from the ordinary nonpersistent suite.

P5 one-navigation app characterization verified

signed-pinned-single-probe.xcresult: PASS, 73.806 s, no skip, signed final build. Two healthy page/native handshakes with real idle unloads establish background creation count 2. The real first-window close leaves the shared pinned Tab in the app but removes it from WebKit: the next real navigation receives no initResp, triggers the production failure (init_stage=none, tab_in_context=false, matching controller, creation count 2). The surviving regular tab and a newly created tab both receive page initResp and forwarded native dashboard. JSON attachment saved in signed-pinned-single-probe-evidence/.

The earlier longer attempts remain red and are not reclassified: this app method proves one lost navigation and unaffected controls, not two app failures/stuck. The DEBUG guard stays enabled. Combined with the separate successful persistent-cache characterization, all 20 distinct methods now have corresponding signed passes, but the final shared 18-method regression and a current-source cache rerun are still running. No single combined 20-method run is claimed.

Final combined regression was red: do not claim 20/20 together

signed-regression-final.xcresult: 18 methods, 16 passed, 2 failed, 0 skipped (XCTest summary, not just stdout). All ten core diagnostics methods passed again. Signed P1/P2/flag/crash controls preceding Network also passed, including the real WebKit timeout in 68.464 s. Network termination produced real failure in 4/4 tabs across two windows, but explicit recovery threw extensionNotFound for its owned extracted extension. The shared TestRunHelper subsequently timed out waiting for view deallocation; the next slow-wake method crashed with signal INT before it could attach evidence. Therefore those two latest combined outcomes are red, despite their earlier isolated passes. Exported 17 JSON reports and the summary remain in signed-regression-final-evidence/ and signed-regression-final-summary.json.

Source check: production WebExtensionLoader.loadWebExtension throws this error when storageProvider.resolveInstalledExtension returns nil, after the context unload. Harness resources used the globally swizzled FileManager.temporaryDirectory/com.duckduckgo.macos.tests; TestRunHelper initialization and test-bundle completion delete that shared root. The deleting actor for this run was not identified, so this is an isolation hazard, not yet a proven cause. Harness extraction is now placed under unswizzled NSTemporaryDirectory()/cpm-app-UUID, outside the helper's global cleanup folder; resolution still delegates to the production storage implementation and logs folder/root existence on failure. No browser reload/failure expectation is weakened. A new signed build/retest is required.

signed-large-cache-final.xcresult: PASS, 47.232 s on the preceding final build, owned store AFFE4DCD-59E5-4DFC-9D2C-6874C2FE498C removed successfully. P3 remains negative. These cache and P5 passes do not make the red combined run green.

Resource isolation recheck: 18/18 together

signed-isolated-regression.xcresult: TEST EXECUTE SUCCEEDED, 18/18 methods passed, 0 failures, 0 skipped, on signed build-signed-resource-isolation.log. This repeats the same selection that previously went red, with extension files outside the helper's global cleanup root. There are zero CPM APP STORAGE MISSING messages. The Network method now completes its real 4-tab fault/reload recovery in 21.692 s, and the next slow-wake method completes in 49.651 s, without the earlier INT. Real WebKit load-timeout method passes in 68.303 s. All ten core scope/diagnostics methods pass in the same invocation.

This verifies the new isolation under the observed conditions; it does not identify the actor that deleted files in the previous run. The prior 16/18 red result remains part of the record. P5 and cache are being repeated separately against this exact build, and will be included in the final matrix rather than described as a single 20-method invocation.

Финальный app-hosted итог — 16 октября 2026

Проверено 15 Oct 19:48 UTC / 16 Oct 01:48 Бишкек: 20/20 методов PASS, 0 failures, 0 skipped, 0 expected failures в трёх запусках одной окончательной signed build. Это 18 вместе + P5 отдельно + cache отдельно, а не один 20-method invocation.

  • signed-isolated-regression.xcresult: 18/18, включая все 10 основных scope/diagnostics tests, P1/P2, first install/flags и Network recovery.
  • signed-isolated-pinned.xcresult: 1/1, 72.644 s, настоящий app P5 в явно ограниченном one-navigation scope.
  • signed-isolated-cache.xcresult: 1/1, 43.053 s; 20 000 ресурсов за 39.113 s, post-reload probe примерно 0.900 s, p3NoBackgroundFailure=false. Собственный persistent store удалён через WebKit; дополнительно проверено отсутствие его каталога.

Сводки получены через xcresulttool get test-results summary, все 20 JSON reports экспортированы. Корень артефактов: /private/tmp/cpm-app-integration-20261015/; актуальные evidence directories — signed-isolated-regression-evidence/, signed-isolated-pinned-evidence/, signed-isolated-cache-evidence/. app-test-results.json содержит per-method latest result, сохранённую историю красных/зелёных попыток, summaries трёх финальных групп и SHA-256 исходников. Старые красные logs/results сохранены, включая joint 16/18 и длинные P5 attempts.

Worktree /Users/admin/DuckDuckGo/apple-browsers.git/cpm-e2e-init, branch scratch/cpm-e2e-init, HEAD неизменён 46f25175c7c42766cab23ea39f447f075c7bdd24, commits нет. macOS 26.7.1 (25G241), Xcode 27.1 (27A9275), system WebKit 21624.5.1.11.3, arm64, GUI session. Финальная сборка — build-signed-resource-isolation.log. Apple Development: Alexey Martemyanov (9SAT8X9S84), team HKE973VLUW, hardened runtime; normal project signing defaults. final-signing.txt содержит цепочку сертификатов; app debug dylib и XCTest bundle проходят strict signature verification. Весь Debug app имеет отдельно описанное выше ограничение deep verify вложенного VPN Subscription.framework; distribution-валидность не заявляется. Unsigned/vault-bypass workaround в окончательном коде отсутствует.

Полный реестр 20 app methods

Первые 10 методов — CPMAppIntegrationTests.swift, остальные 10 — CPMAppKnownFailureIntegrationTests.swift, оба в macOS/IntegrationTests/CPM/.

Метод Финальный результат Что проверено
testHealthyBaselineUsesRealTabsAndNativeDashboard PASS · 1.131 s Baseline: реальные page initResp и native dashboard; tab/controller/handler/readiness/tabs
testDiagnosticProxyFlagCanChangeWithoutBreakingMessaging PASS · 1.705 s Proxy flag off/on через macOS adapter: readiness исчезает/возвращается, messaging healthy
testSlowHTTPDocumentDoesNotProduceCPMFailure PASS · 8.883 s Медленная HTTP-загрузка (8 s image): handshake healthy, ложного failure нет
testSlowNativeSettingsProducesReceivedStageThenSameDocumentRecovery PASS · 8.964 s Медленный native settings (8 s): received → timeout → same-document recovery
testOneNavigationFailureRecoversOnNextNavigation PASS · 5.307 s Одна потерянная навигация: none → healthy на следующей, later_navigation, без stuck
testPersistentFailureOfOneTabLeavesOtherTabsAndWindowsHealthy PASS · 11.167 s Persistent одна таба: два failure/stuck; same-window/other-window/new-tab controls healthy; clear → recovery
testPersistentFailureOfSeveralTabsInOneWindowLeavesControlsHealthy PASS · 19.657 s Две табы одного окна: persistent failure/stuck; оставшаяся таба/другое окно healthy; clear → recovery
testPersistentFailureAcrossAllWindowsIncludesNewTabsAndRecoversAfterClear PASS · 24.966 s Все окна: 4 табы и новая таба fail; clear → recovery во всех
testAutomaticHangRecoveryReloadsExtensionAndRestoresMessaging PASS · 12.870 s Настоящий M4 после подтверждения hang заменяет context; messaging и recovered_after_reload
testDashboardTransportLossIsDistinguishableFromMissingInitResponse PASS · 5.293 s Потеря dashboard confirmation при полученном page initResp: response_sent, затем recovery
testMainFeatureOffOnRetainsOldControllerAndBreaksExistingTabs PASS · 14.562 s P6: реальные main-flag off/on callbacks; старый controller у existing tabs, новая таба healthy
testFirstMainFeatureEnableLeavesExistingTabsWithoutController PASS · 9.137 s Первое main enable: existing tabs с nil controller fail; новая таба healthy
testEmbeddedOnlyFlagCyclePreservesControllerAfterReadiness PASS · 1.256 s Embedded-only cycle сохраняет controller; healthy после readiness
testSharedPinnedTabSurvivesClosingWindowButDisappearsFromWebKit PASS · 72.644 s P5: реальное shared-pin window close → один failure/tab_in_context=false; regular/new-tab healthy
testNetworkProcessTerminationRecordsActualCrossWindowOutcome PASS · 21.692 s Реальный Network SIGKILL → 4/4 tabs fail в двух окнах; explicit reload/readiness → все healthy
testBackgroundProcessTerminationRecordsCrashAndCanRecoverAfterReload PASS · 2.518 s Реальный background SIGKILL: died_crash/dealloc/new view; в этом WebKit самоизлечился; reload healthy
testFirstEmbeddedInstallDuringSessionRecordsStartupRace PASS · 11.882 s Реальный first embedded install с 6 s startup delay: первый init потерян/pending; следующий healthy
testLargePersistentCacheRecordsContextReloadInjectionGap PASS · 43.053 s P3 experiment: 20k persistent-cache resources + baseURL-changing reload; P3 НЕ воспроизведён; cleanup/recovery healthy
testSlowBackgroundWakeReportsPendingThenRecovers PASS · 49.651 s P1: реальный idle unload + 8 s wake delay; pending/none → timeout → поздний ответ/recovery
testFrozenBackgroundHitsWebKitLoadTimeout PASS · 68.303 s P2: новый worker SIGSTOP → настоящий WebKit error 6 около 30 s (25s_to_35s); SIGCONT в cleanup

Границы доказательства и окончательная реализация

  • Зелёный characterization test не означает исправление CPM или доказанную production-причину. Scoped drops/delays действуют только на extracted test copy и проверяют диагностику/масштаб отказа. Навигации, app window/tab callbacks, native provider/delegate, monitor и recorder настоящие; прямые didCloseTab/synthetic monitor events для получения failure не вызываются.
  • P3 не воспроизведён на этой машине/20k cache; этот результат не исключает другие размеры, timing или iOS. Background SIGKILL здесь самоизлечился — зафиксирован crash/recovery, не выдуман persistent failure.
  • App P5 доказывает одну потерянную навигацию после реального закрытия окна и здоровые controls. Две app-навигации/stuck не объявляются зелёными: прежние попытки достигали DEBUG MainWindow lifetime guard, который остаётся включён. Persistent P5/stuck проверен отдельными SPM methods (§12).
  • P1/P2 — управляемые macOS-механизмы. Естественная потеря listener metadata, P5 с tab_in_context=true, iOS suspend/resume/device-only ошибки и production main-flag transitions всё ещё не доказаны.
  • Устранён риск общего test temp root: extension resources теперь в NSTemporaryDirectory()/cpm-app-UUID вне глобального swizzled com.duckduckgo.macos.tests, который очищает TestRunHelper. Старый deleting actor не установлен; после изоляции тот же joint selection прошёл 18/18, missing-resource logs нет. Это не выдаётся за установленную внешнюю причину прежнего удаления.
  • Teardown закрывает только окна своего host, восстанавливает pinned mode/flags, возобновляет остановленные PID и удаляет собственные extension files. Для cache сначала сохраняет evidence, очищает owned website/controller data, завершает Network session этого test host через test-only SPI, освобождает refs и удаляет только свежий UUID store с bounded retry. Пользовательский browser process не выбирается; глобального killall/Review-profile wipe нет.
  • Минимальные seams: optional controllerConfiguration с прежним production default; guarded DEBUG AppDelegate factory; собственный ConfigurationStore directory; loopback-only opt-in exception в Tab; internal macOS diagnostics flag adapter для @testable. Новая pixel schema/production fix не добавлялись. Шесть новых Swift files включены в оба Integration Tests targets, scheme XML/PBX валидны.
  • Strict SwiftLint 11 Swift files: 0 violations, git diff --check чистый. Прежний dirty bundle сохранён (2026.10.14, SHA-256 4254967bb447aa45deb371b26fea1dd93850a8e6a654f8ebecd70fccc3859936) вместе с прежними untracked SPM files. Другой remaining-failures worktree не менялся.

Повторение: схема CPM Integration Tests с project signing defaults; полная инструкция в macOS/IntegrationTests/CPM/README.md, карта файлов — cpm-web-extension-code-guide-2026-10-09#5. App-hosted CPM integration tests — 16 октября 2026. Default scheme пропускает два expensive opt-ins. Реальный timeout включать test-host env CPM_APP_INTEGRATION_EXPENSIVE=1. Cache выбирать отдельным методом с CPM_APP_INTEGRATION_CACHE=1, execution allowance 2400 s (count override CPM_APP_INTEGRATION_CACHE_ENTRIES); cache switch применяется ко всем harness instances выбранного invocation. Финальные .xctestrun планы сохранены в DerivedData/Build/Products/CPM-App-Final.xctestrun и CPM-App-Cache.xctestrun. PR/CI/ClickHouse snapshot выше не обновлялся этим локальным тестовым проходом.

16 Oct: explicit interpretation correction and requested rerun

The statement “all cases from production data reproduced except P3” is not supported. The 20 green methods establish their assertions, not 20 identified production causes. All run inside the actual app/system WebKit, but in an app-hosted test environment with isolated stores, mocked feature/config inputs, intercepted pixel transport and an instrumented extracted bundle (the runtime listener wrappers/DOM observation are present even when a scoped fault is disabled). This is not a black-box test of an unchanged shipping binary.

  • P5 closure, conditional P6 flag transitions and real Network-process termination exercise actual browser/WebKit lifecycle paths. P5 app scope is one failed navigation; the original two-timeout app attempt remains unverified/red because of the DEBUG window guard. Actual main-flag transitions in the affected production sessions are not established. SIGKILL is a forced process failure, not an observation of its production trigger.
  • P1 worker delay and P2 SIGSTOP reproduce a timing/timeout mechanism under imposed conditions; they do not identify why a production worker is slow/frozen. First embedded install uses the real app callback with an injected startup delay.
  • Single-/multi-tab/all-window message filtering, native-method delay and dashboard suppression are explicit fault injections for diagnostics/recovery. Those injection hooks are not shipped production behavior. Matching a failure signature is not causal attribution.
  • Still unproven: P3, natural listener-metadata loss, P5 with tab_in_context=true, real iOS lifecycle/device chains, and production occurrence of the P6 transition. Background SIGKILL self-healed on this WebKit.

The user requested rerunning failed selections. Current unchanged source hashes match the final prior build. Repeat launched in /private/tmp/cpm-app-integration-20261015/rerun-failed-20261016-042858/: the prior red shared selection of 18 methods, current one-navigation P5 method, and current persistent-cache method. Historical removed unsigned/bootstrap variants and the old longer P5 source are not being presented as rerun/fixed. Results pending; no source or submodule changes.

Submodule source check: .gitmodules lists SnapshotReferences and privacy-reference-tests, both uninitialized here. SnapshotTestingSupport resolves its image/data references under SnapshotReferences; privacy-reference-tests is used by BrowserServicesKit reference tests and is listed in Xcode resource phases. No direct reference to either exists in the new CPM integration files. These app integration runs are not the macOS/XCUI scheme; that scheme's Review cleanup pre-action is not invoked.

Requested failed-selection rerun — completed 16 Oct, 04:37 UTC

Rerun folder /private/tmp/cpm-app-integration-20261015/rerun-failed-20261016-042858/. Same signed build and all 14 source hashes unchanged, no source/submodule edits or commits. xcresulttool summaries confirm 20 current methods PASS in 18+1+1 invocations; 0 failures, 0 skips, 0 expected failures. Exported 20 JSON attachments; rerun-summary.json indexes the summaries, immutable source hashes, cache outcome and interpretation limits.

The 18-method selection includes successful Network/reload (22.198 s) and slow wake (49.764 s); no missing-resource log. P5 reruns the current one-navigation method, not the historical two-timeout variant. Cache PASS 44.162 s: 20k resources, fill 40.129 s, post-reload probe 0.963 s, p3NoBackgroundFailure=false; UUID store removal completed. Thus P3 is again not reproduced, although the characterization/cleanup assertions pass.

Nonfatal infrastructure warning remains: cache teardown emits BUG IN CLIENT OF libsqlite3.dylib / vnode unlinked while in use while removing its owned UUID website store. The warning is preserved in cache.log and rerun-summary.json; there is no XCTest assertion failure or claimed clean-runtime result. No code was changed to hide it. The older long app P5 still has its separate DEBUG lifetime limitation. It is therefore wrong to call all historical red runs “only infrastructure” or to mark their original source variants fixed: earlier reload controls also lost the first CPM navigation (page/native response missing), and some controls were subsequently changed to await actual background readiness.

Submodule mapping verified from PBX: privacy-reference-tests resources belong to Unit Tests / Unit Tests App Store. SnapshotReferences is used by SnapshotTestingSupport (unit/package snapshot suites); XCUI accessibility snapshot() calls are a different API. Neither submodule is consumed by the new CPM app test cases; both remain uninitialized here. The macOS XCUI scheme and its Review-profile cleanup were not run.

Interpretation unchanged: this rerun demonstrates test repeatability in the instrumented app-test environment, not reproduction/attribution of all production-data cases. P1/P2 and scoped drops remain imposed faults; P5/P6/Network exercise real browser/WebKit paths with the trigger/production-evidence limits stated above.

Точечные фиксы P1/P2 — начато 16 октября 2026

Пользователь разрешил локальные фиксы и проверку регрессионными тестами, без коммитов. В последнем запросе написано p0/1; в существующей классификации P0 нет, поэтому работа относится к двум обсуждавшимся картинам P1 (slow idle wake) и P2 (wake → error 6). Worktree cpm-e2e-init, исходный HEAD 46f25175c7c42766cab23ea39f447f075c7bdd24; прежние dirty-изменения сохранены. Снимок исходников до работы: /private/tmp/cpm-p1-p2-fixes-20261016/before-changes.tar.gz и before-source-hashes.json.

Добавлены app-регрессии testSlowBackgroundWakeKeepsFirstNavigationHealthy (первый документ после реального idle + 8 s startup должен получить page initResp и native dashboard без detector failure) и testFrozenBackgroundRecoversAutomaticallyForNextNavigation (реальный WebKit error 6 после SIGSTOP, затем SIGCONT; автоматическая замена контекста без второй сломанной навигации, существующие tabs в двух окнах и новая tab здоровы). Начинается прогон до production-фикса; результат ещё не получен. SIGSTOP/JS delay остаются принудительными условиями, не доказательством естественной iOS-причины. Старые 20 зелёных characterization methods не считаются проверкой этих новых исправлений.

Первый красный app-прогон и реализация кандидатов

/private/tmp/cpm-p1-p2-fixes-20261016/red.xcresult: обе новые проверки красные на исходном production-коде. P1 действительно дал detector timeout и отсутствие page/native ответа в момент проверки. Дополнительно teardown попал в DEBUG deallocation deadline, поскольку тест завершался до позднего ответа; добавлено ожидание его завершения после функционального assertion. P2 пока не считается чистым red-доказательством recovery: тест ждал error 6, пока процесс был SIGSTOP; в этом запуске WebKit сообщил error 6 лишь после SIGCONT из defer. Исправлен порядок: держать процесс 32 s, отправить SIGCONT, наблюдать реальное error-notification (сохранённое до возможного unload), затем требовать автоматическую замену контекста. Исторический первый красный результат не удалён.

Локально добавлен кандидат P1: перед каждой HTTP(S) main-frame navigation подготовка background через публичный loadBackgroundContent, совместная задача для параллельных навигаций одного context; ожидание ограничено прежними 5 s и отменяется независимо от WebKit. При этом прямой withTimeout { await Task.value } не годится для неотменяемого callback: task group ждёт завершения дочерней операции. Использован отменяемый AsyncStream, добавлены отдельные unit tests deadline/cancellation. Старый P1 characterization сохранён с webExtensionStateRestorationGate=false.

Кандидат P2: текущий, а не уже заменённый context при настоящем backgroundContentFailedToLoad запускает штатный context reload через существующий recovery kill switch. Общий in-progress guard с M4 и 60 s cooldown для load-error recovery; после reload явно запускается background. При известном app_state=background восстановление не выполняется. Приватные process SPI в production-фикс не добавлены; SIGSTOP остаётся только способом воспроизведения. Оба кандидата ещё собираются/проверяются; зелёный результат пока не заявляется. Добавлен disabled-fix режим только новых app-tests для сравнения того же бинарника с явно заданными флагами.

Проверка кандидата: P1 зелёный, P2 fixture пока не воспроизводит ошибку

fixed-initial.xcresult: новый функциональный P1 PASS (49.628 s), первый документ после idle + 8 s busy worker получил page initResp/native confirmation без timeout. P2 в том же прогоне FAIL на ожидании реального error 6, не на проверке recovery. После учёта 5 s navigation gate пробовались отключение P1 gate и 45 s SIGSTOP (fixed-p2-observer.xcresult): снова healthy late wake без error 6. Этот расширенный fixture имел два окна/два baseline-документа; он не считается воспроизведением P2 и не подтверждает его фикс. Запущен исходный одновкладочный testFrozenBackgroundHitsWebKitLoadTimeout для сравнения с прежним зелёным сценарием. Все отрицательные результаты сохранены.

P2 реализация уточнена: manager наблюдает публичное WKWebExtensionContext.errorsDidUpdateNotification, независимо от navigation gate и пассивного recorder. В прочитанном WebKit source (WebExtensionContextCocoa.mm:2793–2802) service-worker failure записывает error 6 и возвращается без выполнения ожидающих loadBackgroundContent callbacks. Поэтому callback-ожидание дополнительно завершается по error notification и при willReload; recovery guard не удерживается бесконечным ожиданием нового startup. Автоматическая обработка ошибок чужого/старого/не-embedded context исключена. Восстановление пока не объявляется проверенным на app P2.

Пакет после текущих изменений: 806 XCTest, 14 skipped, 0 failures (unit-package-callback.log); strict SwiftLint шести Swift-файлов — 0 violations. Перегенерированные SPM resolver изменения версий CSS/privacy-dashboard сохраняются только в артефактах и будут исключены из итогового diff; это не часть фикса.

P2: исходный fault воспроизведён, автоматический reload есть; page receipt ещё проверяется

p2-original-control.xcresult: исходный one-tab P2 PASS 69.049 s, реальный error 6 ровно через ~30 s после второго background view. Поэтому новый P2 regression возвращён к одному исходному tab/window; проверка нового окна/вкладки перенесена после recovery. Два baseline окна до fault не воспроизводили тот же error 6 — этот вариант остаётся отрицательным результатом.

fixed-final.xcresult: P1 снова PASS 49.908 s. P2 получил настоящий WKWebExtensionContextErrorDomain:6 и автоматический context reload; но регрессия осталась красной: existing-tab probe имел native dashboard=true и 0 detector failures, но page initResp mark=false. Новое окно и новая вкладка получили оба подтверждения. Нельзя объявлять восстановление старого tab по одному native ack. В harness фиксированный sleep 150 ms заменён bounded polling реального page initResp (до 2 s); идёт повтор fixed-page-ack.xcresult. Проверка page receipt сохранена строгой, зелёный P2 пока не заявлен.

Оговорка о пользе P2: исходный standalone testBackgroundStalledPastUnloadTimerFailsToLoad уже проверяет, что следующий wake может восстановиться без нового фикса. Новый кандидат делает recovery заранее, по error notification; его проверка сама по себе не доказывает, что SIGSTOP-модель объясняет persistent iOS stuck или что без reload следующая навигация обязательно сломана.

Первый зелёный P2 regression на окончательном production-кандидате

fixed-page-ack.xcresult: P2 PASS, 85.562 s, 1/1, 0 failures/skips/expected failures. Exported evidence: fixed-page-ack-evidence/755EA182-FC24-479A-95B5-CFF1493D0404.json. Реальный error 6 записан до recovery: context_errors=WKWebExtensionContextErrorDomain:6, bg_load_error_after=25s_to_35s, app_state=inactive, view_creations=2, view_alive=false. Первая fault-navigation: native=false/page=false, 1 initialization failure. После автоматического reload, без явного test reload: existing tab native=true/page=true, 0 failures; новое окно и новая tab также native=true/page=true, 0 failures. Existing-tab probe занял 0.149 s: сам по себе этот повтор не доказывает, что предыдущий page-negative был только недостаточными 150 ms; intermittent/order-dependent delivery остаётся причиной дополнительной проверки повторяемости.

Сборка build-page-ack.log — project signing defaults, TEST BUILD SUCCEEDED; подписи app dylib/XCTest проверены, bundled ZIP сохранён с исходным SHA-256 4254967bb447aa45deb371b26fea1dd93850a8e6a654f8ebecd70fccc3859936. Начат общий прогон оставшихся 19 app methods на этой же сборке (новый P1 включён, P2 уже проверен отдельно, P5/cache отдельно). Контроль с выключенными фиксами и итоговая полная матрица пока не завершены. HEAD не изменился, commits/PR изменений нет.

Повтор выявил нестабильность полного P2 reload; кандидат изменён

p2-repeat.xcresult: FAIL 90.533 s. Настоящий error 6 и автоматический reload опять произошли, но existing tab снова имел native=true/page initResp=false, уже после bounded 2 s ожидания. Значит, первый зелёный fixed-page-ack не доказывал устойчивость: полный context reload как готовый фикс P2 отклонён. Проверка page receipt не ослабляется.

Новый P2 кандидат: после ошибки повторить только loadBackgroundContent с сохранением WKWebExtensionContext, чтобы не инвалидировать ожидающий ответ исходного content script. Pending readiness очищается по реальному error notification до повторного API-вызова, иначе новая попытка могла бы присоединиться к уже завершённой старой задаче. Существующий M4 full reload остаётся fallback для confirmed stuck; kill switch/cooldown/own-current-context checks сохранены. Новый regression требует native dashboard и page initResp на том же failed document до любой новой навигации, тот же объект context, recovery_document=failed_navigation; затем проверяет обычные следующие/new-window/new-tab навигации. Идёт пересборка, зелёного результата для этого кандидата пока нет.

pinned.xcresult: один assertion FAIL: bg_view_creations ожидался 2, стал 3_to_5. Остальные P5 assertions (реальная потеря tab registration, controller match, lost navigation и healthy controls) прошли. Причина дополнительного view установлена по коду: новый P1 явно будит background перед тем, как WebKit отвергает сообщение неизвестной pinned tab. Для включённого navigation gate актуализирован строгий ожидаемый bucket на 3_to_5; сам сбой не спрятан и не исправлен этим изменением.

cache.xcresult: PASS 45.702 s, P3 по-прежнему не воспроизведён. В teardown снова есть nonfatal libsqlite3 vnode unlinked while in use для owned UUID store 71644ce9-6351-4c46-b915-825beb961ee6, в этот раз EnhancedSecuritySites.db. Это отдельное ограничение cleanup, не зелёная проверка целостности SQLite и не сбой CPM assertion. Исторические прогоны и warning сохранены.

Background-only retry: первый строгий P2 PASS без смены документа/контекста

background-retry-points.xcresult: 2/2 PASS, 0 failed/skipped/expected failures. P1 49.734 s, P2 85.234 s. Для P2 теперь проверено: реальный error 6 → публичный повторный loadBackgroundContent → исходный failed document получает native dashboard и page initResp до любой следующей навигации, context object остаётся тем же. Затем существующая tab, новое окно и новая tab здоровы. Доставку не имитировали и не переинициализировали документ из теста.

Фактический pixel в background-retry-points-evidence/A7C288DA-DCCE-42DC-BC30-6E8D6865526D.json: debug_web_extension_cpm_messaging_recovered_without_extension_reload, recovery_from=initialization_failed, recovery_document=failed_navigation, recovery_after=30s_to_2m. Это новый кандидат, прежний once-green full-context prototype не входит в его подтверждение. Пакет: 806 tests / 14 skips / 0 failures (unit-package-background-retry.log), strict lint — 0 violations. Идёт общий app-run с повтором P2, затем новые negative controls и P5/cache на этой же сборке. Окончательная source matrix пока не завершена.

Окончательный итог точечных P1/P2-кандидатов — 16 октября 2026, 12:30 UTC

Завершено локально, без commits и PR mutations. Worktree /Users/admin/DuckDuckGo/apple-browsers.git/cpm-e2e-init, HEAD 46f25175c7c42766cab23ea39f447f075c7bdd24. Сводка: /private/tmp/cpm-p1-p2-fixes-20261016/validation-summary.json; отдельная delta этой работы — point-fixes.diff, предыдущие dirty contents — before-changes.tar.gz. Предыдущие tracked/untracked harnesses и dirty embedded ZIP сохранены; ZIP SHA-256 по-прежнему 4254967bb447aa45deb371b26fea1dd93850a8e6a654f8ebecd70fccc3859936. Проверены отсутствие неожиданных изменений прежних файлов, неизменность final source hashes после сборки, normal Apple Development signing app dylib/XCTest и git diff --check.

Что реализовано

  • P1: перед каждым HTTP(S) main-frame navigation production coordinator через manager готовит embedded background, включая повторный wake. Shared task привязана к объекту context; навигация ждёт максимум прежние 5 s и освобождается при cancellation, даже если WebKit не завершает callback. Остальная lifecycle chain не блокируется. Управляет существующий webExtensionStateRestorationGate.
  • P2: настоящий WKWebExtensionContext.errorsDidUpdateNotification, own/current/embedded context и domain/code 6 запускают один повтор loadBackgroundContent без смены context/Tab/controller. Старый pending startup завершается/очищается до retry, cooldown 60 s, существующий recovery kill switch и M4 in-progress guard. При known app_state=background retry не выполняется; отдельный resume handler не добавлен. M4 full reset остаётся fallback для confirmed stuck, и не держит guard бесконечным ожиданием readiness callback.
  • P2 full-context prototype отвергнут: после once-green результата повтор опять терял page initResp в existing tab при native dashboard=true. Эти красные результаты сохранены, а не объявлены инфраструктурными. Финальный background-only кандидат требует действительного восстановления исходного документа.
  • Harness: bounded polling реального page initResp вместо sleep 150 ms, без подделки response/monitor events. P5 с включённым P1 ожидает дополнительный view (bucket 3_to_5), но всё ещё требует actual tab_in_context=false, controller match, failed pinned navigation и healthy regular/new-tab controls. Raw P1/P3 и fault-фаза P2 явно отключают P1 gate там, где нужно сохранить исходные условия/таймер.

Проверенная матрица окончательного исходника

Run (.xcresult в общей папке) Итог Что доказывает
background-retry-points 2/2 PASS, 0 skips/expected failures P1 first document после idle+8 s startup здоров; P2 error 6 → native+page ответ на том же failed document до новой navigation и без context replacement
background-retry-regression 19/19 PASS, 0 skips/expected failures Все 10 core diagnostics/scopes, известные lifecycle/flag/Network/background paths; P2 same-document повторно PASS 84.776 s
background-retry-pinned 1/1 PASS, 75.409 s Реальный P5 browser→WebKit tab-registration failure с актуальным bg-view bucket; one failed navigation + healthy controls
background-retry-cache 1/1 PASS, 61.906 s 20,000 actual cache responses/records; fill 55.918 s, reload→probe 2.149 s, p3NoBackgroundFailure=false
background-retry-disabled 0/2 PASS, 2 FAIL — ожидаемый negative control Тот же signed binary, P1 gate/P2 retry выключены только в новых test methods: P1 снова timeout; P2 error 6 есть, same-document recovery отсутствует (timeout), а не просто «context не заменился»

Итого 22 уникальных app methods PASS, 0 failed/skipped/expected failures в положительных прогонах. Это 2 + 19 + 1 + 1 = 23 положительных выполнения, поскольку P2 повторён в двух runs; не один combined run из 22 methods. Две deliberate-red проверки не скрыты в этом счёте. Их официальный XCTest expectedFailures=0 — красный результат ожидается самим экспериментом, это не XCTExpectFailure.

P2 first pass 85.234 s, повтор 84.776 s. Фактический recovered pixel: recovered_without_extension_reload, recovery_from=initialization_failed, recovery_document=failed_navigation, recovery_after=30s_to_2m. Test также проверяет DOM initResp и тот же объект WKWebExtensionContext; новый документ из теста не создаётся до этих assertions. Исходный fault: реальная idle-выгрузка, owned SIGSTOP 45 s, WebKit error 6 около 30 s. SIGCONT/teardown ограничены owned PID.

Unit/package: unit-package-background-retry.log — 806 XCTest, 14 skipped, 0 failures. Новые lifecycle unit tests проверяют bounded/cancellable ожидание при callback, игнорирующем cancellation. lint-background-retry.log — strict SwiftLint 6 файлов, 0 violations. Временные resolver-изменения CSS/privacy-dashboard сохранены в artifact diff и исключены из repo diff. Final hashes — background-retry-source-hashes.json.

Практические границы

  • P1 проверен при injected 8 s startup. Ожидание 5 s не ускоряет worker и не доказывает устранение всех более долгих P1 delays; 4 s detector threshold не изменялся.
  • P2 проверен в одном исходном окне/tab с controlled SIGSTOP. Новый window/tab и existing-tab subsequent navigation здоровы; вариант с двумя baseline окнами не воспроизвёл error 6 и остался отрицательным экспериментом. Natural iOS suspend/resume, deferred foreground recovery и причины основных iOS production rows не доказаны. Старый standalone P2 уже мог сам восстановиться на следующем wake; текущая регрессия строже — восстановление того же документа до следующего wake/navigation.
  • Все app methods работают в test environment с instrumented extracted bundle. Это реальные AppDelegate/Tab/window/provider/monitor/WebKit пути, но не unchanged shipping environment и не исчерпывающее causal attribution production cohorts.
  • P3 по-прежнему не воспроизведён; app P5 всё ещё ограничен одной потерянной navigation, прежний long app P5 red сохранён. Отдельные SPM P5/P6 эксперименты и исторические цифры не подменяются новой app-матрицей.
  • Cache cleanup снова вывел nonfatal libsqlite3 vnode unlinked while in use для только тестового UUID store 3ac0b3e6-8945-43dc-aad4-864b08bcc469 (EnhancedSecuritySites.db/-wal/-shm). XCTest assertions и публичное UUID removal завершились; целостность этой удаляемой SQLite базы этим не подтверждается. Warning не исправлялся и не скрыт.
  • CI/PR/ClickHouse в рамках финальной локальной проверки не обновлялись; численные production-срезы выше сохраняют свои даты. #7379 и остальные PR не менялись, никаких commits/push не было.

Выделение P1/P2 в отдельный worktree — 16 октября 2026

По запросу создан /Users/admin/DuckDuckGo/apple-browsers.git/alex/cpm-fix-p1-p2, ветка alex/cpm-fix-p1-p2, от текущего alex/cpm-messaging-readiness-telemetry (01d4a0ec22b4702ca9f9ea37757e060772ec7fa9, уже содержит merge main). Исходный scratch worktree сохранён без изменений. Коммитов нет.

22 — полный исследовательский app-набор, а не число обязательных тестов этих двух фиксов. В новую ветку выделены ровно два app-теста (testSlowBackgroundWakeKeepsFirstNavigationHealthy, testBackgroundLoadFailureRecoversOriginalDocument) и четыре новых lifecycle unit-теста (wake после launch, ограниченное ожидание неотменяемого callback, отмена навигации, выключенный gate). App-harness сокращён до необходимого: реальные AppDelegate/Window/Tab, native handler и system WebKit, изолированные stores, loopback HTTP, наблюдение реального initResp; задержка worker и SIGSTOP/SIGCONT только собственного background PID. Scoped drop/control, Network crash, cache/P3/P5 и остальные 20 сценариев не перенесены.

Фикс P1 сохраняет предел 5 с и существующий gate flag. P2 повторяет background load в том же контексте по настоящей notification error 6, с существующим recovery flag, ограничением частоты и запретом в background; полный context reset не перенесён. На этой базе отсутствует generic applicationState из более поздних telemetry PR, поэтому добавлен только read-only isApplicationInBackground у recorder, читающий уже существующий appSession provider. ZIP расширения и pixel schemas не менялись, более поздние telemetry PR не включены. Изменения macOS AppDelegate/ConfigurationStore/Tab и optional controllerConfiguration нужны для изоляции app-hosted тестов; shipping defaults сохранены.

Проверка новой ветки запущена: signed build-for-testing и lifecycle/navigation unit suites. Strict SwiftLint: 14 Swift-файлов, 0 violations; project.pbxproj проходит plutil, все пять новых Swift-файлов зарегистрированы в обоих Integration Tests targets. Артефакты: /private/tmp/cpm-p1-p2-clean-20261016/. Финальные результаты будут дописаны после прогонов; предыдущие 22/22 относятся к scratch, а не автоматически к этой новой сборке.

Адаптация к текущему контракту базовой ветки

При выделении проверено, что текущий alex/cpm-messaging-readiness-telemetry уже использует единый cpmMessagingEpisode, а не прежние initialization_failed/stuck/recovered events. Первый build выявил несовместимость перенесённого test adapter с удалёнными enum cases (build.log, exit 65). Исправлен исключительно test adapter, без возврата старой telemetry schema: CPMAppFailureRecorder прозрачно делегирует настоящий CPMMessagingDiagnosticsProviding.collectDiagnostics и фиксирует реальный first-failure snapshot, terminal events пишет существующий pixel sink. P2 требует outcome=failed_measurement_response, reload_state=none, плюс фактический page initResp и идентичность context. Это соответствует новой модели: сам episode dashboard response отдельно не доказывает доставку в страницу, поэтому DOM-подтверждение сохранено.

Повторный build-episode-adapter.log: TEST BUILD SUCCEEDED. Подтверждена Apple Development: Alexey Martemyanov (9SAT8X9S84), Team HKE973VLUW, strict verification app debug dylib и фактически загружаемого XCTest bundle успешна. 31 lifecycle unit test + 134 существующих manager/background-load/health-monitor unit tests = 165 PASS, 0 failures. App positive run запущен (enabled.xcresult), отрицательный контроль запланирован на той же сборке. Исходный bundled ZIP 2026.10.14 не изменён, SHA256 ff98a2b0c07819b2ee5ee6de67d6dd8d28696363c08c9fbc2c32255e9d9efa94.

Первый app-прогон выделенной ветки и cleanup fixture

enabled.xcresult: 1 PASS / 1 FAIL. P1 PASS (49.389 с). P2 функциональные assertions прошли: реальная error 6, first-failure snapshot, восстановление native dashboard и page initResp исходного документа, web_extension_cpm_episode(outcome=failed_measurement_response,reload_state=none), healthy existing tab/new tab/new window. Итоговый P2 FAIL (84.990 с) вызван штатным TestRunHelper.swift:453 deallocation guard: оставшиеся AppKit window-chrome views. Это не зелёный app-прогон и не считается PASS.

Для исправления fixture добавлены локальные autoreleasepool вокруг проксируемого collectDiagnostics и синхронного unload/configuration teardown; deallocation guard не отключён, исключения не добавлены. build-cleanup.log: TEST BUILD SUCCEEDED. Повтор обоих app-тестов выполняется в enabled-cleanup.xcresult; результат cleanup ещё проверяется. Исходный scratch сверён с сохранёнными hash предыдущего 22/22 прогона: ни один из его файлов не изменён.

Повтор app-прогона: оба положительных сценария PASS

enabled-cleanup.xcresult: 2/2 PASS, 0 skips, 0 failures, exit 0. P2 84.950 с; P1 49.376 с. Штатные deallocation checks включены и прошли после локальных autoreleasepool в fixture. Production-код с момента успешных 165 unit-тестов не менялся. P2 evidence содержит actualBackgroundLoadError [6], failed first probe, healthy existing/new-window/new-tab probes, terminal failed_measurement_response / reload_state=none; отдельные assertions проверяют доставку page initResp тому же исходному документу до следующей навигации и тот же context object. P1 evidence: healthy baseline и первый document после idle wake, no failures/episodes. Strict verification финальных app debug dylib и загружаемого XCTest bundle также PASS.

SwiftPM автоматически обновил только package-local content-scope-scripts pin 17.27→17.29 (текущий base BrowserServicesKit/Package.swift уже требует exact 17.29 и workspace lock уже имеет 17.29). Этот побочный diff сохранён как swiftpm-generated-pins.diff и отменён только в новом worktree; зависимостей в итоговом изменении нет. Baseline ZIP и все исходники финальной сборки сверены по cleanup-source-hashes.json: drift отсутствует.

Отрицательный контроль на тех же бинарниках выполняется в disabled.xcresult, с единственной дополнительной test-env переменной CPM_APP_INTEGRATION_DISABLE_P1_P2_FIXES=1. Финальный итог контроля будет записан отдельно.

Финальный итог отдельной ветки P1/P2 — 16 октября 2026, 13:33 UTC

Готово без коммитов: /Users/admin/DuckDuckGo/apple-browsers.git/alex/cpm-fix-p1-p2, branch alex/cpm-fix-p1-p2, HEAD/base 01d4a0ec22b4702ca9f9ea37757e060772ec7fa9 из alex/cpm-messaging-readiness-telemetry. Шесть новых методов: 2 app + 4 lifecycle unit, не 22; остальные 20 исследовательских app-кейсов оставлены в исходном worktree.

Проверка этой ветки Результат Артефакт
Signed app: P2 same-document recovery PASS, 84.950 с enabled-cleanup.xcresult
Signed app: P1 first document after slow wake PASS, 49.376 с enabled-cleanup.xcresult
Lifecycle (включая 4 новых и существующие navigation-gate проверки) 31 PASS, 0 skips unit.log
Существующие manager / background-load / health-monitor tests 134 PASS, 0 skips unit-related.log
Те же бинарники, P2 recovery flag OFF Ожидаемый functional FAIL, 104.468 с: timeout same-document recovery после реальной error 6 disabled.xcresult
Те же бинарники, P1 gate flag OFF Ожидаемый functional FAIL, 49.933 с: нет своевременных native dashboard/page initResp, есть first failure disabled.xcresult
Strict SwiftLint 14 Swift-файлов, 0 violations lint-final.log
Project registration plutil PASS; 5 файлов по одному разу в каждом из двух Integration Tests targets project.pbxproj

Итог положительного app-прогона 2/2 PASS, unit 165/165 PASS. Отрицательные контроли намеренно обычные красные XCTest runs, expectedFailures=0: это проверка чувствительности функциональных assertions, а не инфраструктурное падение. Deallocation checks в финальном положительном прогоне прошли; первая ошибка cleanup сохранена выше как история. Проверки выполнены на текущей базовой episode-телеметрии, без переноса старых pixel cases или более поздних baseline/init-progress слоёв.

Все артефакты: /private/tmp/cpm-p1-p2-clean-20261016/; машинная сводка validation-summary.json, source hashes cleanup-source-hashes.json, JSON evidence в enabled-cleanup-evidence/ и disabled-evidence/. Все 18 зафиксированных файлов (включая unchanged ZIP) неизменны во время финальных прогонов. Исходный scratch также сверён с hash прежнего 22/22 прогона: изменений нет. HEAD не сдвинут, commitsCreated=0, generated package pin diff убран из новой ветки.

Границы доказательства сохранены: P1 проверяет imposed 8 s worker start при gate cap 5 s; P2 — реальную error 6 на macOS с одним начальным window/tab и остановкой только своего PID. Естественный триггер в затронутых production-сессиях и поведение iOS suspension/resume этим не доказаны; отдельный resume recovery не добавлен. P3/P5 и другие research-сценарии не включены в чистую ветку и их прежние ограничения не сняты.